2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > js ES6 Iterator 遍历器与 for for...of for await...of for...in 和 forEach 循环语句

js ES6 Iterator 遍历器与 for for...of for await...of for...in 和 forEach 循环语句

时间:2019-05-19 02:45:48

相关推荐

js ES6 Iterator 遍历器与 for for...of for await...of for...in 和 forEach 循环语句

目录

一、Iterator

1、遍历器(Iterator)

2、Iterator 的方法

(1)、next() 方法

(2)、return() 方法

(3)、throw() 方法

3、Iterator 接口的部署(★★★★★)

(1)、默认部署

(2)、原生具备 Iterator 接口的数据结构

4、调用 Iterator 接口的场合

(1)、Iterator 接口与解构赋值

(2)、Iterator 接口与扩展运算符

(3)、Iterator 接口与 yield*

(4)、Iterator 接口与 Generator 函数

二、for...of 循环

1、for...of 循环的概况

2、for...of循环的应用

(1)、遍历可迭代的数据结构

(2)、遍历不可迭代的数据结构

(3)、遍历类数组对象

三、for await...of 循环

1、for await...of 循环的使用

2、for await...of 循环的错误捕获

3、for await...of 循环与异步 Generator 函数

四、for、for...of、for...in 和 forEach 循环的区别

五、for 和 forEach 循环哪个更快?为什么?

六、遍历对象与数组

1、遍历对象

(1)、for … in

(2)、Object.keys()

(3)、Reflect.ownKeys()

(4)、Object.getOwnPropertyNames()

2、遍历数组

(1)、for

(2)、forEach

(3)、for ... in

(4)、for ... of

七、属性的可枚举性及其迭代方法

一、Iterator

1、遍历器(Iterator)

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。

Iterator 是Symbol 基本数据类型的一个属性,是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:

一是,为各种数据结构,提供一个统一的、简便的访问接口;二是,使得数据结构的成员能够按某种次序排列;三是, ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of使用。

Iterator 的遍历过程是这样的:

通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束。当 done 为 true 时则遍历结束。

只要有遍历器接口,数据结构就可以用for...of 或 while 循环遍历。

2、Iterator 的方法

(1)、next() 方法

每一次调用next方法,都会返回一个包含value和done两个属性对象的当前成员的信息:

value属性是当前成员的值。done属性是一个布尔值,表示遍历是否结束。

如果你自己写遍历器对象生成函数,那么next方法是必须部署的。

var it = makeIterator(['a', 'b']);it.next() // { value: "a", done: false }it.next() // { value: "b", done: false }it.next() // { value: undefined, done: true }function makeIterator(array) {var nextIndex = 0;return {next: function() {return nextIndex < array.length ?{value: array[nextIndex++], done: false} :{value: undefined, done: true};}};}

对于遍历器对象来说,done: false和value: undefined属性都是可以省略的,因此上面的makeIterator函数可以简写成下面的形式。

function makeIterator(array) {var nextIndex = 0;return {next: function() {return nextIndex < array.length ?{value: array[nextIndex++]} :{done: true};}};}

(2)、return() 方法

return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

function readLinesSync(file) {return {[Symbol.iterator]() {return {next() {return { done: false };},return() {file.close();return { done: true };}};},};}

上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的两种情况,都会触发执行return方法。

// 情况一for (let line of readLinesSync(fileName)) {console.log(line);break;}// 情况二for (let line of readLinesSync(fileName)) {console.log(line);throw new Error();}

上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二会在执行return方法关闭文件之后,再抛出错误。

注意,return方法必须返回一个对象,这是 Generator 规格决定的。

(3)、throw() 方法

throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅 Generator 函数一章。

3、Iterator 接口的部署(★★★★★)

(1)、默认部署

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名 Symbol.iterator,它是一个表达式,返回 Symbol 对象的 iterator 属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。

const obj = {[Symbol.iterator] : function () {return {next: function () {return {value: 1,done: true};}};}};

上面代码中,对象 obj 是可遍历的(iterable),因为具有 Symbol.iterator 属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有 next 方法。每次调用 next 方法,都会返回一个代表当前成员的信息对象,具有 value 和 done 两个属性。

(2)、原生具备 Iterator 接口的数据结构

原生具备 Iterator 接口的数据结构有以下 7 个:

ArrayMapSetStringTypedArray函数中的 arguments 对象NodeList 对象

①、数组的Symbol.iterator属性

let arr = ['a', 'b', 'c'];let iter = arr[Symbol.iterator]();iter.next() // { value: 'a', done: false }iter.next() // { value: 'b', done: false }iter.next() // { value: 'c', done: false }iter.next() // { value: undefined, done: true }

