1. 伪数组有时又称类数组,是数组很相似的对象,因此伪数组无法使用数组类型的相关 API(如 push、shift、pop、map 等)
  2. 若我们想要使用这些 API 的话,那么就需要先将伪数组转为真数组

伪数组(array-like object)

如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)。

  1. var obj = {
  2. 0: 'a',
  3. 1: 'b',
  4. 2: 'c',
  5. length: 3
  6. };
  7. obj[0] // 'a'
  8. obj[1] // 'b'
  9. obj.length // 3
  10. obj.push('d') // TypeError: obj.push is not a function

上面代码中,对象obj就是一个类似数组的对象。但是,“类似数组的对象”并不是数组,因为它们不具备数组特有的方法。对象obj没有数组的push方法,使用该方法就会报错。

“类似数组的对象”的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但是有一个问题,这种length属性不是动态值,不会随着成员的变化而变化。

  1. var obj = {
  2. length: 0
  3. };
  4. obj[3] = 'd';
  5. obj.length // 0

上面代码为对象obj添加了一个数字键,但是length属性没变。这就说明了obj不是数组。

典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串。

  1. // arguments对象
  2. function args() { return arguments }
  3. var arrayLike = args('a', 'b');
  4. arrayLike[0] // 'a'
  5. arrayLike.length // 2
  6. arrayLike instanceof Array // false
  7. // DOM元素集
  8. var elts = document.getElementsByTagName('h3');
  9. elts.length // 3
  10. elts instanceof Array // false
  11. // 字符串
  12. 'abc'[1] // 'b'
  13. 'abc'.length // 3
  14. 'abc' instanceof Array // false

上面代码包含三个例子,它们都不是数组(instanceof运算符返回false),但是看上去都非常像数组。

数组的**slice**方法可以将“类似数组的对象”变成真正的数组

  1. var arr = Array.prototype.slice.call(arrayLike);

除了转为真正的数组,“类似数组的对象”还有一个办法可以使用数组的方法,就是通过call()把数组的方法放到对象上面。

  1. function print(value, index) {
  2. console.log(index + ' : ' + value);
  3. }
  4. Array.prototype.forEach.call(arrayLike, print);

上面代码中,arrayLike代表一个类似数组的对象,本来是不可以使用数组的forEach()方法的,但是通过call(),可以把forEach()嫁接到arrayLike上面调用。

下面的例子就是通过这种方法,在arguments对象上面调用forEach方法。

  1. // forEach 方法
  2. function logArgs() {
  3. Array.prototype.forEach.call(arguments, function (elem, i) {
  4. console.log(i + '. ' + elem);
  5. });
  6. }
  7. // 等同于 for 循环
  8. function logArgs() {
  9. for (var i = 0; i < arguments.length; i++) {
  10. console.log(i + '. ' + arguments[i]);
  11. }
  12. }

字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call遍历。

  1. Array.prototype.forEach.call('abc', function (chr) {
  2. console.log(chr);
  3. });
  4. // a
  5. // b
  6. // c

注意,这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。

  1. var arr = Array.prototype.slice.call('abc');
  2. arr.forEach(function (chr) {
  3. console.log(chr);
  4. });
  5. // a
  6. // b
  7. // c

使用 slice 将伪数组转真数组

  1. var arr1 = [1, 2, 3]
  2. var arr2 = arr1.slice()
  3. var arr3 = arr1
  4. arr2 // [1, 2, 3]
  5. arr1 === arr2 // false
  6. arr1 === arr3 // true

类数组对象是一个拥有 length 属性和索引元素的普通对象。这类对象并没有数组的标准方法,比如 pushpop 或者 slice。这种对象的一个例子是 arguments 对象。当你调用 Array.prototype.slice.call(arrayLike),你实际上是在调用 slice 函数,并将它的 this 值设置为你的类数组对象。由于 slice 函数只关心对象的 length 属性和索引元素,因此它可以正常工作。

slice 函数的工作原理是创建一个新数组,并将原数组或类数组对象中的元素复制到新数组中,因此返回的结果是一个真正的数组。

因此,这种方法可以将具有 length 属性和索引元素的任何对象转换为真实的数组,然后使用所有的数组方法,如 mapfilter 等,并且这个数组包含了原对象的所有元素。