Hacker News new | past | comments | ask | show | jobs | submit login
JavaScript: Warts and workarounds (might.net)
117 points by fogus on Feb 14, 2012 | hide | past | favorite | 44 comments



For the record, CoffeeScript tries to help ameliorate all of these "warts".

* "Hoisting under the hood" -- Function declarations vs. function expressions don't get you into hoisting trouble, because there are no function declarations, and every value is an expression.

* Explicit block scopes can be (more easily) created with:

    do (x, y) ->
      # here, x and y are captured in a new scope.
* "What does 'this' mean?" -- The value of "this" can be fixed lexically, by using the bound function (fat arrow) =>, instead of the normal function arrow: ->. Look ma, no "var that" or "var self":

    $.getJSON url, (resp) =>
      this.setResponse resp
* "Fixing Arguments" -- Function arguments are always available as an array instead of an Arguments object.

    myVariadicFunction = (args...) ->
      # here, "args" is an Array.
* "Avoiding truthiness" -- There is no "==" and "!=" in CoffeeScript, all equality is strict equality. For the one case where double equals is useful in JS, you have the existential operator...

  if a is b
    # here, a === b.


I'm a friend of Matt's, and I enjoy his blog posts, but I'm afraid this post is full of bad advice and misinformation. He claims that `with` followed by an object literal is fully statically analyzable (it's not, because of prototypes), and he recommends using it without considering how badly it deoptimizes in all modern JS engines.

And spicyj is right that NaN is not equal to itself in most languages because that's how IEEE754 specifies it. In particular, this is true in Scheme, one of Matt's favorite languages. ;-P

