function foo() {
var x = -512;
if (x < 0) {
var tmp = -x;
...
}
console.log(tmp); // 512
}Variable tmp is created inside an if condition, but it continues to live until the end of the function. That is because:
Each variable declaration is moved to the begining of the function at execution time:
function foo() {
console.log(tmp); // undefined
if (false) {
var tmp = 3; // (1)
}
}Internally this function is executed like this:
function foo() {
var tmp; // hoisted declaration
console.log(tmp);
if (false) {
tmp = 3; // assignment stays put
}
}Each function stays connected to the variable that surrounds it, for example:
function createIncrementor(start) {
return function () {
start++;
return start;
}
}
> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8Each call to the function retains the previous value, the function createIncrementor returns another function which is connected to the variable inc. This is called a closure.
The example above has the same result as this:
var start = 5;
function inc() {
return start++;
}
> inc()
6
> inc()
7
> inc()
8But the variable start is globally accesible to anyone and can get changed. With a closure we can protect the variable by making it locally scoped, but at the same time avilable in the parent scope accessible only to the variable inc.
The constructor Function() evaluates javascript code in the provided strings.
The following function decaration:
var add = function (x, y) {
return x + y;
};Is the same as:
var add = new Function('x', 'y', 'return x + y');But, it's much better to use a function declaration as it is easier to read and the code isn't stored in strings, which can be difficult to mantain (inaccessible to tools for syntax check).
Given this exampe:
function f() {
if (condition) {
var tmp = ...;
...
}
// tmp still exists here
// => not what we want
}We dont want the variable tmp to exist past the if block. We can use the IFFE pattern:
function f() {
if (condition) {
(function () { // open block
var tmp = ...;
...
}()); // close block
}
}Immediately invoking the function body we can create a new scope inside the if block.
!function () { // open IIFE
// inside IIFE
}(); // close IIFE
void function () { // open IIFE
// inside IIFE
}(); // close IIFEThe advantage of using prefix operators is that forgetting the terminating semicolon does not cause trouble.
var File = function () { // open IIFE
var UNTITLED = 'Untitled';
function File(name) {
this.name = name || UNTITLED;
}
return File;
}(); // close IIFEWhen the IFFE is inside the expression context (var File = function () {...), there is no need to enforce expression ((function () { ... })();) context for the IFFE because its already inside one.
This:
var x = 23;
(function (twice) {
console.log(twice);
}(x * 2));Is similar to:
var x = 23;
(function () {
var twice = x * 2;
console.log(twice);
}());The bracket operator allows us to refer to a property via an expression.
> var obj = { someProperty: 'abc' };
> obj['some' + 'Property']
'abc'
> var propKey = 'someProperty';
> obj[propKey]
'abc'
> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123
> var obj = { '6': 'bar' };
> obj[3+3] // key: the string '6'
'bar'The bind function creates a new function that calls the receiver of bind. This three invokations are similar:
jane.sayHelloTo('Tarzan');
var func1 = jane.sayHelloTo.bind(jane);
func1('Tarzan');
var func2 = jane.sayHelloTo.bind(jane, 'Tarzan');
func2();The apply() function is useful to convert an array into actual arguments.
> Math.max.apply(null, [13, 7, 30])
30var proto = {
describe: function () {
return 'name: '+this.name;
}
};
var obj = {
[[Prototype]]: proto,
name: 'obj'
};
> obj.describe
[Function]var PersonProto = {
describe: function () {
return 'Person named '+this.name;
}
};
var jane = {
[[Prototype]]: PersonProto,
name: 'Jane'
};
var tarzan = {
[[Prototype]]: PersonProto,
name: 'Tarzan'
};
> jane.describe()
Person named Jane
> tarzan.describe()
Person named Tarzanvar PersonProto = {
describe: function () {
return 'Person named '+this.name;
}
};
var jane = Object.create(PersonProto, {
name: { value: 'Jane', writable: true }
});
> jane.describe()
'Person named Jane'Or, create an empty object then add a property:
var jane = Object.create(PersonProto);
jane.name = 'Jane';> Object.getPrototypeOf(jane) === PersonProto
true> var A = {};
> var B = Object.create(A);
> var C = Object.create(B);
> A.isPrototypeOf(C)
true
> C.isPrototypeOf(A)
falsefunction getDefiningObject(obj, propKey) {
obj = Object(obj); // make sure it’s an object
while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
obj = Object.getPrototypeOf(obj);
// obj is null if we have reached the end
}
return obj;
}Given this inheritance:
var proto = Object.defineProperties({}, {
protoEnumTrue: { value: 1, enumerable: true },
protoEnumFalse: { value: 2, enumerable: false }
});
var obj = Object.create(proto, {
objEnumTrue: { value: 1, enumerable: true },
objEnumFalse: { value: 2, enumerable: false }
});We can loop through the properties like this:
> for (var x in obj) console.log(x);
objEnumTrue
protoEnumTrue