8

I'm working through The Principles of Object-Oriented Javascript and am confused with Zakas's use of a named key inside an array (as opposed to inside an object). See comment:

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);
});

His code works fine (as it does if you substitute the array for an object literal), but my understanding has been that you should use an object if you want to access the contents by name. From the array article on w3schools:

Many programming languages support arrays with named indexes.
Arrays with named indexes are called associative arrays (or hashes).
JavaScript does not support arrays with named indexes.
In JavaScript, arrays always use numbered indexes.

Is there a good reason that Zakas used an array like this? Can you please explain it? Alternatively, is this something I should submit to the errata?

8
  • This code is correct if type is integer
    – hindmost
    Commented Sep 13, 2015 at 15:41
  • This code still works because it is setting properties on the array object, not in the actual array. Check Object.getOwnPropertyNames(this._listeners) and then Object.keys(this._listeners). this._listeners should be an object, not an array.
    – rgajrawala
    Commented Sep 13, 2015 at 15:42
  • @hindmost: The code is correct (in that it's functional) even if type isn't an integer (and it seems pretty likely it won't be, much more likely to be a string like "click"). Commented Sep 13, 2015 at 15:43
  • 4
    @T.J.Crowder Unless somebody passes the string "length" for type. Then exciting things happen. Commented Sep 13, 2015 at 15:44
  • 1
    @RaymondChen: Indeed, which is why ES6 has Map. Commented Sep 13, 2015 at 15:48

4 Answers 4

8

The only reason I can see is : confusing people. JavaScript doesn't enforce anything, really, and since everything is an object, you can do pretty much whatever you want. This guy is using an array to store named properties, but he could very well have used a function or anything else !

Edit : almost everything is an object (it would have occured to me that someone could try to set a property on undefined, since one of the most common errors in JavaScript is TypeError : undefined is not an object. sighs JavaScript, why are you doing this to us ?

2
  • 5
    "everything is an object". Not everything!
    – Ram
    Commented Sep 13, 2015 at 15:40
  • 2
    Undefined is not an object... typeof undefined === "undefined"
    – rgajrawala
    Commented Sep 13, 2015 at 15:40
3

Arrays are just special objects....

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

Output;

value 7 9
value abc 20
value xxx 30
3

Is there a good reason that Zakas used an array like this?

From the quoted code, I can't think of one, no. He's not using the fact that _listeners refers to an array. It frankly just looks like a typo. Since it works (because JavaScript's normal arrays are objects), the typo wasn't caught. Well, until you caught it. :-)

Unless there's some code you haven't quoted which is then adding array enries* to that array, there's no reason to use an array there (and arguably a couple of reasons not to).

* "array entry" = property whose key is an array index. So what's an "array index"? "A String property name P is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232−1." (spec)

1

I'm inclined to say it's a typo but in the off-chance it was intended, then the following is the only functional use-case I can come up with... (warning: may divide opinion).

Given that an observer/event pattern is a generic concept that could be applied to any object and the "private" (denoted by the underscore) _listeners property will likely never need to be known about by the target - then it's arguable that the data it contains is superfluous to the object itself.

That is to say, it's probably not desirable to transmit this kind of data should the target object be serialized. The following example illustrates how the JSON serializer ignores the non-numeric array properties in foo.baz - similarly in your own example, all attached event data would be removed:

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":[]}
1
  • Imma give you an upvote because damn dude, that's some lateral thinking right there. I don't think it's necessarily a good reason for abusing JS constructs, but it's thought-provoking. Commented Oct 2, 2019 at 15:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.