JavaScript: Warts and workarounds
matt.might.net
matt.might.net
* "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.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.)
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.> 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. 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;
}
}
} 0 == '0' // true
0 == '' // true
'0' == '' // falseThose statements are basically (as per ECMA-262 11.9.3):
0 === Number('0') 0 === Number('') '0' === ''
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.
2. Just don't ever use == or !=. Forget they exist. Done.
new Breakfast('bacon', 'eggs') // great
new Breakfast.apply(null, ['bacon', 'eggs']) // b0rk 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 // trueIn 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. 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);
} foods = ['bacon', 'eggs', 'toast']
new Breakfast foods...Also, on the topic of the "eta" function, it's odd that the author seems unaware of existing implementations of `bind`.
Edit: He says it's not that `with` isn't useful, but that there is never a case where it isn't confusing.
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...
It's simpler to bind the function to the current object. https://developer.mozilla.org/en/JavaScript/Reference/Global...
In another note, the article says that '' == false yields false but in Chrome and Firefox it returns true.
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...
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.
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.
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`.
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.
JS developers, understand this: the language is not elegant.