渐深学习中,让解构成立引发的思考

整理了一些对比论和考点,可直接移步知识点梳理

面试原题

js 复制代码
// 让下面的代码成立
var [a, b] = {
    a: 3,
    b: 4
}
console.log(a, b) // 3, 4

我们先直接执行这段代码,会得到什么呢

Uncaught TypeError: {(intermediate value)(intermediate value)} is not iterable

答案是会报错,因为对象不是可迭代的,所以解构赋值会报错

引发第一个思考

什么是可迭代对象

满足可迭代协议的对象,就是可迭代对象。

可迭代协议

  • 对象属性有 Symbol.iterator, 值是函数,返回一个迭代器,迭代器的属性有 next 方法,返回一个对象,对象有 done 和 value 两个属性,done 表示是否迭代完成,value 表示迭代的值
  • 一个对象既可以是 可迭代对象(拥有[Symbol.iterator]),又可以是迭代器(自身含有next()方法)。
javascript 复制代码
{
    [Symbol.iterator]: function (){
        return {
            next:function () {
                return {
                    value: value/undefined,
                    done: true/false => 完成/未完成
                }
            }
        }
    }
}

由此我们可以进行尝试,将题目的对象变成一个可迭代对象,然后解构赋值

js 复制代码
var [a, b] = {
    a: 3,
    b: 4,
    [Symbol.iterator]: function () {
        // 利用数组的迭代器
        return Object.values(this)[Symbol.iterator]()
    }
}
console.log(a, b)

题目的要求不能改变源代码,所以我们可以在对象原型上添加 Symbol.iterator 属性,开始尝试

js 复制代码
Object.prototype[Symbol.iterator] = function () {
    return Object.values(this)[Symbol.iterator]()
}
var [a, b] = {
    a: 3,
    b: 4
}
console.log(a, b)

在 ES6 中,还有一个生成器,也可用于此

js 复制代码
Object.prototype[Symbol.iterator] = function* () {
    yield* Object.values(this)
}

ES6 内建可迭代类型

  • Array、 String、 Map、 Set、 arguments、 NodeList DOM 集合
  • 解构、for of、展开运算符(...)
  • ECMAScript 的迭代机制基于可迭代协议统一了对数据结构的访问方式,使得各种集合类型数据结构可以通过一致的语法进行遍历。

追加下一个问题,什么是迭代器

此处文献来自掘金

在 JavaScrip 中有非常多的集合,比如数组、set、map等,他们是用来存放数据的集合。数据必然存在遍历,通常会使用for来遍历,就需要书写比较长的代码。官方就提供了一个属性 Iterable,并设定具有 Iterable 属性的数据结构就是可迭代的。

手搓一个迭代器

了解内部结构,助于理解迭代器原理

js 复制代码
function MyIterator(arr) {
    let i = 0;
    return {
        next: function () {
            let done = i >= arr.length;
            let value = !done ? arr[i++] : undefined;
            return {
                done,
                value
            }
        }
    }
}

let iterator = MyIterator([1, 2, 3]);
console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
console.log(iterator.next()); // { done: false, value: 3 }
console.log(iterator.next()); // { done: true, value: undefined }

for of

只要是拥有迭代器属性的数据结构,那么他就可以被 for of 遍历,也就是说我们只要在某个数据结构上添加迭代器属性,那么这个数据结构就可以被 for of 遍历。

for of 遍历的机制就是,先获取迭代器,然后通过 next 方法获取迭代的值,直到迭代完成,for of 遍历结束。

我们来看这段代码

js 复制代码
let obj = {
    value: 1
}
for (let item of obj) {
    console.log(item)
}

这段代码的执行结果会报错,obj is not iterable, 这点其实也证实了 for of 遍历的是这个数据结构身上的迭代器属性,我们尝试给 obj 添加迭代器属性

js 复制代码
let obj = {
    value: 1,
}
obj[Symbol.iterator] = function () {
    return MyIterator([1, 2])
}
for (let item of obj) {
    console.log(item) // 1, 2
}

可以看到 obj 可以被 for of 遍历了,但是并不是遍历的 Obj 身上的属性,而是传进去的数组,因为 for of 遍历的是 obj 上的迭代器对象。

继续手撕一个 for of

for of 首先检索对象身上是否有迭代器属性,如果没有则会报错,如果有,则通过 next 方法获取迭代的值,直到迭代完成,for of 遍历结束。

