This page contains the answers to the Gotcha quiz.
Equality
if (a == b) { ... }Ordinary equality (==) can be surprising and so use === instead:
0 == '0' // True 0 == '' // True '' == '0' // False. Not transitive! undefined == null // TrueI don’t know of any situations where == is preferred. To compare two values after conversion to string (respectively number) make it explicit by writing respectively:
'' + x === '' + y +x === +y
Addition
x = (a + b) + c y = a + (b + c) x == y // True or False? When?In string + number the number is silently converted to a string.
js> typeof ('1' + 2) string js> ('1' + 2) + 3 123 js> '1' + (2 + 3) 15In number + string the number is again silently converted to a string.
js> typeof (1 + '2') string js> (1 + 2) + '3' 33 js> 1 + (2 + '3') 123If conversion to a string is required make it explicit by writing:
'' + a + b + c
Trailing comma
x = [ 'first value', 'second value', 'third value', ]Doing this is good in Python and bad in JavaScript. In Python it makes it easier to reorder, insert and delete values. In JavaScript you’ll get, depending on the browser a syntax error (IE) or a trailing undefined in the array (FF).
Missing comma
x = [ [1, 2, 3], [4, 5, 6] [7, 8, 9] ]In Python you’d get an TypeError from this code, as in:
py> [4, 5, 6][7, 8, 9] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: list indices must be integers, not tupleIn JavaScript, we have:
js> [4, 5, 6][7, 8, 9] === undefined trueand so the assignment to x is equivalent to
x = [ [1, 2, 3], undefined ]
Missing new
var Point = function(x, y){ this.x = x; this.y = y; } pt = Point(2, 4)After running this code we have, at the command line:
js> x 2 js> y 4In other words, we’ve assigned values to the global object! In addition, we have:
js> pt == undefined true
Bind is transient
fn = obj.method; // General form of the gotcha. x = [0, 1, 2, 3, 4, 5]; // Example of gotcha. a = x.slice(1, 3); // What happens behind the scenes? tmp = x.slice; // What happens here? b = tmp(1, 3); // What happens here?Python uses bound methods and so in Python a.b is a first class object.
class A(object): def b(self): pass py> a = A() # Create an instance. py> a.b <bound method A.b of <__main__.A object at 0x21d7910>>JavaScript uses what can be called transient bound methods. Thus
js> a = [1, 2, 3] 1,2,3 js> b = [4, 5, 6, 7, 8] 4,5,6,7,8 js> a.slice === Array.prototype.slice true js> a.slice === b.slice // No implied reference to *this*. true js> a.slice(1, 3) // Transient implied ref to *this*. 2,3 js> b.slice(1, 2) // Difference transient implied ref. 5,6The function call
b = tmp(1, 3);is equivalent to calling Array’s slice method on the global object, which is probably not what is wanted!
Missing var
var average = function(a, b){ total = a + b; return total / 2; }The Python code
def average(a, b): total = a + b return total / 2.0works because in Python total is recognised as being local to the function average.
In JavaScript variables are global unless explicitly declared as local.
The sample code has a interesting side effect. It assigns to the global variable total.
js> average(3, 7) 5 js> total 10
Missing closure
// Add handlers: click on item to show item number. var make_and_add_handlers(items){ for(var i=0; i < items.length; i++){ items[i].onclick = function(){ alert("This is item " + i); }; } }This code, when run, gives all items the same number (namely number of items less one). This is because only one value of i is captured. (It is not because all items are getting the same onclick function. They are getting different functions, but with identical behaviour.)
Here’s one solution. In the main function instead write
items[i].onclick = alert_n(i);where we have:
var alert_n(n){ return function(){ alert("This is item " + n); }; };When we do this, each onclick function captures its own copy of the argument i to alert_n. (Closure capture function parameters in the same way as it captures function variables.)
Missing that
// Return fn that does something (and logs the instance). proto.fn_factory = function(...){ var fn = function(...){ ... log("Name = " + this.name); ... } return fn; } // Example of desired output. js> instance.name Charles js> fn = instance.fn_factory(...) js> fn(...) // Logging to go to console. Name = CharlesThe code above (provided the global object does not have a name attribute) produces:
js> fn(...) // Logging to go to console. Name = undefinedWhen a JavaScript function executes, what this refers to depends on the calling context. In particular, in
var fn2 = function(...){ this.attribute = '123'; return function() { return this.attribute; }the two occurences of this usually refer to completely different objects. To fix instead write:
var fn2 = function(...){ this.attribute = '123'; var that = this; return function() { return that.attribute; }This works because that does not have the special role this has. In the returned function, that refers to the same object as that does in main part of fn2, which in turn refers to the same object as the this object in the body.
It is customary to write that=that to achieve this effect. (Following the custom means no further explanation is required.)