Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Many over-simplifications and misconceptions #84

Closed
angus-c opened this Issue · 9 comments

7 participants

@angus-c

[I apologize if this feedback sounds harsh. I admire the effort but want to ensure that JavaScript developers are not mislead by the advice]

Object Usage and Properties

Everything in JavaScript acts like an object, with the only two exceptions being null and undefined.

Not true. Objects act like objects. Primitives (string, boolean, number and undefined) have no properties so cannot act like objects.
The confusion is because false.toString() first coerces the false primitive to a Boolean object.
See http://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/

A common misconception is that number literals cannot be used as objects

Its not a misconcpetion. Its true :-) Again "number literals" are just primitives, they have no properties.
(2).toString() references a property. It only works because the numeric primitive is coerced to an Object

That is because a flaw in JavaScript's parser tries to parse the dot notation

Not a flaw. The syntax 2.toString() is ambiguous.

Objects in JavaScript can also be used as a Hashmap

In JavaScript, all objects are hashes, all hashes are objects

setting the property to undefined or null only remove the value associated with the property, but not the key.

Setting to null does not remove the value - since null is itself a value

for(var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i, '' + obj[i]);
}
}

console.log is not supported in all browsers
'' + obj[i] is unecessary - all console log output is ultimately coerced to a string

The Prototype

Not one mention of constructor in entire text of this section!

Bar.prototype = new Foo();

Bad practice - creating a new instance of Foo may have side effects or throw error if constructor requires args
Use Object.create() or shim thereof

One mis-feature that is often used is to extend Object.prototype or one of the other built in prototypes.

Extending Object.prototype is far more dangerous than extending other native prototypes yet they are all lumped together here. Misleading

Further, the native prototypes should never be extended unless it is for the sake of compatibility with newer JavaScript features.

Many would argue with this

The for in Loop

Only hasOwnProperty will give the correct and expected result, this is essential when iterating over the properties of any object

An over-simplification. What if you wanted to iterate the prototype of a custom type?
And some would say it is never necessary for non custom object iteration since Object.prototype, by convention is never extended. John Resig didn't feel he had to use it everywhere in JQuery
Yes its necessary for array iteration using for-in but you shouldn't be using for-in over an array in the first place!

This version is the only correct one to use. Due to the use of hasOwnProperty it will only print out moo. When hasOwnProperty is left out, the code is prone to errors in cases where the native prototypes - e.g. Object.prototype - have been extended.

Again, no one extends Object.prototype.

One widely used framework which does this is Prototype. When this framework is included, for in loops that do not use hasOwnProperty are guaranteed to break.

Prototype has not extended Object.prototype for about 6 years. It extends Array.prototype but again for-in is not useful for array iteration

It is recommended to always use hasOwnProperty.

:-/

How this Works

There are exactly five different ways in which the value of this can be bound in the language.

There are at least 6 - what about within the evaluation context?
http://javascriptweblog.wordpress.com/2010/08/30/understanding-javascripts-this/

The arguments object

The arguments object is not an Array. While it has some of the semantics of an array - namely the length property

Also shared with array are the numerically indexed properties: arguments[0], arguments[1] etc....

Array.prototype.slice.call(arguments);
This conversion is slow, it is not recommended to use it in performance critical sections of code.

The peformance impact is negligible in modern browsers

Arrays

the getter of the length property simply returns the number of elements that are contained in the array

Not exactly - it returns one larger than the highest indexed property.
var a = [];
a[2] = 23;
a[5] = 12;
a.length = 6;

The typeof operator

The typeof operator (together with instanceof) is probably the biggest design flaw of JavaScript, as it is near of being completely broken.

Its not that bad. There is just one flaw - null is considered an 'object' (though it is also inconvenient that arrays are considered 'object's)

typeof really has only one practical use case, which does not happen to be checking the type of an object.
typeof foo !== 'undefined'
The above will check whether foo was actually declared or not; just referencing it would result in a ReferenceError. This is the only thing typeof is actually useful for.

Not true, typeof has many uses - checking for function, boolean, number, string and undefined.
Its also easy to use it to check for regular objects while excluding null:-
var isObj = a && (typeof a == 'object')

some return values of typeof are not defined in the specification

Wrong, they are all defined. See http://ecma262-5.com/ELS5_HTML.htm#Section_11.4.3

Why not to use eval

The use of eval should be avoided at all costs. 99.9% of its "uses" can be achieved without it.
eval should never be used, any code that makes use of it is to be questioned in its workings, performance and security. In case something requires eval in order to work, its design is to be questioned and should not be used in the first place, a better design should be used, that does not require the use of eval.

What about the other 0.01%? JSONP? JSON.parse()? Crockford, JQuery, Prototype all use it.

General

