eval
like var a = eval; a(code)
or (0, eval)(code)
or window.eval(code)
are treated as a global evals, i.e., they can't access or declare variables in the lexical scope. For example,
var x=1; (function(){ var x=0; (eval)("console.log(x)"); })(); // prints 0 var x=1; (function(){ var x=0; (0,eval)("console.log(x)"); })(); // prints 1You can check this post by Juriy Zaytsev for more information about global eval. According to another post of the same author, which examines the
delete
operator and eval
, you can also delete a variable declared in eval:
(function (){ var x = 10; return delete x; })() // false (function (){ eval('var x = 10;'); return delete x; })() // trueIn words, eval-introduced bindings do not have the
DontDelete
property set on them, so they can be deleted unlike proper lexical variables. Note that the behaviours of eval
will change if strict mode is used.Variable scope. In JavaScript, variables declared by
var
are scoped by closures, not by blocks. In particular, variables declared in a block can overwrite a global variable. This feature may confuse programmers familiar with languages such as C++ and Java, where variables are scoped by blocks.var name = "Joe"; if(true) { var name = "Jack" } // name is "Jack" now (function() { var name = "Joe" })() // name is still "Jack"One therefore has to use a closure to declare a genuine local variable. The
let
keyword introduced in ECMA 6 declares block-scoped variables and helps avoid this pitfall.Binding vs assignment. In most imperative languages including JavaScript, variable declaration creates a reference to a mutable value. In the following example, variable i enclosed in the anonymous function points to a value which is mutated by further iterations of the loop.
for (var i=0; i<3; ++i) { if(i==1) setTimeout(function (){ console.log(i) }, 1000); }// prints 3To capture the value of i at creation time, we can exploit a feature of JavaScript that functions are called by value when the parameters are of primitive types. In the following, the value of i is copied to parameter j when the anonymous function is created. Thus the value of i is captured as expected.
for (var i=0; i<3; ++i) { if(i==1) setTimeout((function (j){ return function (){ console.log(j) } })(i), 1000); }// prints 1In general, binding creates a new variable within the current context, while assignment changes the value of an existing variable within the narrowest scope. In languages such as SML and Go, you can use different syntactic rules to choose between binding and assignment. In JavaScript, the "=" symbol always denotes an assignment. One however can bind a variable through a call-by-value function parameter, as is shown in above example.
Function declaration. When a JavaScript program executes, it runs with context (variable bindings, call stack, etc) and process (statements to be invoked in sequence). Declarations contribute to the context when the execution scope is entered. They are different from statements and are not subject to the order in which statements are invoked. In the following example, function
foo
is returned even though the code that defines it is unreachable at runtime:(function (){ return foo; function foo(){} })();In ES5, function declarations are forbidden within non-function blocks (such as an
if
block). However, all browsers allow them and interpret them in different ways. For example, consider(function (){ if(false) { function foo(){} } return foo; })();In Firefox, the function declaration is interpreted as a statement. Thus
foo
is not defined when it is returned, which would cause a runtime error. In IE, Chrome and Safari, however, the function is returned as expected with standard function declarations.Declaration hoist. In JavaScript, declarations of functions and variables are hoisted (moved) to the beginning of their innermost enclosing scope. Note that it is the declaration that got hoisted, not the assignment expressions. In the following example, the global variable x is shadowed by the local one due to declaration hoist.
var x = 0; (function (){ console.log(x); var x = 1; })() // prints "undefined" |
equals to |
var x = 0; (function (){ var x; console.log(x); x = 1; })() // prints "undefined" |
(function (){ function foo() { return 0 } return foo(); function foo() { return 1 } })(); // returns 1 |
(function (){ var foo = function (){ return 0 } return foo(); var foo = function (){ return 1 } })(); // returns 0 |
foo
is hoisted and thus shadows that of the first foo
. In the right snippet, only the declaration of the second foo
is hoisted, and this doesn't change the result of the first assignment. See also the "function declaration" paragraph.Array allocation. JavaScript arrays should be treated as a special hash table. When you initialize an array with a statement like
var a = new Array(10)
, you don't allocate a memory of 10 cells as you do in Java and C++. Instead, you create an Array object with length
property of value 10. You can use any type of keys to store any type of values in an array, e.g., a[-1] = 1
, a["fruit"] = "apple"
, etc. The length property of an array is updated automatically to accommodate the largest non-negative integer key of its contained values. Whatever the keys are, an array traversal only visit the values with non-negative integer keys explicitly assigned, as is shown in this code: (see this thread for more details)
var a = new Array(); a[-1] = a["a"] = a[11] = 1; console.log(a.length); // prints 12 // Map traversal: visit all keys ever set for(var key in a) { if(a.hasOwnProperty(key) console.log(key + ': ' + a[key]) } // Array traversal: visit all valid array indices a.forEach(function (val, index){ console.log(index + ': ' + val) }) // prints "11: 1"To actual allocate a memory block of 10 cells, use
Array.apply(null, Array(10))
. To assign initial values to an array, use either this trick or utility functions such as _.range of underscore.js.Trailing comma. ES3 does not allow a trailing comma when defining an object literal. For example, we should write
{foo1:"bar1",foo2:"bar2"}
instead of {foo1:"bar1",foo2:"bar2",}
. However, most browsers (except IE) go against the spec and allow both usages. ES5 resolved this issue by going with the majority and legitimizing the trailing comma in the spec. Note that a trailing comma is still not allowed in JSON according to ES5. Thus '{foo:"bar",}' does not represent a valid JSON object.Non-commutative operators. Check this table for a detailed list of the surprising behaviors of operators +, *, == and ===. These behaviors result from JavaScript's eccentric type coercion rules. One can make use of these coercion rules to write extremely puzzling code, see this script for instance. If you are not that familiar with these rules, be extremely careful when you have to do arithmetic operations over objects of different types.
Discrete floating point. Numbers in JavaScript are internally stored in double-precision floating-point format. Hence, not all numbers can be exactly represented, even for those falling in the seeming reasonable range. For example,
var x = 9999999999999999; // x == 10000000000000000 var eq = (.3 == .1 + .2); // eq is false because .1 + .2 == .30000000000000004If you need to check equality between numbers, you have to take relative error into account, e.g.:
function eq(x, y) { // x and y are numbers return (x==y) || (!(x>0)^(y>0) && Math.abs(x - y) < Number.EPSILON) }
(more to come in the future)