JavaScript has many pitfalls. This post examines whether they make JavaScript “unfixable” as a language – as some argue. To do so, it separates the pitfalls into two categories: The ones that become harmless after learning about them and the ones that don’t. We’ll also look at how the upcoming ECMAScript 6 fixes most problems.
Warning: If you are new to JavaScript then don’t let this post be your introduction to it. Consult other material first.
var obj = { name: "Jane", friends: ["Tarzan", "Cheeta"], printFriends: function () { // forEach argument is block lambda => can omit parentheses this.friends.forEach { | friend | console.log(this.name+" knows "+friend); } } }
var Employee = Person <| function (name, title) { super.constructor(name); this.title = title; } Employee.prototype .= { describe() { return super.describe() + " (" + this.title + ")"; } };Additionally, there might be class literals which would make things even simpler.
function createInterval(start, end) { // Variable tmp already exists here console.log(tmp); // undefined if (start > end) { var tmp = start; start = end; end = tmp; } return [start, end]; }You quickly learn to use a pattern called immediately-invoked function expression (IIFE). Not pretty, but it works.
function createInterval(start, end) { // Variable tmp does not exist here, // accessing it would cause an error if (start > end) { (function () { // IIFE: open var tmp = start; start = end; end = tmp; }()); // IIFE: close } return [start, end]; }ECMAScript 6 will have block-scoped variables via the let statement.
function createInterval(start, end) { // Variable tmp does not exist here if (start > end) { let tmp = start; start = end; end = tmp; } return [start, end]; }But with ECMAScript 6’s destructuring assignment, you don’t even need a temporary variable to do the swapping:
[start, end] = [end, start];
var result = []; for(var i=0; i < 10; i++) { result.push(function () { return i }) } console.log(result[5]()); // 10, not 5To avoid sharing, you need to make a copy, via an IIFE with a parameter:
var result = []; for(var i=0; i < 10; i++) { (function (i) { // copied i result.push(function () { return i }) }(i)); // original i } console.log(result[5]()); // 5Inadvertent sharing is not a quirk, it’s how things should normally work. I find that I normally notice quite easily when it happens and do the requisite copying. But it can foil beginners. Which is why Dart creates a fresh copy of the iteration value for each loop iteration. ECMAScript 6’s for...of loop will probably do the same. If you use an iteration method where the iteration value is passed to a function, then sharing won’t be a problem, either:
range(0, 10).map(function (i) { result.push(function () { return i }) }); console.log(result[5]()); // 5 function range(start, end) { var arr = []; for(; start<end; start++) { arr.push(start); } return arr; }
function repeat(n, func) { for(var i = 0; i < n; i++) { func(); } } var counter = { count: 0, inc: function () { this.count++; } } // The second argument can’t be just counter.inc repeat(2, counter.inc.bind(counter));
> function Point(x,y) { this.x = x; this.y = y } > Point(12, 7) undefined > x 12ECMAScript 5 strict mode [4] fixes that problem:
> function Point(x,y) { "use strict"; this.x = x; this.y = y } > Point(12, 7) TypeError: Cannot set property 'x' of undefined
> function f() { foobar = "hello" } > f() > foobar 'hello'With strict mode, you get an error:
> function f() { "use strict"; foobar = "hello" } > f() ReferenceError: foobar is not defined
> var proto = { protoProp: true }; > var obj = Object.create(proto); > obj.objProp = true; > for(var propName in obj) { console.log(propName) } objProp protoPropIf, say, Object.prototype.toString wasn’t non-enumerable, it would also show up above, because by default, all objects inherit from Object.prototype.
> var arr = [ "a", "b" ]; > arr.hello = true; > for(var propName in arr) { console.log(propName) } 0 1 helloFor arrays, it would make much more sense to iterate (only) over the array elements and not their indices. ECMAScript 6’s for...of loop will do that.
var arr = [ "apple", "pear", "orange" ]; // You probably only want elem, but you have index, too arr.forEach(function(elem, index) { console.log(elem); });To iterate over the property names of an object, you can use:
Object.keys({ first: "John", last: "Doe" }). forEach(function (propName) { console.log(propName); });
function printArguments() { console.log(Array.prototype.join.call(arguments, "; ")); }Not pleasant, but learnable. And you get errors quickly if you are doing something wrong. At least in ECMAScript 6, you won’t have to use arguments, any more:
function printArguments(...args) { console.log(args.join("; ")); }As a side benefit, function arity can now be automatically checked by the language.