Article is quite repetitive - the same statements about the same topics resurface in many sections. The "don't ever do this" always "do this" tone is too simplistic and misleading. Almost every JavaScript feature has its uses - our job is to explain not dictate.

@michaelficarra

Not a flaw. The syntax 2.toString() is ambiguous.

It's not necessarily ambiguous, it's just ambiguous when using a 1-character lookahead.

Objects in JavaScript can also be used as a Hashmap

In JavaScript, all objects are hashes, all hashes are objects

Same thing. Objects can be used in any way you like. Objects can be used as histograms. It's all just looking at the same thing from a different angle. So there's nothing wrong with the current text, it's just not that useful.

console.log is not supported in all browsers

For the sake of the examples, we should assume it does. It's not teaching about console.log, that's simply a tool.

Further, the native prototypes should never be extended unless it is for the sake of compatibility with newer JavaScript features.

Many would argue with this

Not many would. Maybe a re-wording like "It is generally accepted as a good practice to refrain from extending native prototypes except when implementing shims." because, really, it is.

The for in Loop

This whole section is written defensively, and there's nothing wrong with defensive programming. Good programmers look both ways before crossing a one-way street.

There are exactly five different ways in which the value of this can be bound in the language.

There are at least 6 - what about within the evaluation context?

La la la evaluation context doesn't exist la la la.

Not true, typeof has many uses - checking for function, boolean, number, string and undefined. Its also easy to use it to check for regular objects while excluding null:-
var isObj = a && (typeof a == 'object')

I've found that typeof is useful for only 2 things: avoiding ReferenceErrors for variables that may not have been declared and testing for [[call]]ability by check that typeof X == "function". It's good for nothing else. var isObj = Object(a) === a.

Wrong, they are all defined. See http://ecma262-5.com/ELS5_HTML.htm#Section_11.4.3

Double wrong. What's the expected value of typeof /(?:)/? Hint: it's not defined. It depends whether the regexp instances in the implementation are callable, which is likely if you have a recent version of chrome of firefox, but unlikely if you have a nightly since they both just removed that feature.

What about the other 0.01%

A regular JS user should never EVER ever ever use eval. NEVER. It's only use is in shims for missing browser functionality. Examples: JSON API, cross-document messaging API (JSONP replacement), etc. The frameworks you mention are providing this missing functionality for browsers that don't have it, and only use it when they've run out of other options.

Article is quite repetitive - the same statements about the same topics resurface in many sections.

I agree.

The "don't ever do this" always "do this" tone is too simplistic and misleading.

Agreed. Always and never are strong words. It's very opinionated the way it is currently written, but that's maybe not such a bad thing.

Everything I didn't comment upon I have either have no objections to, agree with, or both.

@padolsey

A common misconception is that number literals cannot be used as objects

Its not a misconcpetion. Its true :-) Again "number literals" are just primitives, they have no properties.
(2).toString() references a property. It only works because the numeric primitive is coerced to an Object

Might be worth mentioning other implications of this. E.g.

var nExplicit = new Number(3);
var n = 3;

nExplicit.prop = 'FOO';
n.prop = 'FOO';

nExplicit.prop; // => 'FOO'
n.prop; // => undefined
@sfoster

Most (mis)uses of eval stem from not knowing you can do property lookup with variable property names e.g. someObject[propName]. So you get people doing eval('someObject.' + propName).
Any text that advises on eval should address this specifically

hasOwnProperty isnt useful when copy/merging properties from one object to another, if there are prototyped properties you want. I prefer:

var empty = {}; 
for(var i in obj) {
    if(i in empty) continue;
    /// do stuff with i or obj[i]
}

This meets the goal of avoiding unintentional enumerable properties resulting from Object.prototype extensions, without conflating the issue with 'ownProperties' and what that means in the context of custom prototypes.

The repeated conversion of arguments to an array can bite you performance-wise if its used in library functions that get heavy use. To avoid it is a micro-optimization in most places, but legit in others.

@robotlolita

Everything in JavaScript acts like an object, with the only two exceptions being null and undefined.

Not true. Objects act like objects. Primitives (string, boolean, number and undefined) have no properties so cannot act like objects.

Actually, it's true. Primitives act like objects, because they are coerced to one when you manipulate them. They are not, however, objects.


Further, the native prototypes should never be extended unless it is for the sake of compatibility with newer JavaScript features.

Many would argue with this

Not many would. Maybe a re-wording like "It is generally accepted as a good practice to refrain from extending native prototypes except when implementing shims." because, really, it is.

Extending native prototypes (Object.prototype, inclusive), is okay, as long as you have control over the environment. There is really no argument against it, since the semantics of all native objects are well defined by specs, and the implementations follow close (or reasonably close afaict). Extending host objects is dangerous because the semantics are not guaranteed.


Again, no one extends Object.prototype.

I do.


A regular JS user should never EVER ever ever use eval. NEVER.

