关于javascript:详解js循环和ES6的iterator

40次阅读

共计 8664 个字符,预计需要花费 22 分钟才能阅读完成。

一、js 循环

1. 个别 for 循环

    var array = [1,2,3,4,5,6,7];  
    for (var i = 0; i < array.length; i++) {console.log(i,array[i]);  
    }  

2.for…in

let array = [2, 4, 6, 8, 9];
for(let index in array) {console.log(index+"---"+array[index]);  
    };  //0---2 1---4 ...

index 遍历的是数组或字符串的下标,或者对象的键名。

可枚举 的都能够用 for…in 遍历。

留神:fo…in 循环个别用于对象的遍历,然而这里有一个坑须要留神:

任何对象都继承了 Object 对象,或者其它对象,继承的类的属性是默认不可遍历的,for… in 循环遍历的时候会跳过,然而这个属性是能够更改为能够遍历的,那么就会造成遍历到不属于本身的属性。

举例来说,对象都继承了 toString 属性,然而 for…in 循环不会遍历到这个属性。

var obj = {};// toString 属性是存在的 obj.toString 
// toString() { [native code] }
for (var p in obj) {console.log(p);
} // 没有任何输入

// 这里 obj 有 length 属性,然而 length 不是可遍历的。

如果继承的属性是可遍历的,那么就会被 for…in 循环遍历到。但如果只想遍历本身的属性,应用 for…in 的时候,应该联合应用 hasOwnProperty 办法,在循环外部判断一下,某个属性是否为对象本身的属性。否则就能够产生遍历失真的状况。

var person = {name: '老张'};
for (var key in person) {if (person.hasOwnProperty(key)) {console.log(key);
      }
}// name

3.for…of

for…of 是 ES6 新增的,能够间接遍历该对象的值。

let v = [1,2,3]
for(let v of array) {console.log(v);  
}; // 1 2 3
let s = "hello";  
for(let c of s) {console.log(c); 
} // h e l l o

留神 :for…of 只能用于 iterator 对象,所以只能用于数组和字符串, 不能用于对象

4.forEach

array.forEach(calllback(currentValue, index, arr), thisValue)

  • callback

    为数组中每个元素执行的函数,该函数接管三个参数:

    • currentValue数组中正在解决的以后元素。
    • index 可选数组中正在解决的以后元素的索引。
    • array 可选forEach() 办法正在操作的数组。
  • thisArg 可选

    可选参数。当执行回调函数 callback 时,用作 this 的值。

  • 返回值 undefined
var array1 = [1,2,3,4,5];
array1.forEach((value,index,arr)=>{console.log(value+"--"+index);//1--0 2--1 ...  
    console.log(arr); // [1,2,3,4,5]
}); 

留神:除了抛出异样以外,没有方法停止或跳出 forEach() 循环。如果你须要停止或跳出循环,forEach() 办法不是该当应用的工具。

forEach() 为每个数组元素执行一次 callback 函数;与 map() 或者 reduce() 不同的是,它总是返回 undefined 值,并且不可链式调用。

5.map()循环:

map 办法将数组的所有成员顺次传入参数函数,而后把每一次的执行后果组成一个新数组返回。

留神:是返回一个新数组,而不会扭转原数组。

var numbers = [1, 2, 3];

numbers.map(function (n) {return n + 1;}); 
// [2, 3, 4] 

numbers // [1, 2, 3]

map 办法承受一个函数作为参数。该函数调用时,map 办法向它传入三个参数:以后成员、以后地位和数组自身。

[1, 2, 3].map(function(elem, index, arr) {return elem * index;}); 
// [0, 2, 6]

此外,map()循环还能够承受第二个参数,用来绑定回调函数外部的 this 变量,将回调函数外部的 this 对象,指向第二个参数,间接操作这个参数(个别是数组)。

var arr = ['a', 'b', 'c'];

[1, 2].map(function (e) {return this[e];
}, arr)
 // ['b', 'c']

