ES6-标准入门·变量声明与解构赋值

作者 : jamin 本文共4992个字,预计阅读时间需要13分钟 发布时间: 2020-10-18 共984人阅读

变量声明与解构赋值

柏林已经来了命令,阿尔萨斯和洛林的学校只许教 ES6 了…他转身朝着黑板,拿起一支粉笔,使出全身的力量,写了两个大字:“ES6 万岁!”(《最后一课》)。

阮一峰的《ES6 标准入门》第二版和第三版都有购入,第二版是去年买的实体书,当初大略翻了一遍,今年第三版又出世了,在原来的基础上新增了不少内容,是时候重拾书本学习了。

Any application that can be written in JavaScript will eventually be written in JavaScript. –Jeff Atwood

let 和 const

基本用法

let 和 const 声明变量的三大特性:不存在变量提升、暂时性死区、不允许重复声明

不存在变量提升

let 声明的变量一定要在声明后使用,否则便会报错。

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

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

暂时性死区

ES6 规定,如果区块中存在 let 和 const 命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。这在语法上称为“暂时性死区”(temporal dead zone,简称 TDZ)。

var tmp = 123
if (true) {
  // TDZ 开始
  tmp = 'abc' // ReferenceError
  let tmp
  // TDZ 结束
  console.log(tmp) // undefined
}

“暂时性死区”也意味着 typeof 不再是一个百分之百安全的操作,变量用 let 声明的话,那么在声明之前引用会报错。作为比较,如果一个变量根本没有被声明,使用 typeof 反而不会报错。

typeof x // ReferenceError
let x

typeof undeclared_variable // "undefined"

总之,暂时性死区的本质就是,只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

不允许重复声明

let 和 const 命令不允许在相同作用域内重复声明同一个变量。

function foo(arg) {
  let arg // 报错
}

function bar(arg) {
  {
    let arg // 不报错
  }
}

扩展

扩展 1:const 实际上保证的是变量指向的那个内存地址不得改动。如果真的想将对象冻结,应该使用 Object.freeze 方法。

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

扩展 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 命令声明的全局变量不属于顶层对象的属性。

var a = 1
window.a // 1

let b = 1
window.b // undefined

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 这些方法都可能无法使用。

综上所述,很难找到一种方法可以在所有情况下都取到顶层对象。以下是两种勉强可以使用的方法:

// 方法一
typeof window !== 'undefined'
  ? window
  : typeof process === 'object' && typeof require === 'function' && typeof global === 'object'
  ? global
  : this

// 方法二
const getGlobal = function() {
  if (typeof self !== 'undefined') {
    return self
  }
  if (typeof window !== 'undefined') {
    return window
  }
  if (typeof global !== 'undefined') {
    return global
  }
  throw new Error('unable to locate global object')
}

变量的解构赋值

数组的解构赋值

解构(Destructuring)属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

let [x, y, ...z] = ['a']
x // "a"
y // undefined
z // []

// 不完全解构
let [a, b] = [1, 2, 3]
a // 1
b // 2

对于 Set 结构,也可以使用数组的解构赋值。

let [x, y, z] = new Set(['a', 'b', 'c'])
x // "a"

事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

function* fibs() {
  let a = 0
  let b = 1
  while (true) {
    yield a
    ;[a, b] = [b, a + b]
  }
}

let [first, second, third, fourth, fifth, sixth] = fibs()
sixth // 5

解构赋值允许指定默认值。ES6 内部使用严格相等运算符(===)判断一个位置是否有值。所以,如果一个数组成员不严格等于 undefined,默认值是不会生效的

let [x = 1] = [undefined]
x // 1

let [y = 1] = [null]
y // null

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到时才会求值。

// x 可以取值,函数不会执行
function f() {
  console.log('aaa')
}
let [x = f()] = [1]

默认值可以引用解构赋值的其他变量,但该变量必须已经声明:

let [x = 1, y = x] = [] // x=1; y=1

对象的解构赋值

对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' }
baz // 'aaa'

上面的代码中,foo 是匹配的模式,baz 才是变量。真正被赋值的是变量 baz,而不是模式 foo。

与数组一样,解构也可以用于嵌套结构的对象:

