JavaSrcipt Garden

#Objects

###Object Usage and Properties
Everything in JavaScript acts like an object, with the only two exceptions being null and undefined.

####Accessing Properties

The properties of an object can be accessed in two ways, via either the dot notation or the square bracket notation.

1
2
3
4
5
6
7
8
9
var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten
var get = 'name';
foo[get]; // kitten
foo.1234; // SyntaxError
foo['1234']; // works

The notations work almost identically, with the only difference being that the square bracket notation allows for dynamic setting of properties and the use of property names that would otherwise lead to a syntax error.

###The Prototype

Simply using Bar.prototype = Foo.prototype will result in both objects sharing the same prototype. Therefore, changes to either object's prototype will affect the prototype of the other as well, which in most cases is not the desired effect.

###hasOwnProperty

To check whether an object has a property defined on itself and not somewhere on its prototype chain, it is necessary to use the hasOwnProperty method which all objects inherit from Object.prototype.

hasOwnProperty is the only thing in JavaScript which deals with properties and does not traverse the prototype chain.

1
2
3
4
5
6
7
8
9
// Poisoning Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

Using hasOwnProperty is the only reliable method to check for the existence of a property on an object. It is recommended that hasOwnProperty be used in many cases when iterating over object properties as described in the section on for in loops.

#Functions

###Function Declarations and Expressions

####Named Function Expression

1
2
3
4
var foo = function bar() {
bar(); // Works
}
bar(); // ReferenceError

Here, bar is not available in the outer scope, since the function only gets assigned to foo; however, inside of bar, it is available. This is due to how name resolution in JavaScript works, the name of the function is always made available in the local scope of the function itself.

###How this Works

There are exactly five different ways in which the value of this can be bound in the language.

ES5 Note: In strict mode, the global case no longer exists. this will instead have the value of undefined in that case.

####1. The Global Scope

1
this;

When using this in global scope, it will simply refer to the global object.

####2. Calling a Function

1
foo();

Here, this will again refer to the global object.

####3. Calling a Method

1
test.foo();

In this example, this will refer to test.

####4. Calling a Constructor

1
new foo();

A function call that is preceded by the new keyword acts as a constructor. Inside the function, this will refer to a newly created Object.

####5. Explicit Setting of this

1
2
3
4
5
function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]); // array will expand to the below
foo.call(bar, 1, 2, 3); // results in a = 1, b = 2, c = 3

When using the call or apply methods of Function.prototype, the value of this inside the called function gets explicitly set to the first argument of the corresponding function call.

As a result, in the above example the method case does not apply, and this inside of foo will be set to bar.

Common Pitfalls

1
2
3
4
5
6
Foo.method = function() {
function test() {
// this is set to the global object
}
test();
}

A common misconception is that this inside of test refers to Foo; while in fact, it does not.

In order to gain access to Foo from within test, you can create a local variable inside of method that refers to Foo.

1
2
3
4
5
6
7
Foo.method = function() {
var self = this;
function test() {
// Use self instead of this here
}
test();
}

As of ECMAScript 5 you can use the bind method combined with an anonymous function to achieve the same result.

1
2
3
4
5
6
Foo.method = function() {
var test = function() {
// this now refers to Foo
}.bind(this);
test();
}

###Assigning Methods
Another thing that does not work in JavaScript is function aliasing, which is assigning a method to a variable.

1
2
var test = someObject.methodTest;
test();

Due to the first case, test now acts like a plain function call; therefore, this inside it will no longer refer to someObject.

###Closures and References
With closures, scopes always keep access to the outer scope, in which they were defined. Since the only scoping that JavaScript has is function scope, all functions, by default, act as closures.

####Closures Inside Loops

1
2
3
4
5
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

The above will not output the numbers 0 through 9, but will simply print the number 10 ten times.

####Avoiding the Reference Problem

In order to copy the value of the loop's index variable, it is best to use an anonymous wrapper.

1
2
3
4
5
6
7
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
1
2
3
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}

###The arguments Object
The arguments object is not an Array. While it has some of the semantics of an array - namely the length property - it does not inherit from Array.prototype and is in fact an Object.