总结:forEach 和 map 的差异是,forEach 返回值为空,map 则返回函数执行后果的数组。

6.filter()过滤循环

filter 办法用于过滤数组成员,满足条件的成员组成一个新数组返回。它的参数是一个函数,所有数组成员顺次执行该函数,返回后果为 true 的成员组成一个 新数组返回。该办法不会扭转原数组。

[1, 2, 3, 4, 5].filter(function (elem) {return (elem > 3); 
}) // [4, 5]

// 下面代码将大于 3 的数组成员,作为一个新数组返回。var arr = [0, 1, 'a', false]; 
arr.filter(Boolean) // [1, "a"]

filter 办法的参数函数也能够承受三个参数:以后成员,以后地位和整个数 组。

[1, 2, 3, 4, 5].filter(function (elem, index, arr) {return index % 2 === 0;}); // [1, 3, 5]

//filter 办法也能够承受第二个参数,用来绑定参数函数外部的 this 变量。var obj = {MAX: 3}; 
var myFilter = function (item) {if (item > this.MAX) return true; 
}; 
var arr = [2, 8, 3, 4, 1, 3, 2, 9]; 
arr.filter(myFilter, obj) // [8, 4, 9]

下面代码中,过滤器 myFilter 外部有 this 变量,它能够被 filter 办法的第二个参数 obj 绑定,返回大于 3 的成员。

7.some(),every()循环遍历

这两个办法相似“断言”(assert),返回一个布尔值,示意判断数组成员是否合乎某种条件。

它们承受一个函数作为参数,所有数组成员顺次执行该函数。该函数承受三个参数:以后成员、以后地位和整个数组,而后返回一个布尔值。

some 办法是只有一个成员的返回值是 true,则整个 some 办法的返回值就是 true,否则返回 false。

var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {return elem >= 3;});
// true

而 every 办法则相同,所有成员的返回值都是 true,整个 every 办法才返回 true,否则返回 false。两相比拟,some()只有有一个是 true,便返回 true;而 every()只有有一个是 false,便返回 false.

var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {return elem >= 3;});
// false

这两个办法在理论开发中,大有可用之处。比方在断定用户是否勾选了不可操作的数据,或者是否勾选了一条能够操作的数据能够应用这两个办法遍历循环数组。

8.reduce(),reduceRight()

reduce 办法和 reduceRight 办法顺次解决数组的每个成员,最终累计为一个值。它们的差异是,reduce 是从左到右解决(从第一个成员到最初一个成员),reduceRight 则是从右到左(从最初一个成员到第一个成员),其余齐全一样。

[1, 2, 3, 4, 5].reduce(function (a, b) {console.log(a, b);
  return a + b;
})
// 1 2
// 3 3
// 6 4
// 10 5
// 最初后果:15

reduce 办法和 reduceRight 办法的第一个参数都是一个函数。该函数承受以下四个参数。

累积变量,默认为数组的第一个成员
以后变量,默认为数组的第二个成员
以后地位(从 0 开始)
原数组
这四个参数之中,只有前两个是必须的,后两个则是可选的。

如果要对累积变量指定初值,能够把它放在 reduce 办法和 reduceRight 办法的第二个参数。

[1, 2, 3, 4, 5].reduce(function (a, b) {return a + b;}, 10);
// 25

下面的第二个参数相当于设定了 默认值,解决空数组时尤其有用,可防止一些空指针异样。

因为这两个办法会遍历数组,所以实际上还能够用来做一些遍历相干的操作。

作用:找出字符长度最长的数组成员。

function findLongest(entries) {return entries.reduce(function (longest, entry) {return entry.length > longest.length ? entry : longest;}, '');
}

findLongest(['aaa', 'bb', 'c']) // "aaa"

下面代码中,reduce 的参数函数会将字符长度较长的那个数组成员,作为累积值。这导致遍历所有成员之后,累积值就是字符长度最长的那个成员。

