在JavaScript中创建类似数组的对象

25

在JavaScript中,存在一些表现为数组或类似于数组的对象。这些对象包括 arguments、从getElementsByClassName等方法返回的NodeList对象以及jQuery对象。

当使用console.log打印时,它们会显示为数组,但实际上它们并不是数组。我知道想要将一个对象定义为类似于数组的对象,必须具备一个length属性。

因此,我创建了一个像这样的“对象”:

function foo(){
    this.length = 1;
    this[0] = "bar";
}

var test = new foo;

当我使用console log(test)时,我会得到一个(预期的)foo对象。我可以使用以下方法“转换”为数组:

当我使用 console log(test) 时,我会得到一个(预期的)foo对象。我可以使用以下方法将其“转换”为数组:

Array.prototype.slice.call(test)

但我不想把它转换成数组,我希望它表现得像一个类数组对象。如何创建类数组对象,使得用console.log输出时它看起来像一个数组?

我尝试过将foo.prototype = Array.prototype,但是console.log(new foo)仍然显示一个foo对象而非数组。


4
“我该如何创建一个类似数组的对象,以便在控制台打印时它看起来像是一个数组?”仅仅因为控制台没有使用数组字面语法或其他让你觉得它是数组的语法而显示该对象,并不意味着它不是“类似数组”的。控制台如何显示数据与数据本身是什么无关。 - user1106925
1
@amnotiam,虽然这是真的,但我发现在帮助控制台协助我的情况下很有用。当我记录一个“类似数组”的对象时,我并不是非常关心对象本身,而更关心它的内容。 - zzzzBov
@amnotiam:我只是有点好奇jQuery对象和arguments是如何工作的。我想知道为什么它们被记录为数组。:-P - gen_Eric
@zzzzBov:我并不反对这个观点。我只是想说,拥有一个“类数组对象”和控制台如何显示该对象之间存在区别。 - user1106925
1
@Rocket:我理解你的意思。但是这不是jQuery对象或arguments的工作方式。这是各种控制台的工作方式,它们可能彼此不同。也许我有点误解了你上面的句子。 - user1106925
@amnotiam:不,你是对的。我没意识到那只是控制台在这么做。我还以为这是对象本身的某种特殊情况。 - gen_Eric
5个回答

32

具体取决于控制台。在Chrome的开发者控制台和Firebug中,对于自定义对象,您将需要使用lengthsplice属性。 splice 也必须是一个函数。

a = {
    length: 0,
    splice: function () {}
}
console.log(a); //[]

需要注意的是,目前并没有官方标准。

以下代码是 jQuery (v1.11.1) 内部使用的,用于确定一个对象应该使用 for 循环还是 for..in 循环:

function isArraylike( obj ) {
    var length = obj.length,
        type = jQuery.type( obj );

    if ( type === "function" || jQuery.isWindow( obj ) ) {
        return false;
    }

    if ( obj.nodeType === 1 && length ) {
        return true;
    }

    return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
注意,可能会出现在控制台中呈现为数组 ([]) 但在 jQuery 中使用 for..in 循环进行迭代的对象,或者在控制台中呈现为对象 ({}) 但在 jQuery 中使用 for 循环进行迭代的对象。

不错!添加 splice 属性确实将其显示为数组! - gen_Eric
@Rocket,这就是为什么jQuery对象在控制台中看起来像数组,尽管它们并不继承自“Array”的原因。 - zzzzBov
3
无论如何,这只是“控制台”实现的一种幻觉。毕竟,在ECMAscript中并没有“Array”(当我们忘记TypedArrays时)。 - jAndy
@TimDown: 我的意思是指,指定的 Array 只是从 Object 继承了一些语法糖。所以实际上,一个 console 对象在记录这些内容时应该对两者都进行相同处理。 - jAndy
函数和 window 是类似数组的这一点非常糟糕。它们上面的 length 属性毫无意义。 - usr
显示剩余3条评论

5
相同的问题也在我的脑海中浮现,我们为什么可以使用类似于arguments参数的数组:

function arrayLike() {
  console.log(typeof arguments)
  console.log(arguments)
  console.log(Array.from(arguments))
}
arrayLike(1,2,3)

那么,让我们尝试创建自己的类似数组的对象:

let arrayLikeObject = {
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))