(Also, depending on what he means by "true equality," his `equal` function fails to distinguish -0 and 0, which are distinct values in the language. However, I suspect those are best treated as the same value. It's extremely rare to want to treat -0 as if it exists at all.)


Yet, there is still a value x such that x != x and x !== x.

That value is NaN.

If true equality matters, use a helper function:

  function equal(a, b) {
    if (a === b)
      return true ;
    if (isNaN(a) && isNaN(b))
      return true ;
    return false
  }
People keep repeating this as a strangeness of JavaScript, whereas in fact this is standard floating-point behavior, the same as you'll see in any other language (C, Java, Python, really everything). In JavaScript, you almost always just want a === b, not a function that checks for NaN.


I think it's because you're more likely to encounter NaN in JavaScript due to the lack of real integers (er, should I say true integers? Everything is floating-point, anyway).


Since NaN != NaN you could also do (a === b) || ((a != a) && (b != b))


I don't know why you'd use a != a instead of isNaN(a). The former hides what you're doing. Though treating two NaN values as equal doesn't even make sense in the first place.


The first code sample in the article is wrong:

> In most curly-braced languages, blocks delineate lexical scope. For example, in C or Java:

    { 
        int i = 13 ;
        { 
           int i = 42 ; 
           print(i) ;
        }
        print(i) ;
    }
In fact this will not compile in Java.


I am not very familiar with Java details. Can you explain why this doesn't compile?


It's this part:

    int i;
    {
        int i;
    }
Java does not allow you to shadow one local variable with another.

This is ok:

    {
        int i;
    }
    int i;
because the scopes do not overlap.

This is ok too:

    int i;
    {
        class LocalClass {
            public void method() {
                int i;
            }
        }
    }


One of the strangest things about == is that although it's commutative, it's not transitive:

  0 == '0'  // true
  0 == ''   // true
  '0' == '' // false


Is it really that strange?

Those statements are basically (as per ECMA-262 11.9.3):

0 === Number('0') 0 === Number('') '0' === ''


Honestly, I don't agree that most of these are warts. In particular function scoping vs block scoping, get THE F over it. Gees.

Sure when I first started JavaScript it bit me exactly once. Since then, I got use to it. It's a different language, every language has its quirks. I'm sure if you started with JavaScript the fact that many other languages DON'T have block scope might upset you. You know what else doesn't have block scope? PYTHON!

    def foo():
      if True:
        x = 123
      print x
prints 123. In C, Java, C++ you'd get an error that x doesn't exist.

    void foo() {
      if (true)
        int x = 123
      printf("%d\n", x);
    }

    error: ‘x’ was not declared in this scope
4 meanings of this?

The fact is it gives you huge flexibility. By default, lots of libraries add names to the global object (for browsers that's 'window') but because you can control 'this' for all functions you can force any library to install itself to some other object.

Can you do that in C, C++ or Java? As far as I know the only way to fix that in those languages is to edit a bazillion source files and change namespaces of you're lucky enough to have them.

Truthiness?

Again it's just different not broken. Lots of dynamic languages have strange kinds of conversions. Learn them and get over it. The only people this messes up are people expecting it to behave like Java or C/C++. It's not Java or C++. It's not JavaScript's fault you're too stubborn or set in your ways to try it a different way.


1. Don't use with. It's hard to reason about, and it breaks many compiler optimizations (you're adding a dynamic object into the scope chain).

2. Just don't ever use == or !=. Forget they exist. Done.


I've always found == to be very useful when comparing undefined, because undefined == null. It's very rare that I want to treat undefined and null separately (unless one or the other means there was an error earlier).


That's the only reasonable use for ==, IMO.


I recently added my own syntax rule to JavaScript mode in Emacs to highlight lone { at the beginning of line (with or without trailing whitespaces). After your comment, I think about adding some more highlights for with, == and !=.


Are there any other hard and fast rules I should be aware of?


Are there ever. Read "JavaScript: The Good Parts" or watch the lectures by the same name on YouTube.


It's also impossible to instantiate an object with `new` and varargs.

    new Breakfast('bacon', 'eggs') // great
    new Breakfast.apply(null, ['bacon', 'eggs']) // b0rk


CoffeeScript has to bend over backwards to allow this, but it does...

    foods = ['bacon', 'eggs', 'toast']

    new Breakfast foods...


This works:

    var a = new (Breakfast.bind.apply(Breakfast, [null, 'bacon', 'eggs']))
    a instanceof Breakfast // true
Or...

    var a = new (Function.bind.apply(Breakfast, [null].concat(['bacon', 'eggs'])))
    a instanceof Breakfast // true
UPDATE (for kicks):

Or... (works with Crockford's Object.create)

    var a = Object.create(Breakfast.prototype);
    Breakfast.apply(a, ['bacon', 'eggs']);
    a instanceof Breakfast // true


Awesome. I do notice a gigantic warning on MDN to not rely on this behavior, though: https://developer.mozilla.org/en/JavaScript/Reference/Global...

In p.js (github.com/jayferd/pjs), I used this pattern/workaround:

    var Breakfast = function(args) {
      if (!(this instanceof Breakfast)) return new Breakfast(arguments);
      if (args && typeof this.init === 'function') this.init.apply(this, args);
    }
So these would be equivalent:

    Breakfast('bacon', 'eggs')
    new Breakfast(['bacon', 'eggs'])
Whereas `new Breakfast` would return an "uninitialized" object as in Object.create.


I used a similar pattern once, except _generically_ as follows:

    function Breakfast(my, args) {
      if (!(this instanceof arguments.callee)) return new arguments.callee(arguments);
      if (Object.prototype.toString.call(arguments[0]) === '[object Arguments]') arguments.callee.apply(this, args);
    }


Very cool. Bind is a much-appreciated addition to the language.


Unfortunately it wasn't there from day one. It was only specified a year or two ago (thus IE 8 and Opera 11.50 didn't have it, and apparently Safari 5.1 still doesn't) so you have to be prepared to check for it and roll your own.

http://kangax.github.com/es5-compat-table/


I just stumbled across this interesting SO answer:

http://stackoverflow.com/a/3362623/49485


That use of `with` makes me squeamish as hell.

Also, on the topic of the "eta" function, it's odd that the author seems unaware of existing implementations of `bind`.


Crockford warns against ever using `with` in a talk he gave last year. Discussion on `with` at 19m58s: http://ontwik.com/javascript/douglas-crockford-javascript-pr...

Edit: He says it's not that `with` isn't useful, but that there is never a case where it isn't confusing.


That is very well put. It is a shame that JS got its scoping so very upside-down, because it means that tiny changes in code can cause incredibly confusing and initially undetectable bugs. `with` is basically an infected bandaid on a broken system. It seems like it's helping until you notice the smell.


Sure there are - why are so many Crockford pronouncements taken as a signal to stop thinking?

Notice that his "with is bad" examples (which are usually bad examples and bad usages [1]) always involve contrived examples of potentially ambiguous assignment - one of the more useful ways to use `with` is with objects whose properties are DSL-like functions, named in such a way that there's no mistaking their origin and with no ambiguous assignment involved.

Simple example with includes: https://github.com/insin/DOMBuilder/blob/master/examples/red...

More extensive example of inheritable templates for a CRUD admin app: https://github.com/insin/sacrum/blob/master/lib/sacrum/admin...

[1] http://webreflection.blogspot.com/2009/12/with-worlds-most-m...


The way around this scoping issue is to declare `that`.

It's simpler to bind the function to the current object. https://developer.mozilla.org/en/JavaScript/Reference/Global...


Something sort of random I just noticed: you can wrap stuff in {} to make blocks without an actual block statement...just a random block of code. That may be pretty useful once the let keyword becomes widely available, and could be used now to maybe organize lots of code?


You can also use blocks this way in C, Java, etc.


However, in C and Java they're actually useful because they delimit variable scope. In JavaScript I'm not aware of any use for them.


EcmaScript is working on growing a way to indicate block scope with "let" as mentioned elsewhere in this thread.


Not really, wrapping the code in {} makes no difference whatsoever, but a anonymous function executed after creation does (function(){ /* code */ })()

In another note, the article says that '' == false yields false but in Chrome and Firefox it returns true.


Actually, wrapping the code in {} does make a difference with 'let'. A 'let' in braces does not escape the braces.


Well, the "let" keyword still doesn't work on any major browser so is like it doesn't even exist yet.


"let" has worked in Firefox for years; support for it first shipped in Firefox 2 back in 2006. You do have to opt in to it by putting type="application/javascript;version=1.7" on your script tag, since the feature is not backwards-compatible and hence couldn't be added to existing scripts without possibly breaking them.

JS 1.7 in general added a bunch of neat features (array comprehensions, destructuring assignment, etc) that are only now slowly making their way to specifications after some foot-dragging by certain parties. See https://developer.mozilla.org/en/JavaScript/New_in_JavaScrip...


Yeah, where right now you must use a self-invoking function, the let keyword would imitate that behavior with just {}'s. Less typing.

The only difference is there's no way to return a value from the blocks because this: var name = { return 'Awesome' }

...wouldn't work because, besides return being only for functions, when you use the = it assumes an object literal and throws a syntax error.


My rule of thumb is, as soon as it gets to the point where I need to use "this", I find a library that abstracts that whole business away.

I've been there and it sucks. I say leave it to someone with the expertise to use "this" in Javascript without shooting themselves in both feet, at least for browser-side work.


Learn the language. You can't just outsource understanding the language you write code in. Even if you just mash together some jQuery you have to understand the semantics for `this` or you're going to trip over them later.

It's not even that complicated. Honestly, just put in 10 minutes one day to learn how it behaves. Even if you end up just always assigning a new variable (e.g. var self = this) it's better than putting your fingers in your ears and closing your eyes when you see `this`.


Most of the JS development that I've seen happen is done by people holding their nose and refusing to properly learn the language's idioms, or by people that wish they could be writing in another language that abuses JS as a glorified bytecode interpreter.

I agree that it's a pretty awful language. I wish it weren't the only choice in web dev (and resent the HTML5 zealots that chafe at any attempt to go beyond it with new languages - LOL, as if HTML5 was a real standard to begin with!) But it's there, that's reality, and the good programmers need to deal with it.

Coffeescript makes it a modicum less terrible, but it's Yet Another Language to learn, and people that are already burdened with maintaining more than just the FE stack, like myself, are getting pretty tired of context-switching between all these languages and mini-frameworks-with-adorable-nonsensical-names.


BAM, the best comment from a regular, passionate, and smart developer.

JS developers, understand this: the language is not elegant.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: