01 变量声明与解构赋值
柏林已经来了命令,阿尔萨斯和洛林的学校只许教 ES6 了...他转身朝着黑板,拿起一支粉笔,使出全身的力量,写了两个大字:“ES6 万岁!”(《最后一课》)。
阮一峰的《ES6 标准入门》第二版和第三版都有购入,第二版是去年买的实体书,当初大略翻了一遍,今年第三版又出世了,在原来的基础上新增了不少内容,是时候重拾书本学习了。
Any application that can be written in JavaScript will eventually be written in JavaScript. --Jeff Atwood
let 和 const
基本用法
let 和 const 声明变量的三大特性:不存在变量提升、暂时性死区、不允许重复声明。
不存在变量提升
let 声明的变量一定要在声明后使用,否则便会报错。
暂时性死区
ES6 规定,如果区块中存在 let 和 const 命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。这在语法上称为“暂时性死区”(temporal dead zone,简称 TDZ)。
“暂时性死区”也意味着 typeof 不再是一个百分之百安全的操作,变量用 let 声明的话,那么在声明之前引用会报错。作为比较,如果一个变量根本没有被声明,使用 typeof 反而不会报错。
总之,暂时性死区的本质就是,只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
let 和 const 命令不允许在相同作用域内重复声明同一个变量。
扩展
扩展 1:const 实际上保证的是变量指向的那个内存地址不得改动。如果真的想将对象冻结,应该使用 Object.freeze
方法。
扩展 2:ES5 只有两种声明变量的方法:使用 var 命令和 function 命令。ES6 除了添加了 let 和 const 命令,还有另外两种声明变量的方法:import 命令和 class 命令。所以,ES6 一共有 6 种声明变量的方法。
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,容易出现变量覆盖和变量泄露的问题。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定:在块级作用域之中,函数声明语句的行为类似于 let,在块级作用域之外不可引用。
块级作用域的出现,实际上使得获得广泛应用的立即执行匿名函数(IIFE)不再必要了。
但是由于这条规则会对旧代码产生很大影响。为了减轻因此产生的不兼容问题,浏览器可以不遵守这条规则,所以尽量避免在块级作用域内声明函数。
顶层对象的属性
顶层对象在浏览器环境中指的是 window 对象,在 Node 环境中指的是 global 对象。在 ES5 中,顶层对象的属性与全局变量等价。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var 命令和 function 命令声明的全局变量依旧是顶层对象的属性;另一方面规定,let 命令、const 命令、class 命令声明的全局变量不属于顶层对象的属性。
global 对象
ES5 的顶层对象在各种实现中是不统一的。
在浏览器中,顶层对象是 window,但 Node 和 Web Worker 没有 window。
在浏览器和 Web Worker 中,self 也指向顶层对象,但是 Node 没有 self。
在 Node 中,顶层对象是 global,但其他环境都不支持。
同一段代码为了能够在各种环境中都取到顶层对象,目前一般是使用 this 变量,但是也有局限性。
在全局环境中,this 会返回顶层对象。但是在 Node 模块和 ES6 模块中,this 返回的是当前模块。
对于函数中的 this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this 会指向顶层对象。但是严格模式下,this 会返回 undefined。
不管是严格模式,还是普通模式,
new Function('returnthis')()
总会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么 eval、new Function 这些方法都可能无法使用。
综上所述,很难找到一种方法可以在所有情况下都取到顶层对象。以下是两种勉强可以使用的方法:
变量的解构赋值
数组的解构赋值
解构(Destructuring)属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
对于 Set 结构,也可以使用数组的解构赋值。
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。ES6 内部使用严格相等运算符(===)判断一个位置是否有值。所以,如果一个数组成员不严格等于 undefined,默认值是不会生效的。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到时才会求值。
默认值可以引用解构赋值的其他变量,但该变量必须已经声明:
对象的解构赋值
对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。
上面的代码中,foo 是匹配的模式,baz 才是变量。真正被赋值的是变量 baz,而不是模式 foo。
与数组一样,解构也可以用于嵌套结构的对象:
注意,这时 p 是模式,不是变量,因此不会被赋值。如果 p 也要作为变量赋值,可以写成下面这样。
同数组解构一样,对象解构也可以使用默认值,默认值生效的条件是,对象的属性值严格等于 undefined。
注意:如果要将一个已经声明的变量用于解构赋值,必须非常小心。
上面代码的写法会报错,因为 JavaScript 引擎会将 { x } 理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免将其解释为代码块,才能解决这个问题。
由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
字符串的解构赋值
对字符串解构时,字符串会被转换为类数组对象。
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值时都会报错。
函数参数的解构赋值
圆括号问题
解构赋值时,对于编译器来说,一个式子到底是模式还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
ES6 规定:只要有可能导致解构的歧义,就不得使用圆括号。
不能使用圆括号的情况
变量声明语句
上面 6 个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。
函数参数
函数参数也属于变量声明,因此不能使用圆括号。
赋值语句的模式
上面的代码将整个模式放在圆括号之中,导致报错。
上面的代码将一部分模式放在圆括号之中,导致报错。
可以使用圆括号的情况
可以使用圆括号的情况只有一种:赋值语句的非模式部分可以使用圆括号。
上面 3 行语句都可以正确执行,因为它们都是赋值语句,而不是声明语句,另外它们的圆括号都不属于模式的一部分:
第 1 行语句中,模式是取数组的第 1 个成员,跟圆括号无关;
第 2 行语句中,模式是 p 而不是 d;
第 3 行语句与第 1 行语句的性质一致。
扩展
任何部署了 Iterator 接口的对象都可以用 for...of 循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值获取键名和键值就非常方便。
最后更新于