显然,这里没有定义长度属性,所以我们的arrayLikeObject只会返回一个空数组。现在,让我们试着定义一个长度属性:

let arrayLikeObject = {
  length: 2,
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))

如果长度设置不同会怎样?

let arrayLikeObject = {
  length: 1,
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))
 // it will only return the value from first `0: 1`

let arrayLikeObject = {
  length: 5,
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))
 // other 3 values will be printed as undefined


但是,我不想转换它...

实际上你想创建一个数组,而不是类似数组的对象。类似数组的对象必须像你说的那样被转换:

Array.prototype.slice.call(arrayLikeObject)
// Or,
[].slice.call(arrayLikeObject)

如果你尝试在类似数组的对象上使用数组方法,那么你将会得到类型错误:

let arrayLikeObject = {
  length: 5,
  0: 1,
  1: 2
 }

 console.log(arrayLikeObject.sort())

因此,要在arrayLikeObject上使用数组方法,我们需要将其转换为数组,就像我们在之前的示例中使用Array.from一样。
否则,您只需要创建一个数组:
let arr = [1,2] // I don't mean, you don't know

其他注意事项:
你不能将其用作构造函数:

let arrayLikeObject = {
    length: 1,
    slice: function () {
      return 1
    }
}

console.log(new arrayLikeObject) // Type error

在下面的代码片段中,由于长度属性被设置为1但没有索引为0的属性,结果将是[undefined]

let arrayLikeObject = {
  length: 1,
  slice: function () {
    return 1
  }
}
console.log(Array.from(arrayLikeObject))

但是,如果将长度设置为0,则结果将是一个空数组[],因为我们告诉这个类似数组的对象没有任何值。


很高兴你觉得有帮助。 - Bhojendra Rauniyar

3

看这个:

var ArrayLike = (function () {

 var result;

 function ArrayLike(n) {

     for (var idx = 0; idx < n; idx++) {
         this[idx] = idx + 1;
     }

     // this.length = Array.prototype.length; THIS WILL NOT WORK !

 }


 // ArrayLike.prototype.splice = Array.prototype.splice; THIS WILL NOT WORK !


 // THIS WILL WORK !
 Object.defineProperty(ArrayLike.prototype, 'length', {

     get: function() {

         var count = 0, idx = 0;

         while(this[idx]) {
             count++;
             idx++;
         }
         return count;

     }

 });


 ArrayLike.prototype.splice = Array.prototype.splice;


 ArrayLike.prototype.multiple = function () {

     for (var idx = 0 ; idx < this.length ; idx++) {

         if (result) {
             result = result * this[idx];
         } else {
             result = this[idx];
         }
     }

     return result;
 };

 return ArrayLike
 })();

var al = new ArrayLike(5);

al.__proto__ = ArrayLike.prototype;

console.log(al.length, al.multiple(), al); 

这将在 Chrome 中显示:5 120 [1, 2, 3, 4, 5]


如果数组中的任何元素为假值,长度属性不会给出正确的结果,是吗? - Clement Cherlin

3
这有用吗:扩展数组原型,看起来他正在做你所做的事情,并将原型创建为数组,但包括一个额外的方法(可能有效,也可能无效,我没有测试过):
var MyArray = function() {
};

MyArray.prototype = new Array;

MyArray.prototype.forEach = function(action) {
    for (var i = 0, l=this.length; i < l, ++i) {
        action(this[i]);
    }
};

希望对您有所帮助。


这将Array.prototype中的所有函数添加到MyArray中,但是对于arguments和jQuery对象,它们没有这些函数。 - gen_Eric
这也添加了一个enumerable属性forEach - NoSkill

1

我想this是你正在寻找的内容。覆盖toString函数。

foo.prototype.toString = function()
{
    return "[object Foo <" + this[0] +">]";
}

不完全正确。我的意思是当你使用 console.log 时,它应该像一个数组 [] 一样显示。在 Chrome 中,当你使用 console.log(arguments) 时,它看起来像一个数组,但实际上并不是。 - gen_Eric

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