let obj = { p: ['Hello', { y: 'World' }] }
let {
  p: [x, { y }]
} = obj

注意,这时 p 是模式,不是变量,因此不会被赋值。如果 p 也要作为变量赋值,可以写成下面这样。

let {
  p,
  p: [x, { y }]
} = obj

同数组解构一样,对象解构也可以使用默认值,默认值生效的条件是,对象的属性值严格等于 undefined。

注意:如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法
let x
;{ x } = { x: 1 } // SyntaxError: syntax error

上面代码的写法会报错,因为 JavaScript 引擎会将 { x } 理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免将其解释为代码块,才能解决这个问题。

// 正确的写法
let x
;({ x } = { x: 1 })

由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

let arr = [1, 2, 3]
let { 0: first, [arr.length - 1]: last } = arr
first // 1
last // 3

字符串的解构赋值

对字符串解构时,字符串会被转换为类数组对象。

const [a, b, c, d, e] = 'hello'
a // 'h'
b // 'e'

数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let { toString: s } = 123
s === Number.prototype.toString // true

let { toString: s } = true
s === Boolean.prototype.toString // true

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值时都会报错。

let { prop: x } = undefined // TypeError
let { prop: y } = null // TypeError

函数参数的解构赋值

function move({ x, y } = { x: 0, y: 0 }) {
  return [x, y]
}
move({ x: 3, y: 8 }) // [3, 8]
move({ x: 3 }) // [3, undefined]
move({}) // [undefined, undefined]
move() // [0, 0]

圆括号问题

解构赋值时,对于编译器来说,一个式子到底是模式还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。

ES6 规定:只要有可能导致解构的歧义,就不得使用圆括号

不能使用圆括号的情况

  1. 变量声明语句
// 全部报错
let [(a)] = [1]
let {x: (c)} = {}
let ({x: c}) = {}
let {(x: c)} = {}
let {(x): c} = {}
let { o: ({ p: p }) } = { o: { p: 2 } }

上面 6 个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。

  1. 函数参数

函数参数也属于变量声明,因此不能使用圆括号。

// 报错
function foo([(z)]) { return z }
  1. 赋值语句的模式
// 全部报错
;({ p: a }) = { p: 42 }
;([a]) = [5]

上面的代码将整个模式放在圆括号之中,导致报错。

// 报错
;[{ p: a }, { x: c }] = [{}, {}]

上面的代码将一部分模式放在圆括号之中,导致报错。

可以使用圆括号的情况

可以使用圆括号的情况只有一种:赋值语句的非模式部分可以使用圆括号。

;[b] = [3] // 正确
;({ p: d } = {}) // 正确
;[parseInt.prop] = [3] // 正确

上面 3 行语句都可以正确执行,因为它们都是赋值语句,而不是声明语句,另外它们的圆括号都不属于模式的一部分:

  • 第 1 行语句中,模式是取数组的第 1 个成员,跟圆括号无关;
  • 第 2 行语句中,模式是 p 而不是 d;
  • 第 3 行语句与第 1 行语句的性质一致。

扩展

任何部署了 Iterator 接口的对象都可以用 for…of 循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值获取键名和键值就非常方便。

// 获取键名
for (let [key] of map) {
  // ...
}
// 获取键值
for (let [, value] of map) {
  // ...
}
本站所提供的部分资源来自于网络,版权争议与本站无关,版权归原创者所有!仅限用于学习和研究目的,不得将上述内容资源用于商业或者非法用途,否则,一切后果请用户自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容资源。如果上述内容资对您的版权或者利益造成损害,请提供相应的资质证明,我们将于3个工作日内予以删除。本站不保证所提供下载的资源的准确性、安全性和完整性,源码仅供下载学习之用!如用于商业或者非法用途,与本站无关,一切后果请用户自负!本站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。如有侵权、不妥之处,请联系站长以便删除!
金点网络-全网资源,一网打尽 » ES6-标准入门·变量声明与解构赋值

常见问题FAQ

免费下载或者VIP会员专享资源能否直接商用?
本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
是否提供免费更新服务?
持续更新,永久免费
是否经过安全检测?
安全无毒,放心食用

提供最优质的资源集合

立即加入 友好社区
×