js 复制代码
function myForOf(obj, callback) {
    if (!obj[Symbol.iterator]) {
        throw new Error(obj + ' is not iterable');
    }
    let iterator = obj[Symbol.iterator]();
    let item = iterator.next();
    while (!item.done) {
        callback(item.value);
        item = iterator.next();
    }
}

let arr = [1, 2, 3];
myForOf(arr, (item) => {
    console.log(item);
})

持续追问,什么是生成器(Generator)

  • 生成器是 ES6 引入的一种特殊函数形式,具备"暂停执行"和"恢复执行"的能力。它不是一次性执行到底,而是通过 yield 分段执行,由调用者控制流程。
  • 生成器的本质是状态机,其内部维护着上下文信息,在多个 yield 之间保持函数状态,是构建复杂控制流(如携程、异步流程控制)的基础

基本语法

1.定义生成器函数
js 复制代码
function* generatorFunction() {
    yield 'hello';
    yield 'world';
}
  • 使用 function* 定义
  • 使用 yield 暂停和返回数据
2. 调用方式
js 复制代码
let generator = generatorFunction();
console.log(generator.next()); // { value: 'hello', done: false }
console.log(generator.next()); // { value: 'world', done: false }
console.log(generator.next()); // { value: undefined, done: true }

生成器的工作机制

关键词 说明
yield 暂停函数执行,返回数据
next() 恢复函数执行,传入数据给上一个 yield 表达式
done 表示函数是否执行完毕
return() 提前结束生成器,并返回值
throww() 向生成器内部抛出异常

示例:带参数的 .next()

js 复制代码
function* calc() {
    const x = yield 10;
    const y = yield x + 5;
    return y;
}

const it = calc();
console.log(it.next()); // { value: 10, done: false }
console.log(it.next(20)); // { value: 25, done: false }
console.log(it.next(99)); // { value: 99, done: true } 

生成器 vs 普通函数

特性 普通函数 生成器函数
是否可以中断执行 是(yield 暂停)
是否可多次返回值 是(通过多次 yield)
返回值类型 任意 Iterator 迭代器对象
控制权 函数内部 调用方可控制执行流程

生成器的用途与应用场景

  • 惰性求值/无线序列

    节省内存,在需要的时候再生成结果

    js 复制代码
    function* fibonacci() {
      let i = 0;
      while (true) yield i++;
    }
  • 自定义迭代器

    js 复制代码
    const MyIterator = {
      *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;  
      }
    }
  • 控制异步流程

    js 复制代码
    function* getData() {
      const user = yield fetchUser();
      const posts = yield fetchPosts(user.id);
    }

常见面试高频问题

生成器和 async/await 的区别
特性 Generator async/await
本质 可中断函数 异步语法糖
是否返回 Promise
控制流程 手动调用 .next() 自动执行
场景 同步流程、数据流 异步流程
yield*的作用?

委托执行另一个生成器或可迭代对象

js 复制代码
function* inner() {
    yield 1;
    yield 2;
}

function* outer() {
    yield 'start';
    yield* inner(); // 委托 inner 的全部 yield
    yield 'end';
}

注意事项

  • 生成器不能用箭头函数(function* 是关键字)
  • 生成器本质是 "暂停式函数",但每次恢复执行时上下文仍然保留
  • yield 只能在生成器函数中使用
  • 每次调用 next() 都会推进一次 yield
  • 可以使用 return() 提前终止生成器。
  • throw() 可以向生成器内部抛异常。

生成器速记口诀

vbnet 复制代码
function 星星起手式,yield 中断给值时;
next 叫醒继续跑,return throw 提前跑;
惰性状态都能搞,async 之前它称王。

生成器与迭代器

生成器是"自动构建迭代器的工厂函数"

js 复制代码
function* gen() {
    yield 1;
    yield 2;
}

const it = gen();
typeof it.next === 'function'; // true
it[Symbol.iterator] === it; // true
  • 生成器返回的对象天然遵守迭代器协议
  • 同时也是可迭代对象

生成器 + 迭代器组合使用场景