Due to this, it is not possible to use standard array methods like push, pop or slice on arguments. While iteration with a plain for loop works just fine, it is necessary to convert it to a real Array in order to use the standard Array methods on it.

####Converting to an Array

1
Array.prototype.slice.call(arguments);

Because this conversion is slow, it is not recommended to use it in performance-critical sections of code.

#####Passing Arguments

1
2
3
4
5
6
function foo() {
bar.apply(null, arguments);
}
function bar(a, b, c) {
// do stuff here
}

###Constructors
Any function call that is preceded by the new keyword acts as a constructor.

If the function that was called has no explicit return statement, then it implicitly returns the value of this - the new object.

In case of an explicit return statement, the function returns the value specified by that statement, but only if the return value is an Object.

1
2
3
4
5
6
7
8
9
10
11
12
13
function Car() {
return 'ford';
}
new Car(); // a new object, not 'ford'
function Person() {
this.someValue = 2;
return {
name: 'Charles'
};
}
new Person(); // the returned object ({name:'Charles'}), not including someValue

When the new keyword is omitted, the function will not return a new object.

1
2
3
4
function Pirate() {
this.hasEyePatch = true; // gets set on the global object!
}
var somePirate = Pirate(); // somePirate is undefined

####Creating New Objects via Factories

It is often recommended to not use new because forgetting its use may lead to bugs.

In order to create a new object, one should rather use a factory and construct a new object inside of that factory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function CarFactory() {
var car = {};
car.owner = 'nobody';
var milesPerGallon = 2;
car.setOwner = function(newOwner) {
this.owner = newOwner;
}
car.getMPG = function() {
return milesPerGallon;
}
return car;
}

###Scopes and Namespaces
Although JavaScript deals fine with the syntax of two matching curly braces for blocks, it does not support block scope; hence, all that is left in the language is function scope.

####The Bane of Global Variables

1
2
3
4
5
// script A
foo = '42';
// script B
var foo = '42'

The above two scripts do not have the same effect. Script A defines a variable called foo in the global scope, and script B defines a foo in the current scope.

####Local Variables
The only source for local variables in JavaScript are function parameters and variables declared via the var statement.

####Hoisting
JavaScript hoists declarations. This means that both var statements and function declarations will be moved to the top of their enclosing scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}

