本文深入解析ECMAScript中的迭代器机制,从传统循环遍历到ES6引入的迭代器模式。详细讲解Symbol.iterator工厂函数、可迭代协议以及next()方法的done与value属性,帮助开发者理解如何自定义迭代器并优化数据结构遍历。
前言
在初级前端开发者向中级进阶的过程中,面试常会考察迭代器和生成器的相关知识。虽然实际开发中经常使用,但许多开发者对其原理缺乏深入了解或仅停留在表面认知。本文旨在为这部分开发者厘清迭代器的核心概念,提供实质性的帮助。
较早的迭代
迭代的概念通俗理解即为循环。在JavaScript中,基于计数的循环是最基础的实现方式。具体示例如下:
for(let i = 0; i < 9; ++i){
console.log("[ i ]", i);
}
迭代的核心基础在于循环,其运行需满足以下几个必要条件:
- 必须能够指定迭代的总次数。
- 必须能够指定每次迭代所执行的具体操作。
- 每次迭代的执行必须在下次迭代开始前完成。
- 遍历的顺序必须是预先定义好的。
当需要遍历数组时,迭代操作是在一个有序集合上进行的。“有序”意味着数组中的所有元素可以按照从第一项到最后一项的顺序被完整遍历。由于数组具有确定的长度,且每一项都可通过索引下标获取,因此可以通过索引方式遍历整个数组。具体示例如下:
const arr = ["a", "b", "c"];
for(let i = 0; i < arr.length; ++i){
console.log(arr[i]);
}
然而,这种遍历模式存在局限性,即必须事先明确所使用的数据结构(如数组)。若替换为其他数据类型或具有隐式顺序的数据结构,遍历顺序将变得不确定。
针对这一问题,ES5引入了forEach()方法。具体示例如下:
const arr = ["a", "b", "c"];
arr.forEach(item=>{
console.log(item);
});
该方法无需通过数组索引来遍历和获取单项值,但无法明确标识迭代何时结束,因此仅适用于数组遍历。为了解决这些限制,ES6之后JavaScript引入了迭代器模式。
迭代器模式
迭代器模式是一个较为抽象的概念,可以将其理解为数组或集合等对象,其元素数量有限且相互独立、无歧义。引用《红宝书》的解释如下:
迭代器模式(特别是在ECMAScript语境下)描述了一种方案:将某些结构称为“可迭代对象”(iterable),因为它们实现了正式的Iterable接口,并且可以通过迭代器(Iterator)进行消费。
迭代器工厂函数
迭代器工厂函数即Symbol.iterator(),它是大部分内置类型共有的默认属性。通过该属性暴露Iterable接口(即可迭代协议),这意味着数据类型若要支持迭代,必须遵循可迭代协议。
ECMAScript规定,暴露的默认迭代器必须以“Symbol.iterator”作为键,并返回一个新的迭代器实例。检查某类型是否存在默认迭代器属性的方法十分简单,具体示例如下:
const obj = {};
const arr = ["a", "b", "c"];
console.log(obj[Symbol.iterator]); // underfined
console.log(arr[Symbol.iterator]); // f values() { [native code] }
console.log(arr[Symbol.iterator]()); // ArrayIterator {}
在实际开发中,我们通常无需显式调用迭代器工厂函数。支持可迭代协议的数据类型会自动兼容接受可迭代对象的任何语言特性。例如,在使用循环、for-of语句、解构赋值或扩展操作符时,后台会自动调用提供的可迭代对象的迭代器工厂函数,从而创建迭代器。
迭代器协议
迭代器协议规定,迭代器是一种一次性使用的对象。调用迭代器工厂函数后,将返回一个next()方法。每次迭代成功时,都会调用该方法以获取下一个迭代的值;若不调用,则无法确定当前迭代的位置。
next()方法返回一个对象,包含两个属性:done和value。done表示是否可以继续调用next()方法获取下一个值,即是否“耗尽”,返回布尔值;value表示可迭代对象的下一个值。当done为true时,value的值为undefined;当done为false时,将继续调用下一次迭代。具体示例如下:
// 可迭代对象
let arr = ['foo', 'bar'];// 迭代器工厂函数
console.log(arr[Symbol.iterator]);// f values() { [native code] }
// 迭代器
let iter = arr[Symbol.iterator]();
console.log(iter); // ArrayIterator{}
// 执行迭代
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }
写在最后
通过迭代器协议,你可以实现自定义的迭代器,例如规定迭代器可以被迭代的次数,或者实现提前终止迭代的功能。
到此这篇关于ECMAScript中迭代器的文章就介绍到这了,更多相关ECMAScript迭代器内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!