1

我们经常会用到循环,截止到ES6,我们总共有4种语法用于循环,但是它们各自的功能和使用场景却有很大区别。接下来我们讲对这4种语法一一讲解。首先我们来看一下有哪4种:

1: for(let i = 0; i < number; i ++){...}
2: for(let key in object){...}
3: for(let value of iterable){...}
4: forEach(){...}

接下来我们一个一个地看:
1: for()
语法:

for(let i = 0; i < number, i ++){...};

for最简单,这里不作过多讲解。

2: for...in
语法:

for(let key in object){...}

for...in以任意顺序来遍历一个对象除了Symbole类型外的可枚举属性。
这简单的一句话传达了太多信息,有如下几点:

1: 它的遍历是无序的
2: 它遍历的是对象的属性名,而不是属性对应的值
3: 它只遍历可枚举属性
4: 在可枚举属性里面,不遍历Symbol类型

还有一点需要注意的是,

5: for...in不仅会遍历自己的属性,也会遍历继承的属性。

关于以上几个小点我们先来做一下知识的延伸,以便能更好地理解:

什么是无序遍历?

无序遍历是不能保证输出的数据顺序和放入的数据顺序是一样的,所以有可能一样,也有可能不一样。

什么样的属性是可枚举的?
首先可枚举属性是指那些内部 “可枚举(enumerable)” 标志设置为 true 的属性。
数据的枚举性遵循一下2条规律:

1: 直接通过赋值和属性初始化的属性,默认enumerable == true,是可枚举的
2: 通过Object.defineProperty定义的属性,默认enumerable == false,则不可枚举;如果想要可枚举,需要手动改enumerable:true

for...in的代码例子:

let obj = {name: 'nana', age: 20};
for(let key in obj){
    console.log(`${key}`); // name, age
}

在for...in中对对象属性进行修改会怎样?
在MDN的页面上明确指出,我们在for...in的循环中,最好不要对属性进行添加,删除(通过delete),修改(通过Object.defineProperty)操作,因为在循环中进行这些操作都无法保证得到一个确定的结果。

不要把for...in用于Array
在可迭代对象一文了解到,数组可以看作是以下标为key的对象;我们现在又知道了for...in用于遍历对象的key,那for...in是否可以用于遍历Array呢?答案是:不要这么做。虽然你这么做了并不会又语法错误,但是有一点很重要:下标的有序性对于Array来说是核心,但是我们前面说到for...in的遍历是无序的。所以,不要拿for...in用于遍历Array。

3: for...of
for...of用来遍历可迭代对象的元素的,而不是属性。
我们可以去复习一下什么是可迭代对象,简单的说来有以下几种:

1: ES6内建的可迭代对象:Array,TypedArray,Map, Set,String
2: Array-like object:arguments, nodeList
3: 用户自己创建的可迭代对象(用生成器函数创建的可迭代对象)

特别注意的一点是:

4: weakSet和weakMap都是不可迭代对象,所以不能使用for...of

我们现在来看一下for...of用于一个Array的例子:

let colors = ['red', 'green', 'yellow'];
for(let color of colors){
    console.log(color); // 'red' 'green' 'yellow'
}

for...of用于自创建可迭代对象的例子:

function* numbers() {
    yield 1;
    yield 2;
    yield 3;
}
for(let color of numbers()){
   console.log(color); // 1 2 3
}

4: forEach()
我们先来看一下forEach( )的解释:
The forEach() method executes a provided function once for each array element.
我们来对上面的句子说明以下:

1: forEach()只能用于Array,不能用于其他的可迭代对象
2: forEach()接收一个function作为参数
3: 这个function会为数组里面的每一个元素都执行一次

看一下的语法:

forEach(function (value, index, array) {}, thisArg);

value: 当前轮询的元素的值
index: 当前轮询的元素的下标
array: 被轮询的array本身
thisArg: 在这个回调函数中的this值
关于forEach()方法,有几点需要注意:
1: forEach()总是返回undefined,这于map()和reduce()不同
我们可以看下面一个对比:

let numbers = [1, 2, 3];
let newNumbers1 = numbers.forEach(function (value, index, numbers) {
    return value + 1;
});
console.log(newNumbers1); //undefined

let newNumbers2 = numbers.map(function (value, index, numbers) {
    return value + 1;
})
console.log(newNumbers2); //[2, 3, 4]

2: forEach()要遍历的范围在对第一个元素遍历之前便已经确定
意思就是说:一旦Array对第一个元素调用forEach()里面的回调函数,这之后添加的新元素,并不会在此轮被遍历到。虽然数组已经发生了变化。可以看下面的例子:

let numbers = [1, 2, 3];
numbers.forEach(function (value, index, numbers) {
    console.log(value); // 1, 2, 3
    numbers.push(4);
});
console.log(numbers);//[1, 2, 3, 4, 4, 4]

以上的代码,我们在forEach()里面添加元素,但是总共也还是只遍历了1,2,3这三个元素。虽然在轮询的过程中,我们的numbers就已经发生了变化。
但是,如果在轮询中修改了Array,某些元素可能会被轮询跳过:

let numbers = [1, 2, 3, 4];
numbers.forEach((value, index, numbers)=>{
   console.log(value);
   if(value === 2){
       numbers.shift(); //1 2 4
   }
});
console.log(numbers);// [2, 3, 4]

3: 对于没有初始化的元素,forEach()不会对其调用callback函数

let count = 0;
let numbers = [1, 2, , 3];
numbers.forEach(function (value, index, numbers) {
    console.log(value); //1 2 3
    count ++;
});
console.log(count); //3
console.log(numbers.length); //4

numbers有四个元素,所以它的lenght为4,但是其中一个元素为空,我们看到count只加了3次,因为对于空元素,callback并不会被调用。
4: 在forEach()里面不能跳出轮询
而可以跳出轮询的遍历有以下几个:

1: for循环
2: for...in/for...of
3: Array.prototype.every()
4: Array.prototype.some()
5: Array.prototype.find()
6: Array.prototype.findIndex()

最后来总结以下for...in, for...of, forEach( )的区别:

1: for...in用于对象,遍历对象的key,不可用于Array
2: for...of用于可迭代对象,遍历的是元素的value,(常用于Array,Map等)
3: forEach()用于Array,不可直接用于其他类型数据

nanaistaken
586 声望43 粉丝