9.Object,keys 遍历对象的属性

object.keys 办法的参数是一个对象,返回一个数组。该数组的成员都是该对象本身的(而不是继承的)所有属性名,且只返回可枚举(enumerable)的属性。

var obj = {
  p1: 123,
  p2: 456
};

Object.keys(obj) // ["p1", "p2"]

10.Object.getOwnPropertyNames()遍历对象的属性

Object.getOwnPropertyNames 办法与 Object.keys 相似,也是承受一个对象作为参数,返回一个数组,蕴含了该对象本身的所有属性名。但它能返回不可枚举的属性。

var a = ['Hello', 'World'];

Object.keys(a) // ["0", "1"]
Object.getOwnPropertyNames(a) // ["0", "1", "length"]

下面代码中,数组的 length 属性是不可枚举的属性,所以只呈现在 Object.getOwnPropertyNames 办法的返回后果中。

因为 JavaScript 没有提供计算对象属性个数的办法,所以能够用这两个办法代替。

var obj = {
  p1: 123,
  p2: 456
};
Object.keys(obj).length // 2
Object.getOwnPropertyNames(obj).length // 2

11. 总结

以上循环特色(雷同与不同):

一:map(),foreach,filter 循环的共同之处:

  1. foreach,map,filter 循环中途是无奈进行的,总是会将所有成员遍历完。
  2. 他们都能够承受第二个参数,用来绑定回调函数外部的 this 变量,将回调函数外部的 this 对象,指向第二个参数,间接操作这个参数(个别是数组)。

二:map()循环和 forEach 循环的不同:

​ forEach 循环没有返回值;map,filter 循环有返回值。

三:map 循环和 filter()循环都会跳过空位,for 和 while 不会

var f = function (n) {return 'a'}; 

[1, undefined, 2].map(f) // ["a", "a", "a"] 
[1, null, 2].map(f) // ["a", "a", "a"]
[1, , 2].map(f) // ["a", , "a"] 

下面代码中,map 办法不会跳过 undefined 和 null,然而会跳过空位。forEach 办法也会跳过数组的空位,这里就不举例了。

四:some()和 every():

  • some()只有有一个是 true,便返回 true;而 every()只有有一个是 false,便返回 false.

五:reduce(),reduceRight():

  • reduce 是从左到右解决(从第一个成员到最初一个成员),reduceRight 则是从右到左(从最初一个成员到第一个成员)。

六:Object 对象的两个遍历 Object.keys 与 Object.getOwnPropertyNames:

​ 他们都是遍历对象的属性,也是承受一个对象作为参数,返回一个数组,蕴含了该对象本身的所有属性名。但 Object.keys 不能返回不可枚举的属性;Object.getOwnPropertyNames 能返回不可枚举的属性。

七:for…in 和 for…of

(见阮一峰)

以数组为例,JavaScript 提供多种遍历语法。最原始的写法就是 for 循环。

for (var index = 0; index < myArray.length; index++) {console.log(myArray[index]);
}

这种写法比拟麻烦,因而数组提供内置的 forEach 办法。

myArray.forEach(function (value) {console.log(value);
});

这种写法的问题在于,无奈中途跳出 forEach 循环,break命令或 return 命令都不能见效。

for...in循环能够遍历数组的键名。

for (var index in myArray) {console.log(myArray[index]);
}

for...in循环有几个毛病。

  • 数组的键名是数字,然而 for...in 循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in循环不仅遍历数字键名,还会遍历手动增加的其余键,甚至包含原型链上的键。
  • 某些状况下,for...in循环会以任意程序遍历键名。

总之,for...in循环次要是为遍历对象而设计的,不适用于遍历数组。

for...of循环相比下面几种做法,有一些显著的长处。