上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象。

②、对象默认没部署 Iterator 接口,为其部署

let obj = {0: 'a',1: 'b',2: 'c',}console.log([...obj]);// Uncaught TypeError: obj is not iterableconsole.log({...obj});// {0: "a", 1: "b", 2: "c"}for(let p of obj){console.log(p);//TypeError: obj is not iterable}// Uncaught TypeError: obj is not iterable

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。

一个对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在 Symbol.iterator 的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

--> 在对象的属性上部署 Iterator 接口

class RangeIterator {constructor(start, stop) {this.value = start;this.stop = stop;}[Symbol.iterator]() { return this; }next() {var value = this.value;if (value < this.stop) {this.value++;return {done: false, value: value};}return {done: true, value: undefined};}}function range(start, stop) {return new RangeIterator(start, stop);}for (var value of range(0, 3)) {console.log(value); // 0, 1, 2}

上面代码是一个类部署 Iterator 接口的写法。Symbol.iterator 属性对应一个函数,执行后返回当前对象的遍历器对象。

也可以这样为对象添加 Iterator 接口的例子。

let obj = {data: [ 'hello', 'world' ],[Symbol.iterator]() {const self = this;let index = 0;return {next() {if (index < self.data.length) {return {value: self.data[index++],done: false};} else {return { value: undefined, done: true };}}};}};

--> 在原型链上为对象部署 Iterator 接口

function Obj(value) {this.value = value;this.next = null;}Obj.prototype[Symbol.iterator] = function() {var iterator = { next: next };var current = this;function next() {if (current) {var value = current.value;current = current.next;return { done: false, value: value };} else {return { done: true };}}return iterator;}var one = new Obj(1);var two = new Obj(2);var three = new Obj(3);one.next = two;two.next = three;for (var i of one){console.log(i); // 1, 2, 3}

上面代码首先在构造函数的原型链上部署 Symbol.iterator 方法,调用该方法会返回遍历器对象 iterator,调用该对象的 next 方法,在返回一个值的同时,自动将内部指针移到下一个实例。

③、类数组对象添加Iterator 接口的简便方法

对于类数组对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是 Symbol.iterator 方法直接引用数组的 Iterator 接口。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];// 或者NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];[...document.querySelectorAll('div')] // 可以执行了

下面是另一个类数组对象调用数组的 Symbol.iterator 方法的例子。

let iterable = {0: 'a',1: 'b',2: 'c',length: 3,[Symbol.iterator]: Array.prototype[Symbol.iterator]};for (let item of iterable) {console.log(item); // 'a', 'b', 'c'}

注意,普通对象部署数组的 Symbol.iterator 方法,并无效果。

let iterable = {a: 'a',b: 'b',c: 'c',length: 3,[Symbol.iterator]: Array.prototype[Symbol.iterator]};for (let item of iterable) {console.log(item); // undefined, undefined, undefined}

4、调用 Iterator 接口的场合

(1)、Iterator 接口与解构赋值

对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法。

let set = new Set().add('a').add('b').add('c');let [x,y] = set;// x='a'; y='b'let [first, ...rest] = set;// first='a'; rest=['b','c'];

(2)、Iterator 接口与扩展运算符

在 数组 或 函数参数 中使用 展开语法(...)时,该语法只能用于 可迭代对象。

// 例一var str = 'hello';[...str] // ['h','e','l','l','o']// 例二let arr = ['b', 'c'];['a', ...arr, 'd']// ['a', 'b', 'c', 'd']

(3)、Iterator 接口与 yield*

yield* 后面跟的是一个可遍历的结构,它会默认调用该结构的遍历器接口。

let generator = function* () {yield 1;yield* [2,3,4];yield 5;};var iterator = generator();iterator.next() // { value: 1, done: false }iterator.next() // { value: 2, done: false }iterator.next() // { value: 3, done: false }iterator.next() // { value: 4, done: false }iterator.next() // { value: 5, done: false }iterator.next() // { value: undefined, done: true }

(4)、Iterator 接口与 Generator 函数

Symbol.iterator 方法的最简单实现,是使用 Generator 函数。

Generator 函数默认调用Symbol.iterator 接口。

let myIterable = {[Symbol.iterator]: function* () {yield 1;yield 2;yield 3;}}[...myIterable] // [1, 2, 3]// 或者采用下面的简洁写法let obj = {* [Symbol.iterator]() {yield 'hello';yield 'world';}};for (let x of obj) {console.log(x);}// "hello"// "world"

上面代码中,Symbol.iterator方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。

二、for...of 循环

1、for...of 循环的概况

for...of 循环是 ES6 新引入的循环,作为遍历所有数据结构的统一的方法。

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

2、for...of循环的应用

for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串。

(1)、遍历可迭代的数据结构

①、遍历 Array

for...of 只能遍历部署了 iterator 接口,数组原生具备 iterator 接口(即默认部署了Symbol.iterator属性),可以直接使用for...of循环。

for...of循环读取数组的键值。下面是与for...in与forEach的对比。

var arr = ['a', 'b', 'c', 'd'];arr.forEach(function (element, index) {console.log(element); // a b c dconsole.log(index); // 0 1 2 3});for (let a in arr) {console.log(a); // 0 1 2 3}for (let a of arr) {console.log(a); // a b c d}

for...in循环,只能获得对象的键名,不能直接获取键值,而 for...of 可以。

②、遍历 Map 和Set

Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用 for...of 循环。

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);for (var e of engines) {console.log(e);}// Gecko// Trident// Webkitvar es6 = new Map();es6.set("edition", 6).set("committee", "TC39").set("standard", "ECMA-262");for (var [name, value] of es6) {console.log(name + ": " + value);}// edition: 6// committee: TC39// standard: ECMA-262

(2)、遍历不可迭代的数据结构

for...of 只能遍历部署了 iterator 接口的数据结构,对于没有部署 iterator 接口的数据结构,会报错:

let obj = {name:"Mary", age:18};for(var item of obj){console.log(item);}// TypeError: obj is not iterable

可以使用以下方法代替:

let obj = {name:"Mary", age:18};Object.keys(obj).forEach(key=>{console.log(key, obj[key]);})// name Mary// age 18// 或者这样写for(var key in obj){console.log(key, obj[key]);}// name Mary// age 18

(3)、遍历类数组对象

类数组对象:有length属性的对象。有的对象默认自带 length 对象,比如一个字符串,有的对象是自定义的 length 属性。

①、遍历包含 Iterator 接口的类数组对象

// 字符串let str = "hello";for (let s of str) {console.log(s); }// h// e// l// l// o// DOM NodeList对象let paras = document.querySelectorAll("p");for (let p of paras) {console.log(p);}// <p></p>// <p></p>// <p></p>// arguments对象function printArgs() {for (let x of arguments) {console.log(x);}}printArgs('a', 'b');// 'a'// 'b'

②、遍历不包含 Iterator 接口的类数组对象

并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。

let arrayLike = { length: 2, 0: 'a', 1: 'b' };// 报错for (let x of arrayLike) {console.log(x);}// 正确for (let x of Array.from(arrayLike)) {console.log(x);}

三、for await...of 循环

for...of 循环,用于遍历同步的 Iterator 接口

for await...of 循环,虽然可以用于同步遍历器,但是主要用于遍历异步的 Iterator 接口

1、for await...of 循环的使用

async function f() {for await (const x of createAsyncIterable(['a', 'b'])) {console.log(x);}}// a// b

上面代码中,createAsyncIterable()返回一个拥有异步遍历器接口的对象,for...of循环自动调用这个对象的异步遍历器的next方法,会得到一个 Promise 对象。await用来处理这个 Promise 对象,一旦resolve,就把得到的值(x)传入for...of的循环体。

2、for await...of 循环的错误捕获

如果 next 方法返回的 Promise 对象被 reject,for await...of 就会报错。

【反例】此时用 try...catch 是捕获不到的:

async function fn () {try {for await (const v of createRejectingIterable()) {console.log(v);}} catch (e) {console.error('111', e);// 不会执行的}}

【正例】像下面这样写就可以捕获到错误啦:

async function fn () {for await (let v of createRejectingIterable()) {console.log(v);}}fn().catch(e => console.log('111', e));

3、for await...of 循环与异步 Generator 函数

for await...of 循环可以与异步 Generator 函数结合起来使用。

async function* prefixLines(asyncIterable) {for await (const line of asyncIterable) {yield '> ' + line;}}

异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。

// 同步 Generator 函数function* map(iterable, func) {const iter = iterable[Symbol.iterator]();while (true) {const {value, done} = iter.next();if (done) break;yield func(value);}}// 异步 Generator 函数async function* map(iterable, func) {const iter = iterable[Symbol.asyncIterator]();while (true) {const {value, done} = await iter.next();if (done) break;yield func(value);}}

上面代码中,map是一个 Generator 函数,第一个参数是可遍历对象iterable,第二个参数是一个回调函数func。map的作用是将iterable每一步返回的值,使用func进行处理。上面有两个版本的map,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。

异步 Generator 函数内部,能够同时使用await和yield命令。可以这样理解,await命令用于将外部操作产生的值输入函数内部,yield命令用于将函数内部的值输出。例如:

async function* readLines(path) {let file = await fileOpen(path);try {while (!file.EOF) {yield await file.readLine();}} finally {await file.close();}}(async function () {for await (const line of readLines(filePath)) {console.log(line);}})()

四、for、for...of、for...in 和 forEach 循环的区别

一般的,使用 for...in 遍历对象,使用 for...of 遍历数组。

for:循环代码块一定的次数。

forEach:专门针对数组的循环遍历。

for...in:遍历对象(非 Symbol 类型)的“可枚举”属性(键)。不建议与数组一起使用,数组可以用 Array.prototype.forEach() 和 for ... of 遍历。

for...of:遍历可迭代对象(带有Iterator 遍历器的对象)的属性值(值)

// 遍历字符串let str = "qwertyui";for (let item in str) {console.log(item);// 0~7——键}for (let key of str) {console.log(key);// q~i——值}// 遍历数组let arr = [1,2,3];for (let item in arr) {console.log(item);// 0~2——键}for (let key of arr) {console.log(key);// 1~3——值}// 遍历对象let obj = {name: "Mary",age: 18}for (let item in obj) {console.log(item);// name/age——键}for (let key of obj) {console.log(key);// Uncaught TypeError: obj is not iterable}

注:普通对象不具有Iterator。

下面示例展示了 for...of 和 for...in 循环遍历 Array 时的区别:

Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {};let iterable = [3, 5, 7];iterable.foo = 'hello';

for (let i in iterable) {console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom" }

此循环仅以原始插入顺序记录iterable 对象的可枚举属性。它不记录数组元素3, 5, 7 或hello,因为这些不是枚举属性。但是它记录了数组索引以及arrCustom和objCustom。如果你不知道为什么这些属性被迭代,array iteration and for...in中有更多解释。

for (let i in iterable) {if (iterable.hasOwnProperty(i)) {console.log(i); // logs 0, 1, 2, "foo"}}

这个循环类似于第一个,但是它使用hasOwnProperty() 来检查,如果找到的枚举属性是对象自己的(不是继承的)。如果是,该属性被记录。记录的属性是0, 1, 2和foo,因为它们是自身的属性(不是继承的)。属性arrCustom和objCustom不会被记录,因为它们是继承的。

for (let i of iterable) {console.log(i); // logs 3, 5, 7 }

该循环迭代并记录iterable作为可迭代对象定义的迭代值,这些是数组元素 3, 5, 7,而不是任何对象的属性。

五、for 和 forEach 循环哪个更快?为什么?

在循环大量数据时,for 循环语句更快。

因为:forEach 本身是一个函数,它的调用需要判断上下文,这就稍微比 for 循环语句慢了那么一点点,当处理大量数据时,这个微小的差距就会变大,凸显出来。所以,在循环大量数据时,for 循环语句更快。

六、遍历对象与数组

1、遍历对象

(1)、for … in

遍历 对象(非 Symbol 类型)的“可枚举”属性(键)。不建议用来遍历数组,遍历数组可以用 Array.prototype.forEach() 和 for ... of 遍历。

(2)、Object.keys()

Object.keys() 方法会返回一个由给定对象的所有自身可枚举属性(不包括 Symbol 值作为名称的属性)组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。

(3)、Reflect.ownKeys()

静态 Reflect.ownKeys() 方法返回一个由给定对象的所有自身属性名(包括 不可枚举属性 和 Symbol 值作为名称的属性)的数组。

它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

(4)、Object.getOwnPropertyNames()

Object.getOwnPropertyNames() 方法返回一个由指定对象的所有自身属性的属性名(包括 不可枚举属性 但不包括 Symbol 值作为名称的属性)组成的数组。

(5)、Object.values()

Object.values() 方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用 for...in 循环的顺序相同(区别在于 for-in 循环枚举原型链中的属性)。

需要注意的是,Object.values() 方法输出的值的顺序默认有时候会改变:

var an_obj = { 100: 'a', 2: 'b', 7: 'c' };console.log(Object.values(an_obj)); // ['b', 'c', 'a']

2、遍历数组

(1)、for

(2)、forEach

(3)、for ... in

(4)、for ... of

(1)~(4)详见:本文的 “四、for、for...of、for...in 和 forEach 循环的区别”。

七、属性的可枚举性及其迭代方法

请戳这里:属性的可枚举性及其迭代方法_weixin79893765432...的博客-CSDN博客

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。