The above code gets transformed before execution starts. JavaScript moves the var statements, as well as function declarations, to the top of the nearest surrounding scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// var statements got moved here
var bar, someValue; // default to 'undefined'
// the function declaration got moved up too
function test(data) {
var goo, i, e; // missing block scope moves these here
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // fails with a TypeError since bar is still 'undefined'
someValue = 42; // assignments are not affected by hoisting
bar = function() {};
test();

Missing block scoping will not only move var statements out of loops and their bodies, it will also make the results of certain if constructs non-intuitive.

In the original code, although the if statement seemed to modify the global variable goo, it actually modifies the local variable - after hoisting has been applied.

####Name Resolution Order!!!

All scopes in JavaScript, including the global scope, have the special name this, defined in them, which refers to the current object.

Function scopes also have the name arguments, defined in them, which contains the arguments that were passed to the function.

For example, when trying to access a variable named foo inside the scope of a function, JavaScript will look up the name in the following order:

  1. In case there is a var foo statement in the current scope, use that.
  2. If one of the function parameters is named foo, use that.
  3. If the function itself is called foo, use that.
  4. Go to the next outer scope, and start with #1 again.

Namespaces

A common problem associated with having only one global namespace is the likelihood of running into problems where variable names clash. In JavaScript, this problem can easily be avoided with the help of anonymous wrappers.

1
2
3
4
5
6
7
8
(function() {
// a self contained "namespace"
window.foo = function() {
// an exposed closure
};
})(); // execute the function immediately

Unnamed functions are considered expressions; so in order to be callable, they must first be evaluated.

1
2
3
4
( // evaluate the function inside the parentheses
function() {}
) // and return the function object
() // call the result of the evaluation

There are other ways to evaluate and directly call the function expression which, while different in syntax, behave the same way.

1
2
3
4
5
// A few other styles for directly invoking the
!function(){}()
+function(){}()
(function(){}());
// and so on...

#Arrays

###Array Iteration and Properties

Although arrays in JavaScript are objects, there are no good reasons to use the for in loop. In fact, there are a number of good reasons against the use of for in on arrays.

Because the for in loop enumerates all the properties that are on the prototype chain and because the only way to exclude those properties is to use hasOwnProperty, it is already up to twenty times slower than a normal for loop.

Note: JavaScript arrays are not associative arrays. JavaScript only has objects for mapping keys to values. And while associative arrays preserve order, objects do not.

Iteration

In order to achieve the best performance when iterating over arrays, it is best to use the classic for loop.

for loop is better than for in loop in perfomance.

The length Property

While the getter of the length property simply returns the number of elements that are contained in the array, the setter can be used to truncate the array.

1
2
3
4
5
6
7
var arr = [1, 2, 3, 4, 5, 6];
arr.length = 3;
arr; // [1, 2, 3]
arr.length = 6;
arr.push(4);
arr; // [1, 2, 3, undefined, undefined, undefined, 4]

The Array Constructor

Since the Array constructor is ambiguous in how it deals with its parameters, it is highly recommended to use the array literal - [] notation - when creating new arrays.

1
2
3
4
5
6
[1, 2, 3]; // Result: [1, 2, 3]
new Array(1, 2, 3); // Result: [1, 2, 3]
[3]; // Result: [3]
new Array(3); // Result: []
new Array('3') // Result: ['3']

Being able to set the length of the array in advance is only useful in a few cases, like repeating a string, in which it avoids the use of a loop.

1
new Array(count + 1).join(stringToRepeat);

#Types

Equality and Comparisons

JavaScript has two different ways of comparing the values of objects for equality.

The Equality Operator

The equality operator consists of two equal signs: ==

JavaScript features weak typing. This means that the equality operator coerces types in order to compare them.

1
2
3
4
5
6
7
8
9
"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true

The above table shows the results of the type coercion, and it is the main reason why the use of == is widely regarded as bad practice. It introduces hard-to-track-down bugs due to its complicated conversion rules.

Additionally, there is also a performance impact when type coercion is in play; for example, a string has to be converted to a number before it can be compared to another number.

The Strict Equality Operator

The strict equality operator consists of three equal signs: ===.

It works like the normal equality operator, except that strict equality operator does not perform type coercion between its operands.

1
2
3
4
5
6
7
8
9
"" === "0" // false
0 === "" // false
0 === "0" // false
false === "false" // false
false === "0" // false
false === undefined // false
false === null // false
null === undefined // false
" \t\r\n" === 0 // false

Comparing Objects

While both == and === are called equality operators, they behave differently when at least one of their operands is an Object.

The typeof Operator

The typeof operator (together with instanceof) is probably the biggest design flaw of JavaScript, as it is almost completely broken.

Although instanceof still has limited uses, typeof really has only one practical use case, which does not happen to be checking the type of an object.

The JavaScript Type Table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Value Class Type
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object

In the above table, Type refers to the value that the typeof operator returns. As can be clearly seen, this value is anything but consistent.

The Class refers to the value of the internal [[Class]] property of an object.

The Class of an Object

The only way to determine an object's [[Class]] value is using Object.prototype.toString. It returns a string in the following format: '[object ' + valueOfClass + ']', e.g [object String] or [object Array]:

1
2
3
4
5
6
7
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true

Testing for Undefined Variables

1
typeof foo !== 'undefined'

The above will check whether foo was actually declared or not; just referencing it would result in a ReferenceError. This is the only thing typeof is actually useful for.

In Conclusion

In order to check the type of an object, it is highly recommended to use Object.prototype.toString because this is the only reliable way of doing so. As shown in the above type table, some return values of typeof are not defined in the specification; thus, they can differ between implementations.

###The instanceof Operator
The instanceof operator compares the constructors of its two operands. It is only useful when comparing custom made objects. Used on built-in types, it is nearly as useless as the typeof operator.

Comparing Custom Objects

1
2
3
4
5
6
7
8
9
10
11
function Foo() {}
function Bar() {}
Bar.prototype = new Foo();
new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true
// This just sets Bar.prototype to the function object Foo,
// but not to an actual instance of Foo
Bar.prototype = Foo;
new Bar() instanceof Foo; // false

Using instanceof with Native Types

1
2
3
4
5
new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true
'foo' instanceof String; // false
'foo' instanceof Object; // false

Using instanceof with Native Types

1
2
3
4
5
new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true
'foo' instanceof String; // false
'foo' instanceof Object; // false

One important thing to note here is that instanceof does not work on objects that originate from different JavaScript contexts (e.g. different documents in a web browser), since their constructors will not be the exact same object.

In Conclusion

The instanceof operator should only be used when dealing with custom made objects that originate from the same JavaScript context. Just like the typeof operator, every other use of it should be avoided.

Type Casting

JavaScript is a weakly typed language, so it will apply type coercion wherever possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// These are true
new Number(10) == 10; // Number object is converted
// to a number primitive via implicit call of
// Number.prototype.valueOf method
10 == '10'; // Strings gets converted to Number
10 == '+10 '; // More string madness
10 == '010'; // And more
isNaN(null) == false; // null converts to 0
// which of course is not NaN
// These are false
10 == 010;
10 == '-10';

Constructors of Built-In Types

The constructors of the built in types like Number and String behave differently when being used with the new keyword and without it.

1
2
3
new Number(10) === 10; // False, Object and Number
Number(10) === 10; // True, Number and Number
new Number(10) + 0 === 10; // True, due to implicit conversion

Casting to a String

1
'' + 10 === '10'; // true

Casting to a Number

1
+'10' === 10; // true

Casting to a Boolean

By using the not operator twice, a value can be converted to a boolean.

1
2
3
4
5
6
7
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true

#Core

Why Not to Use eval

The eval function will execute a string of JavaScript code in the local scope.

eval()的参数是一个字符串。如果字符串表示了一个表达式,eval()会对表达式求值。如果参数表示了一个或多个JavaScript声明, 那么eval()会执行声明。不要调用eval()来为算数表达式求值; JavaScript 会自动为算数表达式求值。

如果要将算数表达式构造成为一个字符串,你可以用eval()在随后对其求值。比如,你有一个变量 x ,你可以通过一个字符串表达式来对涉及x的表达式延迟求值,将 "3 * x + 2",当作变量,通过在脚本中调用eval(),随后求值。

如果参数不是字符串,eval()将会将参数原封不动的返回。

eval should never be used. Any code that makes use of it should be questioned in its workings, performance and security. If something requires eval in order to work, it should not be used in the first place. A better design should be used, that does not require the use of eval.

undefined and null

JavaScript has two distinct values for nothing, null and undefined, with the latter being more useful.

The Value undefined

undefined is a type with exactly one value: undefined.

The language also defines a global variable that has the value of undefined; this variable is also called undefined. However, this variable is neither a constant nor a keyword of the language. This means that its value can be easily overwritten.

Here are some examples of when the value undefined is returned:

  • Accessing the (unmodified) global variable undefined.
  • Accessing a declared but not yet initialized variable.
  • Implicit returns of functions due to missing return statements.
  • return statements that do not explicitly return anything.
  • Lookups of non-existent properties.
  • Function parameters that do not have any explicit value passed.
  • Anything that has been set to the value of undefined.
  • Any expression in the form of void(expression)

Uses of null

While undefined in the context of the JavaScript language is mostly used in the sense of a traditional null, the actual null (both a literal and a type) is more or less just another data type.

It is used in some JavaScript internals (like declaring the end of the prototype chain by setting Foo.prototype = null), but in almost all cases, it can be replaced by undefined.

The delete Operator

In short, it's impossible to delete global variables, functions and some other stuff in JavaScript which have a DontDelete attribute set.

Global code and Function code

When a variable or a function is defined in a global or a function scope it is a property of either the Activation object or the Global object. Such properties have a set of attributes, one of which is DontDelete. Variable and function declarations in global and function code always create properties with DontDelete, and therefore cannot be deleted.

Function arguments and built-ins

Functions' normal arguments, arguments objects and built-in properties also have DontDelete set.