let [a, b] = {a: 1, b: 2}
面试官:“请你让这行代码成立,我希望 a = 1, b = 2”。
你的内心:“你是怎么用37°的嘴问出如此冰冷的问题的”。想归想,但如果你无法清晰的解释这个问题。那么认真看完这篇文章,谁看谁会
首先让我们搞明白这个问题问的是什么
这是一份数组的解构,但又像是对象的解构,我们只知道
js提供给我们的解构语法就是这般,那要把一个对象解构给数组,面试官究竟要考察我什么。要理清楚这个问题,我们需要先弄懂以下几个知识点
1. Iterator 接口
首先我们要对Iterator
这个接口有一个清晰的认识。js中提供了很多引用类型的数据,包括(对象,数组,Set,Map等等)
这些引用类型有各自独特的数据访问方式,但是在js中,我们希望对这些数据结构有一种统一的访问机制,于是官方就打造了 for...of
这样一个遍历方法,旨在用它能遍历这些数据结构。但是打造 for...of
的时候设定了这样一个规则,就是只有当这种数据结构拥有迭代器属性的时候,才能被 for...of
所遍历
这时候你可能会好奇为什么要打造
for...of
, 直接用for (let i = 0; i < x; i++) {}
来便利不好吗,事实是,如果我们只需要访问数组中的元素,使用for
语句会比for...of
需要定义更多的变量,以及数组的长度的读取等操作,无疑让代码变得更复杂更不优雅
1.1 迭代器属性
什么是迭代器属性,官方定义的迭代器属性指的是一个形如 [Symbol.iterator]
样的属性,值为一个函数体。简言之就是,一个对象上如果实现了 [Symbol.iterator]
方法,就被认为是可迭代的,就可以使用 for...of
语句来遍历。
让我们看看数组上目前内置了[Symbol.iterator]
属性的样子
当数组上的[Symbol.iterator]
函数被调用,得到了一个对象,该对象中具有 next
方法,每次调用 next
都会得到一个新的对象 { value: 1, done: false }。数组中的元素好像在以一种新的方式被我们得到 要解释这种新的方式我们就要细聊一聊关于迭代器协议
1.2 迭代器协议
迭代器本质上也是一个对象,但一个对象要成为迭代器,必须实现一个特定的协议,即 Iterator
协议。具体来说,该对象必须有一个 next()
方法,每次调用这个方法返回一个具有以下两个属性的对象:
value
:迭代器返回的当前元素。done
:一个布尔值,表示迭代器是否已经完成遍历。
既然协议的规则如此,那我们便了可以很简单的手搓出来一个迭代器对象
哦~~ 原来新形如 myIterator
这样子的对象就叫做迭代器!
1.3 可迭代协议
那如何让一个对象成为可迭代对象呢?看过 1.1 中数组的样子你已经猜到了。一个对象要成为可迭代对象,它必须实现 [Symbol.iterator]
方法。这个方法返回一个迭代器对象。js中所有内置的集合类型(如 Array, Map, Set, String 等)都已经实现了可迭代协议。
如此说来,一个可迭代的对象便是长这般模样了:
2. 解构问题
搞明白了什么是迭代器,什么是可迭代的对象,接下来我们看看解构当中的细节,如果我们直接在v8中运行 let [a, b] = {a: 1, b: 2}
这行代码会发生什么呢?
给看官老爷翻译一下这行报错:
类型错误: {(中间值)(中间值)} 不可迭代
,也就是说报错内容为对象 {} 不是一个可迭代对象
从这里我们可以得出两个结论:
- js中的普通对象不具有
[Symbol.iterator]
属性 - 以数组为例,let [x, y] = [1, 2],解构其实是将数组中的迭代器属性得到的值不断地调用
next
再将得到的对象中的value
赋值给变量x,y
悟了悟了!
3. 回答面试官
明白了以上内容,再回过头来思考面试官提出的问题,现在的嘴角已是非AK能压的
let [a, b] = {a: 1, b: 2}
要让该代码成立的最重要的一步,不就是想办法让对象{a: 1, b: 2}
具有[Symbol.iterator]
属性,并且遵照可迭代协议吗!
那我们顺势就能踏出这关键性的一步
// 往对象原型上添加迭代器属性,值为函数体
Object.prototype[Symbol.iterator] = function() {
}
let [a, b] = {a: 1, b: 2}
console.log(a, b);
可是迭代器属性要返回一个迭代器对象,该对象中要具有next
方法,那我们该如何打造这样一个迭代器对象呢?
再分析一下题目,题目希望我们将对象解构到数组上,这肯定是一个伪命题,其实是希望当v8开始执行解构语法时 代码能变成:let [a, b] = 数组的迭代器
那么答案就呼之欲出了,修改上述代码:
// 往对象原型上添加迭代器属性,值为函数体
Object.prototype[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
let [a, b] = {a: 1, b: 2}
console.log(a, b); // 1 2
解释一下这一行代码:
- 原型上函数体中的this指向同构造函数一样,都指向实例对象,所以这里的this指向的是 {a: 1, b: 2}
- Object.values(this) 获取到对象中所有的 value,以数组形式返回,所以得到 [1, 2]
- 直接返回数组自带的迭代器属性的值
那么当结构语句被v8执行的那一刻 let [a, b] = 数组的迭代器
这个逻辑就成立了,完美解决!
4. for...of的实现原理
顺带我们再提一句 for...of 是如何工作的
5. 总结
一道优秀的面试题,一定是在尽可能少的问题中个考察面试者尽可能多的知识点,这个问题考察了我们以下知识点:
- 对ES6解构的语法原理的认识
- 对原型的理解
- 对迭代器的理解
我愿称之为是一道非常nice的题目,你绷住了嘛
如果学习的道路上你孤独前行,可以加个wx好友,备注掘金小伙伴,我们有一群志同道合为了奔赴大厂正在努力的小伙伴: psc1023wn