let 和 const 命令
let 命令是用来声明块级作用域的变量,
const 命令用来声明块级作用域的常量。
let 在 for 循环中的应用
for循环的计数器适合用 let 来声明。
1)错误的使用闭包
变量 i 是 let 声明的,当前的i只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量。
1 | // 错误的闭包 |
2)for 循环的特别之处
设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
这和下文中会提到的暂时性死区不同。
1 | for (let i = 0; i < 3; i++) { |
const 的本质
const 一旦声明变量,就必须立即初始化,不能留到以后赋值。
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
对于复合类型的数据,变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的。
1 | const foo = {}; |
将对象彻底冻结的函数
1 | var constantize = (obj) => { |
特性
1)不存在变量提升
所谓『变量提升』:
仅发生在使用 var 命令声明变量时,该变量在声明之前便能使用,值为 undefined。
let 命令所声明的变量一定要在声明后才能使用。
1 | // var 的情况 |
2)存在暂时性死区
ES6明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
代码块内,在使用 let 和 const 命令声明变量之前,该变量都是不可用的。
在语法上,把这个区域称为“暂时性死区”(temporal dead zone,简称 TDZ)。
1 | if (true) { |
『暂时性死区』让 typeof 命令不再是一个百分之百安全的操作。
1 | typeof undeclared_variable // "undefined" |
因为『暂时性死区』的存在,函数参数的默认值未声明就使用,也会报错。
1 | function bar(x = y, y = 2) { |
使用 let 声明变量时,变量在还没有声明完成前使用,也会报错。
1 | // 不报错 |
暂时性死区的本质:
只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现后,才可以获取和使用该变量。
3)不允许重复声明
在相同作用域内,不允许重复声明,受『暂时性死区』影响的变量。
4)do 表达式
本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
在块级作用域以外,没有办法得到快内变量的值,因为块级作用域不返回值,除非该变量是全局变量。
现在有一个提案,使得块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为 do 表达式。
1 | let x = do { |
顶层对象
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。
ES5之中,顶层对象的属性与全局变量是等价的。
ES6为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
1 | var a = 1; |
ES5 的顶层对象在各种实现里面是不统一的。
- 浏览器里面,顶层对象是 window,但 Node 和 Web Worker 没有window。
- 浏览器和 Web Worker 里面,self 也指向顶层对象,但是 Node 没有 self。
- Node 里面,顶层对象是 global,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
- 全局环境中,this 会返回顶层对象。
但是,Node 模块和 ES6 模块中,this 返回的是当前模块。 - 函数里面的 this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this 会指向顶层对象。
但是,严格模式下,这时this会返回undefined。 - 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。
但是,如果浏览器用了CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
两种勉强可以取到顶层对象的方法:
1 | // 方法一 |
现在有一个提案,在语言标准的层面,引入global作为顶层对象。
也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象。
垫片库 system.global 模拟了这个提案,可以在所有环境拿到 global。
1 | // CommonJS 的写法 |