深入剖析大厂面试题:解构与迭代器
面试题
面试官:你试试让以下代码成功完成解构。
var [myname,age]={myname:"张三",age:18}
我:大致看一眼,心想这不是相当的简单吗?这题是包拿下的。
我:怎么越看越不对劲了,好家伙,有点慌了。怎么和我见过的不一样。
var [a,b]=[1,2]//数组的解构
var { name, age } = { name: '张三', age: 18 }//对象的解构
var [myname,age]={myname:"张三",age:18}//这是什么东西
执行面试官给的代码会出现{(intermediate value)(intermediate value)} is not iterable
错误,立马联想到迭代器。开始回忆。
迭代器
迭代器的介绍
迭代器是某些数据结构的属性,并不是方法。可以被遍历的数据结构就会有迭代器属性,例如数组、Map和Set等,但是对象没有自带的迭代器属性。
迭代器就是一个对象,这个对象有一个next
方法。每次调用next
方法时会返回一个包含value
和done
键值对的对象,其中value
的值为当前迭代到的元素值、done
的值为布尔值,当done
的值为false表示迭代没有完成,当done
的值为true表示迭代完成。
手搓一个迭代器。
function createIterator(items) {
var i = 0;
return {
next: function () {
var done = i >= items.length
var value = !done ? items[i++] : undefined
return {
done: done,
value: value
}
}
}
}
var iterator = createIterator([1, 2, 3])
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
结果为:
在手搓的迭代器中运用了闭包的原理。在createIterator
方法运行完成后就应该会被垃圾回收机制给回收掉,但是iterator.next
方法执行时会访问createIterator
方法的执行上下文内的变量i
,所以在createIterator
方法的执行上下文被回收后仍然会在执行上下文栈上留下一个内存空间存储变量i
。每调用一次next
方法就会让变量i
自增,实现往后遍历。在没有遍历完时调用next
方法就会返回{ done: false, value: 当前迭代到的元素值 }
,在遍历完后再调用next
方法就会返回{ done: true, value: undefined }
。
for...of
for...of
是ES6新增加的循环结构。for...of
循环的引入可以让遍历操作变得更加直接和易读,减少了样板代码,提高了代码质量。
for...of语法
iterable
是可迭代的对象;variable
是一个变量,在每次迭代中该变量都会被赋值为当前iterable
中所迭代元素的值。
for (variable of iterable) {
// 循环体内的代码,variable为当前迭代到的值
}
for...of原理
for...of
执行时会对循环的数据结构进行判断,判断该数据结构是否具有可迭代性。
在JavaScript中存在一个特殊的方法——[Symbol.iterator]
方法,可以通过调用[Symbol.iterator]
方法获取迭代器。因此for...of
执行时会判断循环的数据结构是否拥有[Symbol.iterator]
方法,进而判断该数据结构是否具有可迭代性。
用一个例子理解[Symbol.iterator]
方法:
function createIterator(items) {
var i = 0;
return {
next: function () {
var done = i >= items.length
var value = !done ? items[i++] : undefined
return {
done: done,
value: value
}
}
}
}
const obj = {
value: 1
}
obj[Symbol.iterator] = function () {
return createIterator([1, 2, 3])
}
通过手搓一个迭代器和[Symbol.iterator]
方法,让obj
对象具有可迭代性。从例子中可以看出调用[Symbol.iterator]
方法返回的值就是一个迭代器(一个对象)。
接下来看看通过给一个对象手搓一个迭代器,并且让该对象具有[Symbol.iterator]
方法后是否可以执行for...of
循环。
for (let value of obj) {
console.log(value);
}
结果为:
手搓一个函数(不太严谨)模拟for...of
的逻辑。
function forOf(obj, cb) {
if (obj[Symbol.iterator]) {
throw new TypeError(obj + "is not iterable")
}
let iterator = obj[Symbol.iterator]()
let res = iterator.next()
while (!res.done) {
cb(res.value)
res = iterator.next()
}
}
var colors = ['red', 'black', 'blue']
forOf(arr, function (value) {
console.log(value);
})
- 首先判断需要循环的对象是否具有可迭代性。
- 然后获取该对象的迭代器,并且用变量
iterator
进行存储。 - 通过变量
res
存储调用迭代器的next
方法所返回的具有value
和done
的对象。 - 再通过while循环判断是否迭代完成,如果没有迭代完成则执行
for...of
循环体内的代码并且再次调用next
方法往后迭代。 - 直到迭代完该数据结构后结束。
面试题的解
回忆了一些关于迭代器的知识。但是这道面试题还涉及到了解构的知识。
简单回忆一下解构的核心原理。
//解构赋值的过程中也涉及到了迭代器
const newArr = ['red', 'black']
const [a, b] = newArr
//解构赋值的逻辑
var iterator = newArr[Symbol.iterator]()
a = iterator.next().value
b = iterator.next().value
回忆结束,开始解题。
var [myname,age]={myname:"张三",age:18}
要使该解构赋值可以成功,那么就需要给对象手搓一个迭代器。
那就给该对象添加一个[Symbol.iterator]
方法,但是不能直接在该对象里添加,而是在该对象的原型上添加。
Object.prototype[Symbol.iterator] = function () {
return {}
}
var [myname,age]={myname:"张三",age:18}
console.log(myname, age);
那返回的迭代器要怎么办呢?有迭代器的数据结构有数组,可以将对象和数组进行关联,然后再通过调用数组的[Symbol.iterator]
方法的返回值作为该对象的[Symbol.iterator]
方法的返回值。思想到位,开整。
可以通过Object.values(this)
返回一个由对象的键值构成的数组,再通过Object.values(this)[Symbol.iterator]()
作为返回值。
Object.prototype[Symbol.iterator] = function () {
return Object.values(this)[Symbol.iterator]()
}
var [myname,age]={myname:"张三",age:18}
console.log(myname, age);
结果为:
这道面试题终于拿下了。
转载自:https://juejin.cn/post/7377647067575762996