ES6学习笔记之let和const声明

作为一个前端开发者,ES6则是必备技能了,针对自己的学习,总结一些笔记,供自己以后回顾....
今天学习的是let和const声明,参考阮一峰的《ECMAScript 6 入门》http://es6.ruanyifeng.com/

let和const声明

let 声明,我把它称为块级声明,因为它的作用域只在它所在的块级作用域里有效,所以它和var是不同的,var的作用域是全局作用域.如下代码:

{
  let a = 10;
  var b = 1;
}
console.log(a);//ReferenceError: a is not defined
console.log(b);1

在开发过程中,强烈建议使用let替换var声明变量,看如下代码:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

上面代码中,因为i是var声明出来的,属于全局变量,每一次循环变量i都会发生改变,所以最后调用数组a索引为6的函数时,里面输出的i的值就是循环最后一次得到的i的值,所以结果为10.
这里一开始我是有点不清楚的,所以把执行步骤过一遍:
步骤1:var a = [],
步骤2:执行for循环,此例为10次,每次执行a[i] = function(){console.log(i)}
这时候,数组a里面的每个索引里的值都分别为function(){console.log(i)},如:

a[1] = function(){console.log(i)}
a[2] = function(){console.log(i)}
a[3] = function(){console.log(i)}
a[4] = function(){console.log(i)}
a[5] = function(){console.log(i)}
a[6] = function(){console.log(i)}
以此类推...

步骤3:调用a数组索引为6的方法

a[6]() //10

错误思维:认为console.log(i)中的i为独立的,所以当调用函数时,索引为6的i值也为6.这个思维时错误的,因为i是全局变量,当调用函数时,i值已经改变为最后一次循环的i值,则为10
换为let声明

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

此时变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
for循环的特别之处:
循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。

let声明的特性

不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

暂时性死区
由于let声明不存在变量提升,因此使用let声明变量之前先使用了该变量,则会报错.这种情况则称为暂时性死区.

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
还有一些比较隐蔽的死区,详情请移步阮一峰的ES6,本文也是参考这本书来总结笔记的.上面已经讲的非常详细了.总之造成暂时性死区的原因是因为变量未声明就使用.
不允许重复声明
使用let声明是不允许在相同作用域内被重复声明的,会报错.
先看看var重复声明代码:

 function x() {
    var x = 2;
    var x = 3;
    console.log(x);        
}
 x();//3

可见,var允许被重复声明,并且会覆盖前面的声明.

 function x() {
    let x = 2;
    let x = 3;
    console.log(x);        
}
 x();//aught SyntaxError: Identifier 'x' has already been declared

注意:函数的参数也是一种隐式的let声明,因此不能在函数内部声明参数,也会报错.

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

块级作用域
ES5的时候,只有全局作用域,和函数作用域,没有块级作用域的概念,
ES6引入了块级作用域.学习了let之后应该也能明显的感受到块级作用域的存在了吧.因为let声明只作用在当前块级作用域,

function f1() {
  let n = 5;
  if (true) {
    let n = 10;//变量n只在当前块有效
  }
  console.log(n); // 5

ES6允许块级作用域任意嵌套,每一层都是单独的作用,并且内层作用域可以与外层作用域有同名的变量.

{
      let insane = 'Hello World';
      {
        {
          {

            {
              let insane = 'Hello World'
              let x = 'default'
            }
            console.log(x) //报错,因为当前块作用域未声明x,
          }
        }
      }
    };

块级作用域的出现,几乎可以替换掉广泛应用的匿名立即执行函数表达式(匿名 IIFE)

// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级作用域写法
{
  let tmp = ...;
  ...
}

块级作用域与函数声明
ES5规定,函数只能在顶层作用域和函数作用域中声明,不能在块级作用域声明.
ES6允许函数在块级作用域中声明,并且仅作用于当前块,类似于let声明.

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

上例代码在ES5环境中运行,得到的结果为:"I am inside",因为在if内声明的函数f会被提升到函数头部,实际运行代码如下:

// ES5 环境
function f() { console.log('I am outside!'); }

(function () {
  function f() { console.log('I am inside!'); }
  if (false) {
  }
  f();
}());

ES6允许在块级作用域中声明函数,相当于使用let声明,因此,当前代码在ES6环境中,在if内声明的函数f不会被提升到函数头部,而且作用域仅在if语句的块中有效.
运行结果:
我的想法是会报错,报f is not defined
但实际上并不是报这个错,而是报了f is not a function,
原因是在ES6中声明函数有三条特殊的规定:

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。
    根据这三条规则,上面的代码实际运行代码如下:
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

强烈建议:避免在块级作用域中声明函数,如果实在需要,可以写成函数表达式,而不是函数声明语句

// 块级作用域内部的函数声明语句,建议不要使用
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 块级作用域内部,优先使用函数表达式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}
const

const声明一个只读的常量.顾名思义,常量就是一旦声明就不能改变的,改变常量的值就会报错.

const PI = 3.1415;
console.log(PI)//3.1415
PI = 3; // TypeError: Assignment to constant variable.

const声明之后一定要赋值,否则会报错

const foo;
// SyntaxError: Missing initializer in const declaration

const的特性和let完全一样,只在声明所在的块级作用域内有效,并且不存在变量提升,因此也会存在暂时性死区,同时也跟let一样不能重复被声明.详情可以查看阮一峰的ES6
重点注意
对于简单类型的数据(数据,字符串,布尔值),const声明的值是不可变的,
对于复杂类型的数据(数组,对象),const声明的值不能保证不可变,
原因是简单类型的数据保存在栈中,而复杂类型的数据保存在堆中,保存在栈中的只是一个指向实际数据指针,const声明只能保证保存在栈中的数据不可变,不能保证保存在堆中的数据不可变.
因此,如果需要对复杂类型进行冻结,不让它改变,可以使用Object.freeze方法。
来自MDN的解释:
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
这里不明白阮一峰写的这段话,希望有知道的同学能帮忙解释一下:
除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

这个Object.freeze()不是已经冻结了对象,然后对象的属性和值都不能被修改,不就代表属性也被冻结了吗.为什么还需要做递归冻结?

最后补充:
在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
const声明常量有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。
所以声明时优先考虑使用const,当遇到该值需要改变时,再将const改为let,这是比较好的代码习惯.
今天关于ES6的let和const声明学习笔记就到这里了, 希望能够帮助到正在阅读的你, 如文中有所纰漏, 希望能够指出, 感谢阅读.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容