Problems iterating over arrays and objects in JavaScript

本文介绍在JavaScript中遍历数组或对象时常用的一些方法,以及使用for循环时的TricksBest Practices.

遍历

基本上,数组和对象的遍历会有如下三种方案:

本文只介绍第一部分,即for循环。

for

语法

for ( ExpressionNoInopt ; Expressionopt ; Expressionopt) Statement
for ( var VariableDeclarationListNoIn ; Expressionopt ; Expressionopt ) Statement

用法

var sum = 0;
for (var i = 0; i < 10; i++) {
  sum += i;
}

这是JavaScript中最传统也是最常使用的遍历数组的方法,这里有一点需要注意,就是使用var定义的变量的作用域始终是所包含的scope,除非使用ES2015+提供的let

ES2015新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

下面是使用let后的代码,大家可以看到i只在for block内有效,在

var sum = 0;
for (let i = 0; i < 10; i++) {
  sum += i;
}

console.log(i); // ReferenceError: i is not defined

如果浏览器不支持let,可以使用Babel将你的ES2015+代码编译为与ES5兼容的代码,如上面这段代码,使用Babel后就会被编译为:

'use strict';

var sum = 0;
for (var _i = 0; _i < 10; _i++) {
  sum += _i;
}

console.log(i); //ReferenceError: i is not defined

从上面代码,可以看出,在for循环中,变量i编译后变为_i了,那自然在代码块外面引用i时会报错了。

for…in

语法

for ( LeftHandSideExpression in Expression ) Statement 
for ( var VariableDeclarationNoIn in Expression ) Statement

用法

var steps = {
  one: 'one',
  two: 'two',
  three: 'three'
};

var countdown = "";

for (var step in steps) {
  countdown += steps[step] + "\n";
}

console.log(countdown);

陷阱1

遍历数组索引,但是同时也会遍历数组的属性值以及原型链上可枚举属性

Array.prototype.foo = 'foo';

var steps = ['one', 'two', 'three'];
var countdown = "";

steps.bar = 'bar';

for (var step in steps) {
  countdown += steps[step] + "\n";
}

console.log(countdown);
// one
// two
// three
// bar
// foo

陷阱2

遍历对象时,对象属性值的遍历顺序是不保证的。

ES3标准规定for…in语句的属性遍历的顺序是由对象定义时属性的书写顺序决定的

The mechanics of enumerating the properties (step 5 in the first algorithm, step 6 in the second) is implementation dependent. The order of enumeration is defined by the object. …

而在ES5标准中对 for…in语句的遍历机制又做了调整,属性遍历的顺序是没有被规定的,也就是说并没有规定对JavaScriptObject类型中的属性的存储顺序。

The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration….

对于对象遍历来说,这个并不是什么大问题,因为它本来就是无序的。但是你并不太可能想遍历数组时也是无序的,因为这会让你得到意想不到的结果。

跳过继承属性

遍历对象时,大多数时候我们并不想遍历继承属性,这时可以在遍历时通过hasOwnProperty()方法跳过继承属性。

for (var step in steps) {
  if (steps.hasOwnProperty(step)) {
    console.log(step);
  }
}

for…of

ES2015中引入了Iterator的概念,它为你迭代集合中的每一个元素提供了更大的便利。Iterator属于一种接口规格,任何数据结构只要部署这个接口,就可以完成遍历操作,即依次处理该结构的所有成员。它的作用有两个,

在ES2015中,遍历操作特指for…of循环,即Iterator接口主要供for…of消费。for…of与其它的遍历一样,除了它是专门用于与Iterables对象(部署了Iterator的对象,Array或是String都是Iterables)一起工作的。

let values = [1, 2, 3];

for (let i of values) {
    console.log(i);
}

关于Iterator和Iterable部分,可以参考ECMAScript 6 入门

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

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

const arr = ['red', 'green', 'blue'];
let iterator  = arr[Symbol.iterator]();

for(let v of arr) {
    console.log(v); // red green blue
}

for(let v of iterator) {
    console.log(v); // red green blue
}

JavaScript原有的for…in循环,只能获得对象的属性名,不能直接获取属性值。而for…of循环,允许遍历获得键值。

var arr = ["a", "b", "c", "d"];

for (a in arr) {
  console.log(a); // 0 1 2 3
}

for (a of arr) {
  console.log(a); // a b c d
}

上面代码表明,for…in循环读取属性名,for…of循环读取属性值。如果要通过for…of循环,获取数组的索引,可以借助数组实例的entries()方法和keys()方法

for each…in

只有Firefox提供,用于遍历对象属性值,不建议使用

用法

var sum = 0;
var obj = {prop1: 5, prop2: 13, prop3: 8};

for each (var item in obj) {
  sum += item;
}

console.log(sum); // logs "26", which is 5+13+8

总结

数组遍历

由于数组遍历有上述的这些问题,也许你永远也不会用for…in去遍历数组了。那么我们有如下的替代选择:

对象遍历

var obj = {
  first: "John",
  last: "Doe"
};
// Visit non-inherited enumerable keys
Object.keys(obj).forEach(function (key) {
  console.log(key);
});

参考

Related Posts

Xin(Khalil) Zhang 11 April 2015
blog comments powered by Disqus