有没有办法将现有的Javascript对象转换为数组,而不创建新的独立数组?

6
有很多地方提到数组是JavaScript中的特殊对象类,例如这里:https://www.codingame.com/playgrounds/6181/javascript-arrays---tips-tricks-and-examples。因此,既然一个object是由属性(或键)和值组成的集合,我在想是否有一种方法可以从一个对象开始,并以数组结束(即使用Array.isArray()方法返回该对象为数组并且返回true)。我已经开始查看数组的属性:

let arr = [0, 1, 2, 3, 4, 5];
console.log(Object.getOwnPropertyNames(arr));
console.log(Array.isArray(arr));

所以我尝试使用对象来模拟相同的操作:

let arrEmulation = {0:0, 1:1, 2:2, 3:3, 4:4, 5:5, "length":6};
console.log(Object.getOwnPropertyNames(arrEmulation));
console.log(Array.isArray(arrEmulation));

但是Array.isArray(arrEmulation)仍然返回false。首先,我想道歉的是,如果这是一个愚蠢的问题,但是否有任何方法可以手动将一个object转换为array并添加特殊属性(或键)?请注意,我不想知道如何将对象转换为数组,我只想了解哪些特殊事物使得将一个对象解释为一个数组成为可能。

1
可能是重复的问题,参考如何在JavaScript中将对象{}转换为键值对数组[]? - jmargolisvt
7
我不认为所建议的重复内容解决了原帖中这部分问题:“我只想了解哪些特殊的东西能使解释一个对象像一个数组一样成为可能。”楼主,也许这篇麻省理工学院文章会有用。它是MDN自己isArray()文档中引用的...但我不确定它是否真正涵盖了你寻找的细节。 - Tyler Roper
1
@jmargolisvt 我不想知道如何将对象转换为数组,我想了解需要向对象添加哪些属性或键才能将它们解释为数组。 - SmaGal
@TylerRoper 谢谢,我会去阅读它的... - SmaGal
1
看一下 MDN polyfill(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray),也许你可以尝试让 Object.prototype.toString.call(arrEmulation) 返回 [object Array]。不过我不确定这个函数的定义是什么。 - david
3个回答

6
我认为在严格意义上,根据标准规范,这是不可能的。查看Array.isArray
如果arg的[[Class]]内部属性的值为“Array”,则返回true。
因此,为了使Array.isArray(arrEmulation)返回true,你必须以某种方式修改对象的[[Class]],使其成为Array,而不是Object。但是,查看ES5的8.6.2 Object Internal Properties and Methods关于[[Class]]的内容:
注意:此规范未定义任何ECMAScript语言运算符或内置函数,允许程序修改对象的[[Class]]或[[Prototype]]内部属性,或将[[Extensible]]的值从false更改为true。修改[[Class]],[[Prototype]]或[[Extensible]]的实现特定扩展不得违反前面一段中定义的不变量。
另外:
请注意,此规范不提供任何方法让程序访问该值,除非通过Object.prototype.toString。
因此,ES5官方规范没有提供一种方法来做到这一点 - 如果有一种方法可以实现,那么它将是非标准的和依赖于实现的。
话虽如此,除非您绝对需要使用Array.isArray或者需要Object.prototype.toString.call(arrEmulation)返回[object Array],否则仍然可以使用Object.setPrototypeOf将arrEmulation的原型设置为Array.prototype,从而允许您在对象上使用数组方法,并使instanceof Array返回true:

const arrEmulation = {0:0, 1:1, 2:2, "length":6};
Object.setPrototypeOf(arrEmulation, Array.prototype);

console.log(arrEmulation instanceof Array);
arrEmulation.forEach((value) => {
  console.log(value);
});
// Internal [[Class]] property is still `Object`, though:
console.log(Object.prototype.toString.call(arrEmulation));
// Unlike a true array:
console.log(Object.prototype.toString.call([]));

console.log('-----');

// although you can set the `toStringTag` to the string 'Array' in ES6+,
// it is cosmetic only and does not pass an `Array.isArray` test:
arrEmulation[Symbol.toStringTag] = 'Array';
console.log(Object.prototype.toString.call(arrEmulation));
console.log(Array.isArray(arrEmulation));

