1. let命令
let命令声明的变量只在所在的代码块中有效。for循环中很适合使用let,如果使用var则会创建一个全局变量,且for循环中声明的函数如果涉及i,指向的是同一个i值。在循环外调用永远是同一个值。
var a = []; for (var i = 0; i < 10; i++){ a[i] = function (){ console.log(i); }; } a[6](); //10
如果使用let的话,每一次循环中的i都是一个由let重新声明的新变量。上面这个例子如果使用let,最后的输出会是6。之所以能有这样的效果,是因为JavaScript引擎内部会记住上一轮循环的值,从而在上一轮的基础上初始化本轮的值。
另外,在for循环中,设置循环变量的部分是一个父作用域,而循环体是一个单独的子作用域。这意味着可以在循环体内用let单独声明i,并且与循环变量没有影响。
此外,let还有三个重要的特性:
- 不存在变量提升:var有着变量提升的特点,在声明之前使用变量,值为undefined。但let在声明前使用会直接报错。
- 暂时性死区(TDZ):只要块级作用域中存在let命令,那么这个声明就“绑定”了这个区域。哪怕这个变量已经有同名的全局变量被声明。在声明语句出现之前,这个变量都是存在但不可以使用的,如果使用则会报错。因此typeof运算符也不再是绝对安全的操作了。
- 不允许重复声明:不允许在相同作用域内重复声明一个变量,也不能在函数内部重新声明参数。
不存在变量提升和暂时性死区的特性,都是为了减少运行时错误,防止在变量声明前就提前使用这个变量,从而导致意料之外的行为。
2. 块级作用域
let实际上为ES6新增了块级作用域。原本使用广泛的匿名立即执行函数表达式不再需要了。
函数能不能在块级作用域中声明是一个很复杂的问题。ES5规定,函数不能在块级作用域中声明(如if语句中)。而ES6引入了块级作用域,允许在块级作用域中声明函数。行为类似于let,在作用域外不得引用。
但在ES6浏览器中,为了减轻兼容带来的问题,允许浏览器有自己的行为方式。ES6浏览器会将函数声明提升到全局作用域或函数作用域头部,以及所在块级作用域的头部,所以在ES6浏览器环境下,函数声明是会报错的。
// 浏览器的 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
由于环境导致的行为差异较大,不推荐在块级作用域中声明函数。如果一定要这么做,建议写成函数表达式的形式。
另外需要注意,ES6的块级作用域必须用大括号包裹起来,如果没有的话JavaScript引擎不认为存在块级作用域。
3. const命令
const用于声明一个只读的常量,声明之后不得更改。这也说明const一旦声明必须立刻初始化,不能留到以后赋值。const与let一样,只在作用域内有效,不存在变量提升,存在暂时性死区,不能重复声明。
const实际上保证的不是变量的值不能改动,而是变量指向的内存地址所保存的数据不能改动。对于简单数据类型,值就保存在那个内存地址中,所以等同于常量。但对符合类型的数据来说,内存地址中保存的是一个指针,const只能保证这个指针是固定的(指向不能更改),但不能保证它指向的数据结构不发生改变。
如果真的想要将对象冻结,只能使用 Object.freeza 方法。
彻底冻结对象,不仅仅要冻结对象本身,还要将对象的属性也全部冻结。具体实现如下:
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };
ES5只有两者变量声明方法:var 和 function 。而ES6有六种,除了这里介绍的 let 和 const,还有import 和 class。
4. 顶层对象的属性,globalThis对象
ES5中,顶层对象属性与全局对象是等价的。这样有很多缺点,如无法在编译时就报出变量未声明的错误,只有运行才知道(因为全局变量的可能是顶层对象的属性创造的,而属性的创造是动态的)。
ES6为了改变这一点,使得 let 和 const 和 class 声明的全局变量不属于顶层对象的属性。
ES2020引入globalThis对象作为顶层对象,任何环境下他都存在,并且可以指向全局环境的this。