2023年8月

for 循环


for是最基础的循环,基本上每种编程语言都能见到他的身影,最为常见的使用方法为:

let a=[1,2,3];
for ( let i=0;i<a.length;i++ ) {
   console.log(a[i]);
}
//输出 0 1 2

不少小伙伴可能会看到有的人建议将数组的长度用一个变量缓存起来,以便提高循环效率,例如上述例子改为:

let a=[1,2,3];
let length=a.length;
for ( let i=0;i<length;i++ ) {
   console.log(a[i]);
}
//输出 0 1 2

但这样做其实并不会给你循环带来性能提升(无论循环次数多少),所以除非有其他需求,否则没必要把数组长度单独缓存起来.

对于遍历对象(关联数组),可以如下操作:

let obj={"a":1,"b":2};
let keys=Object.keys(obj)//获取对象的key组成的数组
for ( let i=0;i<keys.length;i++ ) {
  console.log(keys[i],obj[keys[i]]);
}
//输出:
// a 1
// b 2

这里let keys=Object.keys(obj) 与上述的把数组长度缓存起来有所不同,如果放到for循环中的话,Object.keys每次循环都会去计算、生成索引数组,对循环效率产生极大影响.

除使用Object.keys()外,若还有以下方法可选:

  • Object.values(),与Object.keys()类似,但返回的不是索引,而是值
  • Object.getOwnPropertyNames()返回对象自身属性名(包括不可枚举的属性)组成的数组.
  • Object.getOwnPropertySymbols()返回对象自身的Symbol属性组成的数组,不含普通属性
  • Reflect.ownKeys() 返回对象自身所有属性名组成的数组,包括不可枚举的属性和Symbol属性
  • Object.entries() 返回对象自身所有属性键值组成的数组,不含原型.

     let obj={a:1,b:2,c:3}
     console.log(Object.entries(obj))
     输出:
     [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
    
方法基本属性原型链不可枚举Symbol
Object.keys()
Object.values()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.entries()是(k&v)
Reflect.ownKeys()

以上方法都是返回一个数组,然后配合for循环或者其他循环语句进行遍历.

for的优点是非常灵活,可以实现一些特殊循环,其基本结构为for(a;b;c){},其中a、c可以为表达式、语句、函数、值、对象等等都行, 第二个位置需要是一个表达式或者值,如果等于false则停止循环(null、undefined都不等于false哦).其实a、b、c甚至都可以省略(但分号不能省),以下写法在js中是合法的,但因为第二个位置永不等于false,所以会形成一个死循环.

  for(;;;){}

这里主要想阐述for循环非常灵活,但有似乎点跑题了.

let i=0
let flag=true
let f=()=>{
  if(i>10){
    flag=false
  }
  return i++
}
for ([1,2,3];flag;3) {//其中[1,2,3] 和 3 无意义
  console.log(f());
}

另外也可使用while循环进行遍历,但一般不常用于数组或对象上,所以这里就不展开介绍了

for in


for in 是es5加入的,可以用于遍历数组、对象, 需要注意的是,for in会遍历数组所有的可枚举属性,包括原型属性,不包括Symbol.

Object.prototype.title = 'for in';
 let obj={a:1,b:2,c:3}
console.log(obj); //输出 { a: 1, b: 2, c: 3 }
for(let index in obj){
  console.log(index);
}

输出:
a
b
c
title

为避免输出原型属性,可配合hasOwnProperty()方法来进行判断

Object.prototype.title = 'for in';
let obj={a:1,b:2,c:3}
console.log(obj); //输出 { a: 1, b: 2, c: 3 }
for(let index in obj){
  if (obj.hasOwnProperty(index)) {
    console.log(index);
  }
}

输出:
a
b
c

for of 虽然也可以遍历数组,但是一般不推荐,除上面说到的会遍历出原型属性外,还存在两个问题:

  • 遍历数组获取到的key为sting类型,而非number,不注意的话,在使用时可能导致出错.
  • 遍历数组时,存在不按索引顺序遍历的风险

for of


for of 是es6标准,与 for in 相比:
1.遍历出来的是值而非索引.但可以通过遍历arr.keys()的方式获取索引.
2.不会遍历原型属性
3.不支持遍历普通对象,但支持map、set、arguments甚至字符串等存在iterator接口的对象

Array.prototype.title = 'for of';
let arr=[1,2,3] //输出 [1,2,3]
for(let val of arr){
    console.log(val);
}
输出:
1
2
3

Array.forEach


和for of相比,他有一个特点就是可同时遍历出索引和值.
基本用法:

var arr=[1,2,3]
 arr.forEach((val,key)=>{
    console.log(val,key);
    val+=this; //不会改变原数组内容
    //这里的this是forEach的第二个参数1,该参数是可选的,当不填时,默认指向window
},1)
输出:
1 0
2 1
3 2

Array.map


Array.map可以返回遍历的内容,形成新的数组,而Array.forEach没返回值,示例如下:

let arr=[1,2,3]
let newArr=arr.map(function(val,key,_arr){
    //其中参数_arr为arr本身
    return val * this;
},3);
console.log('newArr:',newArr)
console.log('arr:',arr)
输出:
newArr: [ 3, 6, 9 ]
arr: [ 1, 2, 3 ]


Array.filter


Array.filter顾名思义,对数组进行过滤,返回满足条件的元素组成的数组

//取数组中的奇数
let arr = [1,2,3,4,5,6,7,8,9];
let odds = arr.filter(item => item % 2)
console.log(odds);
输出:
[ 1, 3, 5, 7, 9 ]

和foreach、map、filter的相似点:

  • 都不能中途停止遍历.除非程序报错。
  • 不能循环时通过增加数组元素来改变循环次数
  • 但可以减少数组元素来改变循环次数,如使用pop()、shift()
  • 修改回调传入的值不会改变原数组

Array.reduce


Array.reduce是一个累加器,用于累加整个数组的值,当然,你也可以用于拼接数组中的所有字符串,这取决于你回调函数中的处理逻辑.其中第二个参数为初始值,可省略.

let arr = [1,2,3];
let total = arr.reduce((total,item)=> {
   return total + item;
});
console.log(total);
输出:
6

总结:

  • for效率非常高,而且灵活,但代码可能没其他方法简洁
  • 要遍历对象,除for in 外,一般都是先获取对象的属性成为数组,然后再实现遍历
  • for in 不建议用于数组,除非你清楚他的特性