但请注意,您应该在实际代码中避免使用Object.setPrototypeOf
引用: 警告:更改对象的[[Prototype]]是一项非常缓慢的操作,因为现代JavaScript引擎如何优化属性访问的性质,在每个浏览器和JavaScript引擎中都是如此。更改继承的影响是微妙而广泛的,并不仅限于在Object.setPrototypeOf(...)语句中花费的时间,还可能延伸到具有已更改[[Prototype]]的任何对象的任何代码。如果您关心性能,则应避免设置对象的[[Prototype]]。相反,使用Object.create()创建具有所需[[Prototype]]的新对象。
(当然,Object.create涉及创建一个对象,这与您想要做的更改现有的arrEmulation对象是不同的)

在ES6+中似乎也没有办法做到这一点 - 它的文本有些相似,但并不完全相同。具体来说,为了使Array.isArray返回true,所涉及的对象需要是一个“数组奇异对象”(或指向其的Proxy)- 但setPrototypeOf仅设置原型,它和任何其他方法都不能使对象实际上成为数组奇异对象(似乎必须由解释器本地构造,并且无法模拟足够多)。


谢谢,伙计。这些信息让我更清楚地理解了标准对象和数组之间的区别。只是想让你知道,我并不是真的想用对象来模拟数组,而是想了解它们之间的差异,这对我很有帮助。加一! - SmaGal
1
@SmaGal 在典型的JavaScript引擎中实现将会有进一步的优化。很可能一个典型的数组将是一个密集的内存块,通过指针直接访问。对于相当静态的对象也是如此,它们的属性将被替换为访问内存地址到一个密集的内存结构体。在这两种情况下,运行时密集的方法将被编译为快速的内存访问通过直接指针。 - Falco

2
Javascript的核心在于原型继承:

原型继承:所有JavaScript对象都从一个原型中继承属性和方法:

Date对象从Date.prototype中继承,Array对象从Array.prototype中继承,Person对象从Person.prototype中继承,Object.prototype位于原型继承链的顶部:

Date对象、Array对象和Person对象都继承自Object.prototype。

如此看来,isArray是Array对象原型链中的一个函数。

如果isArray不存在,则可使用MDN Array.isArray() 中建议的polyfill替代:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

因此,类型是由原型链决定的,而不是它返回的值。

最初的回答被翻译成了“Original Answer”。

Similarly, as per Tio Zed's answer

const newArray = Array.from(arrEmulation) // [0, 1, 2, 3, 4, 5]
Array.isArray(newArray)
它真正做的只是将原型从对象更改为数组的原型。
感谢 @Kaiido 让我深入了解 isArray。数组检查这三个点

如果 Type(arg) 不是 Object,则返回 false。 如果 arg 的 [[Class]] 内部属性值为 "Array",则返回 true。返回 false。

而且

数组实例继承自 Array 原型对象的属性, 它们的 [[Class]] 内部属性值为 "Array"。 数组实例还具有以下属性。


呃,不对。Array.isArray 不是检查这个东西的方式。那只是在 MDN 文章中发布的一个简单的 polyfill,它绝不是任何浏览器将要实现的方式。这里是规格书所要求的内容,尽管他们并没有清楚地说明如何检查是否为数组对象。这里有一个证明,证明了它的实现方式并非如此:https://jsfiddle.net/f6182mkh/ - Kaiido
我修改了我的回答,只是想表明一切取决于原型而不是它们在值中看到的键。顺便说一下,感谢你的纠正。如果你仍然觉得我的回答有问题,请编辑我的回答。@Kaiido - Black Mamba
即使是原型模型也不正确:https://jsfiddle.net/vLrgdp86/ - Kaiido
我上面定义的内容目前被用作Polyfill,以支持许多不支持它的浏览器。我搜索了关于它当前正式实现的信息,但没有成功。如果您能为我提供一个文档,那将会很有帮助。这里是我找到的关于Array.isArray的一些详细信息。官方文档在这里展示了如何实现定义数组的原型。@Kaiido - Black Mamba

-2

你可以使用Array.from()将任何接近数组的东西转换为数组。 在你的例子中,我们只需要调用:

const arrEmulation = {0:0, 1:1, 2:2, 3:3, 4:4, 5:5, length: 6};

const newArray = Array.from(arrEmulation) // [0, 1, 2, 3, 4, 5]
Array.isArray(newArray) // true

1
我不是在寻找如何将对象转换为数组,而是想要理解它们之间的区别... - SmaGal
2
@SmaGal,公平点——您在问题标题中没有说“转换”吗? - user8745435
@eric99,我并没有给你点踩。我只是解释我不是在寻找那个东西。 - SmaGal
@SmaGal,我的评论有点玩笑。鉴于问题现在已经变得更加具体,TioZed的回答值得一些赞扬。 - user8745435

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