Eval is not really something one should avoid, it's just misused a lot. Things like run-time macro-expansion can only be achieved through this. There are also some other uses that I could mention, but really, eval is a useful feature in the language. Or at least, all Lispers would tell you so :3

@MarcusPope

The for in Loop

Only hasOwnProperty will give the correct and expected result, this is essential when iterating over the properties of any >>object

An over-simplification. What if you wanted to iterate the prototype of a custom type?
And some would say it is never necessary for non custom object iteration since Object.prototype, by convention is never >extended. John Resig didn't feel he had to use it everywhere in JQuery
Yes its necessary for array iteration using for-in but you shouldn't be using for-in over an array in the first place!

Perhaps object.prototype is never extended by your own convention, but there is no universal standard to never use it despite what many proclaim. But even still, its lack of existence in jQuery doesn't mean that's a standard to emulate.

And by the same token, hasOwnProperty checks would not be necessary for array iteration using for..in if people didn't extend the Array.prototype. Every argument against extending the base Object.prototype could be applied to the Array.prototype yet for some reason people hold a double standard on that one. And really it's quite ok to use for..in on arrays if you add a hasOwnProperty check (in some cases it's faster than checking against array.length.)

As for John Resig, he actually simply didn't know to include the check in the early days of jQuery - it was a bad programming practice that was commonly made back then. But once he researched the issue he supported it's inclusion (I know this because I worked with him on patching the main library.) So he's actually supported the inclusion of hasOwnProperty checks for the past 4 years, but alas he succumbed to peer pressure and "the daunting task of refactoring" and they ultimately closed the requested bug fix a few months ago (and honestly I question whether it was even Resig himself who closed the ticket or, more likely, someone else who took over his bug tracker account.)

The point of the matter is any for..in iteration over any type of object without the use of hasOwnProperty check is a bug in javascript. The bug may not always break the code, but that doesn't mean it's not a bug.

On a side note - why people still use for..in loops instead of an iterator function these days is beyond me, call stack performance loss is negligible, and there are a dozen other benefits to doing so the least of which is it's a DRY practice.

Cheers!

@robotlolita

The point of the matter is any for..in iteration over any type of object without the use of hasOwnProperty check is a bug in javascript. The bug may not always break the code, but that doesn't mean it's not a bug.

Sometimes one may desire to iterate over all of the properties of an object, including the ones in the prototype chain.

For example:

var defaults = Object.create(null, { foo: { value: 1 }, bar: { value: 2 }})
var my_config = Object.create(defaults, { foo: { value: 3 }})

do_something_using(my_config)

In this case, it would be sensible to let do_something_using to iterate over all the exposed properties in the my_config object, including those who come from the prototype. Delegative inheritance is a really powerful thing, and it's quite sad that JS didn't manage to do it properly before (by making all the objects delegate to a root even when you don't want that).

@MarcusPope

@killdream you are absolutely correct on that point - I intended my statement to be limited to the case of iterating over own properties only - for instance if you wanted to sum an array of integers using for..in and you didn't do a hasOwnProperty check, that's a bug. But if you want access to the prototype chain, you most certainly can do so without the check and it is not considered a bug.

In your case however, you still wouldn't want to rely exclusively on for..in without some form of check against the prototypes you're iterating over. Because even though you want 'bar' to be included, you might also iterate over an Object.prototype.baz = function() {}; reference, which is likely not going to play well with your ints. hasOwnProperty isn't the only way to skin a cat, but it's the best way imo.

You can type check the values, or more explicitly check for defaults.hasOwnProperty in addition to my_config.hasOwnProperty. Both of those options exist to protect yourself against your loop context operating on non homogenous data.

But I'm not quite sure what you meant by your last statement that "it would be proper for all objects to delegate to a root even when you don't want that"... can you elaborate some more? (comments@marcuspope.com if you prefer.)

@robotlolita

@MarcusPope I'd recommend reading the example again. defaults don't delegate to anything, it's the last object in the prototype delegation chain: null <| defaults <| my_config. On top of that ES5 introduced Object.defineProperty, which lets you flag properties as non-iterables.

About my comment on how flawled the first design of JavaScript was, I mean that there's a problem when all objects must ultimately have a common root. In Java, that's Object. In some other languages it's root, or bottom or something. In JS, before ECMAScript 5, all objects were forced to share the common root Object.

With ECMAScript 5, you can create objects that don't inherit properties from anything, that is, they don't have Object as their root prototype:

var empty_object = Object.create(null)  // null <| empty_object
var pseudo_empty_object = {}  // null <| Object.prototype <| pseudo_empty_object
@timruffles
Collaborator

Hi! If anyone from this thread would like to suggest pull requests, they'd be welcome. I'm helping to maintain this repo now so things are indeed being merged.

Thanks.

@timruffles timruffles closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.