JavaScript - 数组和类数组对象的区别

58

我在 JavaScript 中经常遇到 "类数组对象" 这个术语。 它是什么?与普通数组有何区别?一个类数组对象和普通对象之间有什么区别?


一个是数组,另一个是对象。一个有数组方法,而另一个可能有也可能没有。 - Kevin B
3
类数组对象是一种可以使用常规 for 循环和数字索引进行迭代的对象。许多本地 DOM 方法(如 getElementsByClassName())返回类数组对象。 - Teemu
1
我认为这不是一个坏问题。“类似数组”的意思可能是该对象例如公开了forEach方法(并不意味着它是)。澄清哪些特征使对象类似数组是好的 :) - Felix Kling
5个回答

53

它是什么?

一个具有非负整数长度属性和通常一些索引属性的对象。例如:

var ao1 = {length: 0},                     // like []
    ao2 = {0: 'foo', 5: 'bar', length: 6}; // like ["foo", undefined × 4, "bar"]

你可以使用 Array.prototype.slice 方法将 类数组对象 转换为它们的 数组 版本。

var arr = Array.prototype.slice.call(ao1); // []

与普通数组相比有何区别?

它不是由Array数组字面量[]构造的,因此(通常)不会继承自Array.prototypelength属性也通常不会自动更新。

ao1 instanceof Array; // false
ao1[0] = 'foo';
ao1.length; // 0, did not update automatically

类数组对象和普通对象有什么区别?

它们没有区别。即使是正常的ArrayJavaScript中也是Object

ao1 instanceof Object; // true
[] instanceof Object; // true

1
@Taurus 如果你使用 ES5 的方式为某个自定义对象设置原型,你将会得到类似数组的对象 Array.isArray(Object.create(Array.prototype)); // false。如果你使用 ES6+ 的 class A2 extends Arraysuper,你将会得到真正的数组 Array.isArray(new A2); // true - Paul S.
1
即使您在ES5版本中执行了类似于function Foo() {}; Foo.prototype = Object.create(Array.prototype); f = new Foo;的操作,也要考虑到f[0] = 1; f.length; // => 0。这意味着它不是真正的数组,因为设置索引没有预期的长度副作用。在ES6+中执行相同的操作会对长度产生副作用。 - Paul S.
@PaulS.说,如果有一个类似数组的对象var objSort = {0: 1, 1: 7, 2: 2},为什么它不能通过Array.prototype.sort.call(objSort);进行排序? - techie_28
1
@techie_28 加上 length 属性就可以排序了,如果没有 length 属性,你的对象就不是类似于数组的。 - Paul S.
@PaulS。是的,那个方法有效。但只有在起始索引为0时才进行排序。即var objSort = {1: 10, 2: 30,3: 20,length:3}不能像var objSort = {0: 10, 1: 30, 2: 20}一样排序。 - techie_28
显示剩余6条评论

6
著名的HTMLCollection文档)和arguments文档)是自动创建的类似数组对象。
一些快速的类似数组(例如HTMLCollection)与真实数组的区别示例:
var realArray = ['value1', 'value2'];
var arrayLike = document.forms; 

相似之处:

长度获取器是相同的:

arrayLike.length; // returns 2;
realArray.length; // returns 2; //there are 2 forms in the DOM.

索引获取器是相同的:
arrayLike[0]; // returns an element.
realArray[0]; // returns an element. ('value')

它们都是 对象

typeof arrayLike; // returns "object"
typeof realArray; // returns "object"

区别:

在类数组对象中,join()concat()includes()等方法不是函数。

arrayLike.join(", "); // returns Uncaught TypeError: arrayLike.join is not a function (also relevant to `concat()`, `includes()` etc.)
realArray.join(", "); // returns "value1, value2"

数组类似物并不是真正的数组:

Array.isArray(arrayLike); //returns "false"
Array.isArray(realArray); //returns "true"

在类似数组中,您无法设置长度属性:
arrayLike.length = 1;
arrayLike.length; //return 2; //there are 2 forms in the DOM.
realArray.length = 1;
realArray.length; //return 1;

1
首先,数组是一种专门的对象。它的特点在于:
  • 有一个特殊的文字语法[ … ]
  • 有一个自动更新的length属性
  • 数组原型包含你通常从数组中期望的函数
另一个明显的特征是所有元素都有一个数字索引。
从JavaScript的角度来看,任何具有length属性的对象都足以被视为类似数组的对象:
var arrayLikeObject = {
    length: 3,
    name: 'thing',
    '1': 'hello'
};
console.log(arrayLikeObject);

length属性不一定正确。即使在普通数组中,也有可能强制length与实际元素数量不同。缺失的元素都将返回undefined

您可以使用Array.from()将类似数组的对象转换为真正的数组。该函数可以接受各种值,但最简单的是:

var arrayLikeObject = {
    length: 3,
    name: 'thing',
    '1': 'hello'
};
var array = Array.from(arrayLikeObject);
console.log(array);

从这里开始,数组具有所有常规的属性和方法。在上面的例子中,属性[1]被复制到新数组中,但元素[name]没有被复制,因为它不属于真正的数组。 Array.from()函数还接受一个映射函数作为第二个参数。这允许您在传输过程中进行任何必要的更改:
var arrayLikeObject = {
    length: 3,
    name: 'thing',
    '1': 'hello'
};
var array = Array.from(arrayLikeObject,
    (element,index) => element?element.toUpperCase():`Item ${index}`
);
console.log(array);

0

此外,还存在性能差异。在这个视频里,Mathias Bynens建议使用数组而不是类数组对象,因为V8针对常见的数组进行了优化。


-1

我认为,在ES6中,如果一个东西是可迭代的(具有[Symbol.iterator]属性),那么它就类似于数组。


1
“类数组对象”是一个真实存在的名称。它具有定义好的属性,可以具体应用于以特定方式构建和使用的对象上。 - monsto
1
你是在想还是在知道?根据这篇文章https://dmitripavlutin.com/javascript-array-from-applications/中的声明,你可能是正确的 -> 此外,你可以使用Array.from()与任何实现可迭代协议的对象或原始值。 - GarfieldKlon
2
No. { length: 5} 是一个类似数组的对象,但不是可迭代的。 - AliN11

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接