数组&迭代器

数组循环

方式一:for

const arr = [1, 2, 3];
for(let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
// 改进版,缓存数组长度、减少重复运算
const arr = [1, 2, 3];
for(let i = 0, size = arr.length; i < size; i++) {
  console.log(arr[i]);
}

方式二:for in

const arr = [1, 2, 3];
for(let index in arr) {
  console.log(arr[index]);
}
// 1 2 3

for in语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性本质是遍历对象的属性、而不是数组的索引。

const person = {
  name: "kevin",
  sex: 'man',
  age: 24
};
for(let info in person) {
  console.log("person[" + info + "] = " + person[info]);
}
// person[name] = kevin
// person[sex] = man
// person[age] = 24

注意:for in遍历是无序的

for in语句可以遍历数组是因为 JS 中数组本质也是一个对象。 在语法上我们可以通过数字下标获取对应元素,并且每个数组有一个length属性,所以和其它语言的数组很相似。可以通过数字索引访问数组元素是因为在运行时数字会自动转换为字符串。 JS 数组在实现上也不是真正意义上的数组,JS 数组在内存中并不是连续的,JS 数组的索引其实是对象的属性名。 for in遍历时并不会遍历length属性,是因为for in语句只能遍历可枚举属性,length属性是不可枚举的。

for in语句并不适合用来遍历数组

Array.prototype.fatherName = "Father";
const arr = [1, 2, 3];
arr.name = "Hello world";
let index;
for(let index in arr) {
  console.log(arr[index]);
}
// 运行结果:1 2 3 Hello world Father

这个结果显然不是我们想得到的,所以想安全的使用for in遍历数组就需要做额外的判断

let key;
const arr = [];
arr[0] = "a";
arr[100] = "b";
arr[10000] = "c";

for(key in arr) {
  console.log(key)
  if(arr.hasOwnProperty(key)  && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294
    ) {
    console.log(arr[key]);
  }
}

缺点:使用for in语句遍历是无序的,还需要额外的属性验证,所以通常性能相对较低。 优点:因为for in是对属性遍历,即只会遍历存在的实体,上面的例子只进行了 3 次循环,如果使用传统的for循环会进行 10000 次循环。所以处理得当for in语句也有合适的使用场景。

方式三:Array.prototype.forEach()

Array.prototype.forEach(callback)是 ES5 引入的新 API

for in语句类似,只会对有效的值运行callback,被删除或未赋值的项目会被跳过。不同的是原型链上的属性不会被遍历。

注意:forEach遍历的范围在第一次调用callback前就会确定

  • 调用forEach后添加到数组中的项不会被callback访问到。
  • 如果已经存在的值被改变,则传递给callback的值是forEach遍历到他们那一刻的值。
  • 已删除的项不会被遍历到。
const data = [1, 2, 3];
data.forEach((value, index, source) => {
  console.log(value);
});

方式四:for of

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环。作为遍历所有数据结构的统一的方法。

for of语句优化现有数组遍历语法的问题:

  1. forEach语句不能breakreturn
  2. for in语句需要进行属性验证;
const arr = ['a', 'b', 'c'];
for(let data of arr) {
  console.log(data);
}

for of语句不仅可以遍历数组,还可遍历任意类数组对象,但是不能遍历普通对象。

类数组对象

类数组对象指的是包含数字索引和length属性的对象。和数组类似可以使用数字索引,通过length属性可以获得属性的数量。不同的是原型链上没有forEachmapfilter等方法。常见的类数组对象有:

  • arguments
  • NodeList
// 类数组对象转换为数组
Array.prototype.slice.call(arrayLike);
Array.prototype.splice.call(arrayLike, 0);
Array.prototype.concat.apply([], arrayLike);

// ES6
Array.from(arrayLike);
const array = [...arrayLike]

// 类数组对象遍历
Array.prototype.forEach.call(arguments, a => console.log(a))

迭代器

Iterator 是 ES6 的新特性,for of语句做为遍历所有数据结构的统一的方法就是基于 Iterator。

Iterator 是一个接口,定义了数据访问机制,实现了 Iterator 接口的对象可以按照接口使用统一的语句进行遍历。 Iterator 接口主要供for of和解构赋值(数组形式)等语法消费,数组、字符串、Map,arguments等类数组对象都实现了 Iterator 接口,前面提到过for of语句不能遍历普通对象就是因为没有实现 Iterator 接口。

Iterator 接口的实现定义在Symbol.iterator属性上,接口实现函数运行后返回对象有next()方法,每次调用next()方法返回一个对象,对象包含两个属性:

  • value: 当前迭代元素的值
  • done: 是否已经遍历完成
const iterator = [1, 2, 4][Symbol.iterator]()
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 4, done: false}
iterator.next(); // {value: undefined, done: true}

普通对象实现 iterator 接口支持for of遍历

//方法一:
var obj = { a:1, b:2, c:3 };

obj[Symbol.iterator] = function(){
  var keys = Object.keys(this);
  var count = 0;
  return {
    next(){
      if(count < keys.length){
        return { value: obj[keys[count++]], done: false};
      }else{
        return { value: undefined, done: true};
      }
    }
  }
};

// 方法二
obj[Symbol.iterator] = function*(){
  var keys = Object.keys(obj);
  for(var k of keys){
    yield [k,obj[k]]
  }
};

TODO:

  • Array.from实现
  • Array.prototype.filter实现
  • Array.prototype.map实现
  • Array.prototype.reduce实现
  • Array.prototype.flat实现
  • Array.prototype.splice实现
  • 数组去重实现
    • Array.from(new Set(arr))
  • 数组

参考

深入了解 JavaScript 中的 for 循环

results matching ""

    No results matching ""