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()
8
Each 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()
8
But 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 IIFE
The 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 IIFE
When 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])
30
var 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 Tarzan
var 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)
false
function 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