在JS数组中使用命名键

7

我正在阅读JavaScript面向对象编程指南,但我对Zakas在数组中使用命名键(而不是在对象内)感到困惑。请看下面的注释:

function EventTarget() {}

EventTarget.prototype = {

    constructor: EventTarget,

    addListener: function(type, listener) {

        if (!this.hasOwnProperty("_listeners")) {
            // Why isn't this: `this._listeners = {};`
            this._listeners = [];
        }

        if (typeof this._listeners[type] === "undefined") {
            this._listeners[type] = [];
        }

        this._listeners[type].push(listener);
    },

    // more stuff
}

var target = new EventTarget();
target.addListener("message", function(event) {
    console.log("Message is " + event.data);
});

他的代码很好用(如果你用对象字面量替换数组),但是我理解为想通过名称访问内容时应该使用对象。 在W3Schools上的数组文章中

许多编程语言支持具有命名索引的数组。
具有命名索引的数组称为关联数组(或哈希)。
JavaScript不支持具有命名索引的数组。
在JavaScript中,数组始终使用数字索引。

Zakas为什么要像这样使用数组?你能解释一下吗? 或者,我应该将此提交给勘误表吗?

这段代码仍然有效,因为它在数组对象上设置属性,而不是在实际数组中。检查 Object.getOwnPropertyNames(this._listeners) 然后再看 Object.keys(this._listeners)this._listeners 应该是一个对象,而不是一个数组。 - rgajrawala
@hindmost:即使“type”不是整数(而且它似乎很可能不是),代码仍然是正确的(因为它是功能性的)。更有可能是像“click”这样的字符串。 - T.J. Crowder
4
除非有人传递了字符串 "length" 作为 type 参数,否则什么也不会发生。如果传递了该参数,则会发生一些令人兴奋的事情。 - Raymond Chen
@hindmost 这是完全正确的,我编辑了示例以显示类型设置为“message”。 - Henry Marshall
1
@RaymondChen:确实,这就是为什么ES6有Map的原因。 - T.J. Crowder
显示剩余3条评论
4个回答

7
我能想到的唯一原因是混淆人们。JavaScript 实际上并不强制执行任何规则,由于每个东西都是一个对象,你可以做任何你想做的事情。这位作者使用数组来存储命名属性,但他也可以使用函数或其他任何东西!编辑:几乎所有东西都是对象(我意识到某些人可能尝试在未定义的变量上设置属性,因为 JavaScript 中最常见的错误之一是“TypeError: undefined is not an object”)。JavaScript,为什么要这样对待我们?

5
“everything is an object”翻译成中文是“一切皆对象”。但实际上并不是所有东西都是对象。 - Ram
2
未定义不是一个对象... typeof undefined === "undefined" - rgajrawala

3

有什么好的理由让Zakas像这样使用一个数组吗?

从引用的代码中,我想不出任何理由。他没有利用_listeners是数组的事实。它看起来只是一个笔误。因为它可以工作(因为JavaScript的普通数组是对象),所以这个笔误没有被发现。直到你发现它为止。:-)

除非有一些你没有引用的代码,然后将数组条目*添加到该数组中,否则没有理由在那里使用数组(并且有几个理由不要使用数组)。

*“数组条目”=其键是数组索引的属性。那么什么是“数组索引”?“如果且仅当ToString(ToUint32(P))等于P且ToUint32(P)不等于232−1,则字符串属性名P是数组索引。”spec


2

数组只是特殊的对象。

a=[]
a[7]=9
a["abc"]=20
a["xxx"]=30
for (i in a) console.log("value",i,a[i])

输出;

value 7 9
value abc 20
value xxx 30

1
我倾向于认为这是一个打字错误,但如果有意的话,那么以下是我能想到的唯一的功能用例...(警告:可能会分裂意见)。
鉴于观察者/事件模式是一个通用概念,可以应用于任何对象,并且目标很可能永远不需要知道由下划线表示的“私有”_listeners属性 - 那么可以说,它包含的数据对于对象本身来说是多余的。
也就是说,如果目标对象被序列化,传输这种数据可能是不可取的。以下示例说明了JSON序列化程序如何忽略foo.baz中的非数字数组属性 - 同样,在您自己的示例中,所有附加的事件数据都将被删除:
var foo = {
    bar: {},
    baz: []
};

foo.bar['p1'] = foo.baz['p1'] = 1;
foo.bar['p2'] = foo.baz['p2'] = 2;

console.log( JSON.stringify(foo) ); // {"bar":{"p1":1,"p2":2},"baz":[]}

我会给你点赞,因为那真是一种横向思维。我不认为滥用JS结构是一个好的理由,但这确实值得深思。 - A Fader Darkly

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