for (let value of myArray) {console.log(value);
}
  • 有着同 for...in 一样的简洁语法,然而没有 for...in 那些毛病。
  • 不同于 forEach 办法,它能够与 breakcontinuereturn配合应用。
  • 提供了遍历所有数据结构的对立操作接口。

上面是一个应用 break 语句,跳出 for...of 循环的例子。

for (var n of fibonacci) {if (n > 1000)
    break;
  console.log(n);
}

下面的例子,会输入斐波纳契数列小于等于 1000 的项。如果以后项大于 1000,就会应用 break 语句跳出 for...of 循环。

二、iterator 迭代器

2.1 应用 iterator 遍历

  1. 并不是所有相似数组的对象都具备 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);
    }

2.2 iterator 遍历拓展

有些数据结构是在现有数据结构的根底上,计算生成的。比方,ES6 的数组、Set、Map 都部署了以下三个办法,调用后都返回遍历器对象。

  • entries() 返回一个遍历器对象,用来遍历 [键名, 键值] 组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值雷同 。Map 构造的 Iterator 接口,默认就是调用entries 办法。
  • keys() 返回一个遍历器对象,用来遍历所有的键名。
  • values() 返回一个遍历器对象,用来遍历所有的键值。

这三个办法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。

let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']

留神:

  1. 数组、Set、Map 的三个办法返回的就是迭代器对象 Iterator,SetIterator {"Gecko", "Trident", "Webkit"}

    这和对象的 Object.keys()等办法不一样!!对象这三个办法,返回就是一个数组!

    例子:

    let es6 = {
      edition: 6,
      committee: "TC39",
      standard: "ECMA-262"
    };
    let ite =  Object.entries(es6)[Symbol.iterator]()
    // Array Iterator {}
  2. for...of循环时,数组和 Set 默认应用 keys(),而 Map 默认应用entries()
  3. 间接用 for...of 循环一个迭代器对象也是一样的成果!

    eg.

    let arr=[3,6,9]
    for(let i of arr){console.log(i)
     }
    
    for(let i of arr[Symbol.iterator]()){console.log(i)}
    // 两个返回雷同 3 6 9

2.3 调用 Iterator 接口的场合

除了 for...of 循环,还有几个别的场合,默认调用 Iterator 接口(即 Symbol.iterator 办法)。

(1)解构赋值

对数组和 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']

下面代码的扩大运算符外部就调用 Iterator 接口。

实际上,这提供了一种简便机制,能够将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只有某个数据结构部署了 Iterator 接口,就能够对它应用扩大运算符,将其转为数组。

let arr = [...iterable];

(3)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)其余场合

因为数组的遍历会调用遍历器接口,所以任何承受数组作为参数的场合,其实都调用了遍历器接口。上面是一些例子。

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比方new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

2.4 与其余遍历语法的比拟

以数组为例,JavaScript 提供多种遍历语法。最原始的写法就是 for 循环。

for (var index = 0; index < myArray.length; index++) {console.log(myArray[index]);
}

这种写法比拟麻烦,因而数组提供内置的 forEach 办法。

myArray.forEach(function (value) {console.log(value);
});

这种写法的问题在于,无奈中途跳出 forEach 循环,break命令或 return 命令都不能见效。

for...in循环能够遍历数组的键名。

for (var index in myArray) {console.log(myArray[index]);
}

for...in循环有几个毛病。

  • 数组的键名是数字,然而 for...in 循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in循环不仅遍历数字键名,还会遍历手动增加的其余键,甚至包含原型链上的键。
  • 某些状况下,for...in循环会以任意程序遍历键名。

总之,for...in循环次要是为遍历对象而设计的,不适用于遍历数组。

for...of循环相比下面几种做法,有一些显著的长处。

for (let value of myArray) {console.log(value);
}
  • 有着同 for...in 一样的简洁语法,然而没有 for...in 那些毛病。
  • 不同于 forEach 办法,它能够与 breakcontinuereturn配合应用。
  • 提供了遍历所有数据结构的对立操作接口。

正文完
 0