实现复杂自定义迭代器(优雅替代next()写法
js 复制代码
const obj = {
    data: [1, 2, 3],
    * [Symbol.iterator]() {
        for (let item of this.data) {
            yield item * 2;
        }
    }
}

for (let item of obj) {
    console.log(item); // 2, 4, 6
}
用生成器实现延迟计算(惰性求值)
js 复制代码
function* lazyMap(arr, fn) {
    for (let item of arr) {
        yield fn(item);
    }
}

const square = lazyMap([1, 2, 3], x => x * x);

for (let item of square) {
    console.log(item); // 1, 4, 9
}
避免无限循环内存爆炸
js 复制代码
function* forMap() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

// 通过 for of 控制迭代数量

let count = 0;
for (let item of forMap()) {
    if (count++ > 10) break;
    console.log(item);
}
多层遍历嵌套 yield* 委托
js 复制代码
function* gen() {
    yield 1;
    yield* inner();
    yield 3;
}

function* inner() {
    yield 2;
}

for (let item of gen()) {
    console.log(item); // 1, 2, 3
}

yield* 可委托数组、Set、Map、字符串、生成器等可迭代对象

知识点梳理

一、基础识记类

问题 答案要点
什么是可迭代协议? 实现了 Symbol.iterator() 方法,返回一个迭代器。
什么是迭代器协议? 拥有 .next() 方法,返回 { value, done }
for...of 要求什么? 对象必须是 iterable(实现 Symbol.iterator())。
Symbol.iteratornext() 区别? Symbol.iterator() 返回迭代器,.next() 控制执行。
如何判断可迭代? typeof obj[Symbol.iterator] === 'function'
yield 作用? 暂停执行,向外返回值,可双向通信。

二、 原理机制类

问题 答案要点
生成器 vs 普通函数? 可暂停、返回迭代器、使用 yield
为什么生成器是状态机? yield 为状态切换点,每次 .next() 推进状态。
yield.next() 怎么通信? .next(val)val 作为上一个 yield 的返回值。
yield* 作用? 委托另一个可迭代对象的迭代过程。
手动实现生成器执行器? 递归调用 .next() 推进流程。
为什么说生成器是迭代器工厂? 返回对象即是迭代器,实现自定义控制流程。

三、 实战应用类

应用场景 示例
无限整数流 function* infinite() { let i = 0; while(true) yield i++; }
惰性 map function* lazyMap(arr, fn) { for (let a of arr) yield fn(a); }
给对象加迭代能力 *[Symbol.iterator]() { for (let d of this.data) yield d; }
分帧执行 for (let i=0; i<10000; i++) { yield; }
模拟 async/await 使用 run(gen) 推进器配合 Promise 实现异步控制

四、 注意事项类

问题 答案要点
生成器能用箭头函数吗? 不能,箭头函数不支持 yield
yield 可在普通函数中用吗? 不可,语法错误。
.return().throw() .return(val) 结束迭代;.throw(err) 抛异常。
提前终止生成器后果? 后续 yield 不再执行,注意资源清理(用 finally)。

五、对比迁移类

生成器 vs async 函数
对比项 生成器 async 函数
暂停方式 yield await
执行控制 手动 .next() 自动推进
异步控制 需结合执行器和 Promise 内建异步控制
返回值 迭代器对象 Promise
生成器 vs Promise
对比项 生成器 Promise
控制的是 执行流程 异步值
执行方式 手动推进 自动执行
状态 多次暂停/恢复 单次完成/拒绝
可迭代对象 vs 类数组对象
对比项 可迭代对象 类数组对象
是否可 for...of 不可(除非手动实现)
是否有 Symbol.iterator 不可
是否有索引和 length 不一定 一定有
相关推荐
hhw1991122 分钟前
vue总结
前端·javascript·vue.js
学习2年半3 分钟前
汇丰eee2
前端·spring
代码续发5 分钟前
Vue进行前端开发流程
前端·vue.js
zpjing~.~8 分钟前
CSS &符号
前端·css
冴羽44 分钟前
SvelteKit 最新中文文档教程(19)—— 最佳实践之身份认证
前端·javascript·svelte
拉不动的猪1 小时前
ES2024 新增的数组方法groupBy
前端·javascript·面试
huangkaihao1 小时前
单元测试 —— 用Vitest解锁前端可靠性
前端
archko1 小时前
telophoto源码查看记录
java·服务器·前端
倒霉男孩1 小时前
HTML5元素
前端·html·html5
柯南二号2 小时前
CSS 学习提升网站或者项目
前端·css