首页 > 代码库 > JavaScript Garden
JavaScript Garden
Objects
Object Usage and Properties
Everything in JavaScript acts like an object, with the only two exceptions being null
and undefined
.
false.toString(); // ‘false‘[1, 2, 3].toString(); // ‘1,2,3‘function Foo(){}Foo.bar = 1;Foo.bar; // 1
A common misconception is that number literals cannot be used as objects. That is because a flaw in JavaScript‘s parser tries to parse the dot notation on a number as a floating point literal.
2.toString(); // raises SyntaxError
There are a couple of workarounds that can be used to make number literals act as objects too.
2..toString(); // the second point is correctly recognized2 .toString(); // note the space left to the dot(2).toString(); // 2 is evaluated first
Objects as a Data Type
Objects in JavaScript can also be used as Hashmaps; they mainly consist of named properties mapping to values.
Using an object literal - {}
notation - it is possible to create a plain object. This new object inherits from Object.prototype
and does not have own properties defined.
var foo = {}; // a new empty object// a new object with a ‘test‘ property with value 12var bar = {test: 12};
Accessing Properties
The properties of an object can be accessed in two ways, via either the dot notation or the square bracket notation.
var foo = {name: ‘kitten‘}foo.name; // kittenfoo[‘name‘]; // kittenvar get = ‘name‘;foo[get]; // kittenfoo.1234; // SyntaxErrorfoo[‘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.
Deleting Properties
The only way to remove a property from an object is to use the delete
operator; setting the property to undefined
or null
only removes the value associated with the property, but not the key.
var obj = { bar: 1, foo: 2, baz: 3};obj.bar = undefined;obj.foo = null;delete obj.baz;for(var i in obj) { if (obj.hasOwnProperty(i)) { console.log(i, ‘‘ + obj[i]); }}
虽然delete操作符运用在对象上可以删除一个对象的属性,但是用在数组上确实不可以的,对于数组,请使用splice
The above outputs both bar undefined
and foo null
- only baz
was removed and is therefore missing from the output.
Notation of Keys
var test = { ‘case‘: ‘I am a keyword, so I must be notated as a string‘, delete: ‘I am a keyword, so me too‘ // raises SyntaxError};
Object properties can be both notated as plain characters and as strings. Due to another mis-design in JavaScript‘s parser, the above will throw a SyntaxError
prior to ECMAScript 5.
This error arises from the fact that delete
is a keyword; therefore, it must be notated as a string literal to ensure that it will be correctly interpreted by older JavaScript engines.
The Prototype
JavaScript does not feature a classical inheritance model; instead, it uses a prototypal one.
While this is often considered to be one of JavaScript‘s weaknesses, the prototypal inheritance model is in fact more powerful than the classic model. It is, for example, fairly trivial to build a classic model on top of a prototypal model, while the other way around is a far more difficult task.
JavaScript is the only widely used language that features prototypal inheritance, so it can take time to adjust to the differences between the two models.
The first major difference is that inheritance in JavaScript uses prototype chains.
function Foo() { this.value = http://www.mamicode.com/42;}Foo.prototype = { method: function() {}};function Bar() {}// Set Bar‘s prototype to a new instance of FooBar.prototype = new Foo();Bar.prototype.foo = ‘Hello World‘;// Make sure to list Bar as the actual constructorBar.prototype.constructor = Bar;var test = new Bar(); // create a new bar instance// The resulting prototype chaintest [instance of Bar] Bar.prototype [instance of Foo] { foo: ‘Hello World‘ } Foo.prototype { method: ... } Object.prototype { toString: ... /* etc. */ }
Note: 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.
Note: Do not use Bar.prototype = Foo
, since it will not point to the prototype of Foo
but rather to the function object Foo
. So the prototype chain will go over Function.prototype
and not Foo.prototype
; therefore,method
will not be on the prototype chain.
In the code above, the object test
will inherit from both Bar.prototype
and Foo.prototype
; hence, it will have access to the function method
that was defined on Foo
. It will also have access to the property value
of the one Foo
instance that is its prototype. It is important to note that new Bar()
does not create a new Foo
instance, but reuses the one assigned to its prototype; thus, all Bar
instances will share the same value
property.
Property Lookup
When accessing the properties of an object, JavaScript will traverse the prototype chain upwards until it finds a property with the requested name.
If it reaches the top of the chain - namely Object.prototype
- and still hasn‘t found the specified property, it will return the value undefined instead.
The Prototype Property
While the prototype property is used by the language to build the prototype chains, it is still possible to assign any given value to it. However, primitives will simply get ignored when assigned as a prototype.
function Foo() {}Foo.prototype = 1; // no effect
Assigning objects, as shown in the example above, will work, and allows for dynamic creation of prototype chains.
Performance
The lookup time for properties that are high up on the prototype chain can have a negative impact on performance, and this may be significant in code where performance is critical. Additionally, trying to access non-existent properties will always traverse the full prototype chain.
Also, when iterating over the properties of an object every property that is on the prototype chain will be enumerated.
Extension of Native Prototypes
One mis-feature that is often used is to extend Object.prototype
or one of the other built in prototypes.
This technique is called monkey patching and breaks encapsulation. While used by popular frameworks such as Prototype, there is still no good reason for cluttering built-in types with additional non-standard functionality.
The only good reason for extending a built-in prototype is to backport the features of newer JavaScript engines; for example, Array.forEach
.
In Conclusion
It is essential to understand the prototypal inheritance model before writing complex code that makes use of it. Also, be aware of the length of the prototype chains in your code and break them up if necessary to avoid possible performance problems. Further, the native prototypes should never be extended unless it is for the sake of compatibility with newer JavaScript features.
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
.
Note: It is not enough to check whether a property is undefined
. The property might very well exist, but its value just happens to be set to undefined
.
hasOwnProperty
is the only thing in JavaScript which deals with properties and does not traverse the prototype chain.
// Poisoning Object.prototypeObject.prototype.bar = 1;var foo = {goo: undefined};foo.bar; // 1‘bar‘ in foo; // truefoo.hasOwnProperty(‘bar‘); // falsefoo.hasOwnProperty(‘goo‘); // true
Only hasOwnProperty
will give the correct and expected result; this is essential when iterating over the properties of any object. There is no other way to exclude properties that are not defined on the object itself, but somewhere on its prototype chain.
hasOwnProperty
as a Property
JavaScript does not protect the property name hasOwnProperty
; thus, if the possibility exists that an object might have a property with this name, it is necessary to use an external hasOwnProperty
to get correct results.
var foo = { hasOwnProperty: function() { return false; }, bar: ‘Here be dragons‘};foo.hasOwnProperty(‘bar‘); // always returns false// Use another Object‘s hasOwnProperty and call it with ‘this‘ set to foo({}).hasOwnProperty.call(foo, ‘bar‘); // true// It‘s also possible to use hasOwnProperty from the Object// prototype for this purposeObject.prototype.hasOwnProperty.call(foo, ‘bar‘); // true
In Conclusion
Using hasOwnProperty
is the only reliable method to check for the existence of a property on an object. It is recommended that hasOwnProperty
is used in every for in
loop to avoid errors from extended native prototypes.
The for in
Loop
Just like the in
operator, the for in
loop traverses the prototype chain when iterating over the properties of an object.
// Poisoning Object.prototypeObject.prototype.bar = 1;var foo = {moo: 2};for(var i in foo) { console.log(i); // prints both bar and moo}
Note: The for in
loop will not iterate over any properties that have their enumerable
attribute set to false
; for example, the length
property of an array.
Since it is not possible to change the behavior of the for in
loop itself, it is necessary to filter out the unwanted properties inside the loop body; this is done using the hasOwnProperty
method of Object.prototype
.
Note: Since for in
always traverses the complete prototype chain, it will get slower with each additional layer of inheritance added to an object.
Using hasOwnProperty
for Filtering
// still the foo from abovefor(var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); }}
This version is the only correct one to use. Due to the use of hasOwnProperty
, it will only print out moo
. When hasOwnProperty
is left out, the code is prone to errors in cases where the native prototypes - e.g. Object.prototype
- have been extended.
One widely used framework that extends Object.prototype
is Prototype. When this framework is included, for in
loops that do not use hasOwnProperty
are guaranteed to break.
In Conclusion
It is recommended to always use hasOwnProperty
. Assumptions should never be made about the environment the code is running in, or whether the native prototypes have been extended or not.
Functions
Function Declarations and Expressions
Functions in JavaScript are first class objects. That means they can be passed around like any other value. One common use of this feature is to pass ananonymous function as a callback to another, possibly an asynchronous function.
The function
Declaration
function foo() {}
The above function gets hoisted before the execution of the program starts; thus, it is available everywhere in the scope it was defined, even if called before the actual definition in the source.
foo(); // Works because foo was created before this code runsfunction foo() {}
The function
Expression
var foo = function() {};
This example assigns the unnamed and anonymous function to the variable foo
.
foo; // ‘undefined‘foo(); // this raises a TypeErrorvar foo = function() {};
Due to the fact that var
is a declaration that hoists the variable name foo
before the actual execution of the code starts, foo
is already declared when the script gets executed.
But since assignments only happen at runtime, the value of foo
will default to undefined before the corresponding code is executed.
Named Function Expression
Another special case is the assignment of named functions.
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
JavaScript has a different concept of what the special name this
refers to than most other programming languages. There are exactly five different ways in which the value of this
can be bound in the language.
The Global Scope
this;
When using this
in global scope, it will simply refer to the global object.
Calling a Function
foo();
ES5 Note: In strict mode, the global case no longer exists.this
will instead have the value of undefined
in that case.
Here, this
will again refer to the global object.
Calling a Method
test.foo();
In this example, this
will refer to test
.
Calling a Constructor
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
.
Explicit Setting of this
function foo(a, b, c) {}var bar = {};foo.apply(bar, [1, 2, 3]); // array will expand to the belowfoo.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
.
Note: this
cannot be used to refer to the object inside of an Object
literal. So var obj = {me: this}
will not result in me
referring to obj
, since this
only gets bound by one of the five listed cases.
Common Pitfalls
While most of these cases make sense, the first can be considered another mis-design of the language because it never has any practical use.
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
, it is necessary to create a local variable inside of method
that refers to Foo
.
Foo.method = function() { var that = this; function test() { // Use that instead of this here } test();}
that
is just a normal variable name, but it is commonly used for the reference to an outer this
. In combination with closures, it can also be used to pass this
values around.
Assigning Methods
Another thing that does not work in JavaScript is function aliasing, which is assigning a method to a variable.
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
.
While the late binding of this
might seem like a bad idea at first, in fact, it is what makes prototypal inheritance work.
function Foo() {}Foo.prototype.method = function() {};function Bar() {}Bar.prototype = Foo.prototype;new Bar().method();
When method
gets called on an instance of Bar
, this
will now refer to that very instance.
Closures and References
One of JavaScript‘s most powerful features is the availability of closures. 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.
Emulating private variables
function Counter(start) { var count = start; return { increment: function() { count++; }, get: function() { return count; } }}var foo = Counter(4);foo.increment();foo.get(); // 5
Here, Counter
returns two closures: the function increment
as well as the function get
. Both of these functions keep a reference to the scope of Counter
and, therefore, always keep access to the count
variable that was defined in that scope.
Why Private Variables Work
Since it is not possible to reference or assign scopes in JavaScript, there is no way of accessing the variable count
from the outside. The only way to interact with it is via the two closures.
var foo = new Counter(4);foo.hack = function() { count = 1337;};
The above code will not change the variable count
in the scope of Counter
, since foo.hack
was not defined in that scope. It will instead create - or override - the global variable count
.
Closures Inside Loops
One often made mistake is to use closures inside of loops, as if they were copying the value of the loop‘s index variable.
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.
The anonymous function keeps a reference to i
. At the time console.log
gets called, the for loop
has already finished, and the value of i
has been set to 10
.
In order to get the desired behavior, it is necessary to create a copy of the value of i
.
Avoiding the Reference Problem
In order to copy the value of the loop‘s index variable, it is best to use an anonymous wrapper.
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); }, 1000); })(i);}
The anonymous outer function gets called immediately with i
as its first argument and will receive a copy of the value of i
as its parameter e
.
The anonymous function that gets passed to setTimeout
now has a reference to e
, whose value does not get changed by the loop.
There is another possible way of achieving this, which is to return a function from the anonymous wrapper that will then have the same behavior as the code above.
for(var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000)}
There‘s yet another way to accomplish this by using .bind
, which can bind a this
context and arguments to function. It behaves identially to the code above
for(var i = 0; i < 10; i++) { setTimeout(console.log.bind(console, i), 1000);}
The arguments
Object
Every function scope in JavaScript can access the special variable arguments
. This variable holds a list of all the arguments that were passed to the function.
Note: In case arguments
has already been defined inside the function‘s scope either via a var
statement or being the name of a formal parameter, the arguments
object will not be created.
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
The code below will return a new Array
containing all the elements of the arguments
object.
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
The following is the recommended way of passing arguments from one function to another.
function foo() { bar.apply(null, arguments);}function bar(a, b, c) { // do stuff here}
Another trick is to use both call
and apply
together to create fast, unbound wrappers.
function Foo() {}Foo.prototype.method = function(a, b, c) { console.log(this, a, b, c);};// Create an unbound version of "method" // It takes the parameters: this, arg1, arg2...argNFoo.method = function() { // Result: Foo.prototype.method.call(this, arg1, arg2... argN) Function.call.apply(Foo.prototype.method, arguments);};
Formal Parameters and Arguments Indices
The arguments
object creates getter and setter functions for both its properties, as well as the function‘s formal parameters.
As a result, changing the value of a formal parameter will also change the value of the corresponding property on the arguments
object, and the other way around.
function foo(a, b, c) { arguments[0] = 2; a; // 2 b = 4; arguments[1]; // 4 var d = c; d = 9; c; // 3}foo(1, 2, 3);
Performance Myths and Truths
The only time the arguments
object is not created is where it is declared as a name inside of a function or one of its formal parameters. It does not matter whether it is used or not.
Both getters and setters are always created; thus, using it has nearly no performance impact at all, especially not in real world code where there is more than a simple access to the arguments
object‘s properties.
ES5 Note: These getters and setters are not created in strict mode.
However, there is one case which will drastically reduce the performance in modern JavaScript engines. That case is the use of arguments.callee
.
function foo() { arguments.callee; // do something with this function object arguments.callee.caller; // and the calling function object}function bigLoop() { for(var i = 0; i < 100000; i++) { foo(); // Would normally be inlined... }}
In the above code, foo
can no longer be a subject to inlining since it needs to know about both itself and its caller. This not only defeats possible performance gains that would arise from inlining, but it also breaks encapsulation because the function may now be dependent on a specific calling context.
Making use of arguments.callee
or any of its properties is highly discouraged.
ES5 Note: In strict mode,arguments.callee
will throw a TypeError
since its use has been deprecated.
Constructors
Constructors in JavaScript are yet again different from many other languages. Any function call that is preceded by the new
keyword acts as a constructor.
Inside the constructor - the called function - the value of this
refers to a newly created object. The prototype of this new object is set to the prototype
of the function object that was invoked as the constructor.
If the function that was called has no explicit return
statement, then it implicitly returns the value of this
- the new object.
function Foo() { this.bla = 1;}Foo.prototype.test = function() { console.log(this.bla);};var test = new Foo();
The above calls Foo
as constructor and sets the prototype
of the newly created object to Foo.prototype
.
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
.
function Bar() { return 2;}new Bar(); // a new objectfunction Test() { this.value = http://www.mamicode.com/2; return { foo: 1 };}new Test(); // the returned object
When the new
keyword is omitted, the function will not return a new object.
function Foo() { this.bla = 1; // gets set on the global object}Foo(); // undefined
While the above example might still appear to work in some cases, due to the workings of this
in JavaScript, it will use the global object as the value of this
.
Factories
In order to be able to omit the new
keyword, the constructor function has to explicitly return a value.
function Bar() { var value = http://www.mamicode.com/1; return { method: function() { return value; } }}Bar.prototype = { foo: function() {}};new Bar();Bar();
Both calls to Bar
return the same thing, a newly create object that has a property called method
, which is a Closure.
It should also be noted that the call new Bar()
does not affect the prototype of the returned object. While the prototype will be set on the newly created object, Bar
never returns that new object.
In the above example, there is no functional difference between using and not using the new
keyword.
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.
function Foo() { var obj = {}; obj.value = ‘blub‘; var private = 2; obj.someMethod = function(value) { this.value =http://www.mamicode.com/ value; } obj.getPrivate = function() { return private; } return obj;}
While the above is robust against a missing new
keyword and certainly makes the use of private variables easier, it comes with some downsides.
- It uses more memory since the created objects do not share the methods on a prototype.
- In order to inherit, the factory needs to copy all the methods from another object or put that object on the prototype of the new object.
- Dropping the prototype chain just because of a left out
new
keyword is contrary to the spirit of the language.
In Conclusion
While omitting the new
keyword might lead to bugs, it is certainly not a reason to drop the use of prototypes altogether. In the end it comes down to which solution is better suited for the needs of the application. It is especially important to choose a specific style of object creation and use it consistently.
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.
function test() { // a scope for(var i = 0; i < 10; i++) { // not a scope // count } console.log(i); // 10}
Note: When not used in an assignment, return statement or as a function argument, the{...}
notation will get interpreted as a block statement and not as an object literal. This, in conjunction with automatic insertion of semicolons, can lead to subtle errors.
There are also no distinct namespaces in JavaScript, which means that everything gets defined in one globally shared namespace.
Each time a variable is referenced, JavaScript will traverse upwards through all the scopes until it finds it. In the case that it reaches the global scope and still has not found the requested name, it will raise a ReferenceError
.
The Bane of Global Variables
// script Afoo = ‘42‘;// script Bvar 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.
Again, that is not at all the same effect: not using var
can have major implications.
// global scopevar foo = 42;function test() { // local scope foo = 21;}test();foo; // 21
Leaving out the var
statement inside the function test
will override the value of foo
. While this might not seem like a big deal at first, having thousands of lines of JavaScript and not using var
will introduce horrible, hard-to-track-down bugs.
// global scopevar items = [/* some list */];for(var i = 0; i < 10; i++) { subLoop();}function subLoop() { // scope of subLoop for(i = 0; i < 10; i++) { // missing var statement // do amazing stuff! }}
The outer loop will terminate after the first call to subLoop
, since subLoop
overwrites the global value of i
. Using a var
for the second for
loop would have easily avoided this error. The var
statement should never be left out unless the desired effect is to affect the outer scope.
Local Variables
The only source for local variables in JavaScript are function parameters and variables declared via the var
statement.
// global scopevar foo = 1;var bar = 2;var i = 2;function test(i) { // local scope of the function test i = 5; var foo = 3; bar = 4;}test(10);
While foo
and i
are local variables inside the scope of the function test
, the assignment of bar
will override the global variable with the same name.
Hoisting
JavaScript hoists declarations. This means that both var
statements and function
declarations will be moved to the top of their enclosing scope.
bar();var bar = function() {};var someValue = http://www.mamicode.com/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.
// var statements got moved herevar bar, someValue; // default to ‘undefined‘// the function declaration got moved up toofunction 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 = http://www.mamicode.com/42; // assignments are not affected by hoistingbar = 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.
Without knowledge of hoisting, one might suspect the code below would raise a ReferenceError
.
// check whether SomeImportantThing has been initializedif (!SomeImportantThing) { var SomeImportantThing = {};}
But of course, this works due to the fact that the var
statement is being moved to the top of the global scope.
var SomeImportantThing;// other code might initialize SomeImportantThing here, or not// make sure it‘s thereif (!SomeImportantThing) { SomeImportantThing = {};}
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:
- In case there is a
var foo
statement in the current scope, use that. - If one of the function parameters is named
foo
, use that. - If the function itself is called
foo
, use that. - Go to the next outer scope, and start with #1 again.
Note: Having a parameter called arguments
will prevent the creation of the default arguments
object.
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.
(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.
( // evaluate the function inside the parenthesesfunction() {}) // 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.
// A few other styles for directly invoking the !function(){}()+function(){}()(function(){}());// and so on...
In Conclusion
It is recommended to always use an anonymous wrapper to encapsulate code in its own namespace. This does not only protect code against name clashes, but it also allows for better modularization of programs.
Additionally, the use of global variables is considered bad practice. Any use of them indicates badly written code that is prone to errors and hard to maintain.
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.
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.
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.
Iteration
In order to achieve the best performance when iterating over arrays, it is best to use the classic for
loop.
var list = [1, 2, 3, 4, 5, ...... 100000000];for(var i = 0, l = list.length; i < l; i++) { console.log(list[i]);}
There is one extra catch in the above example, which is the caching of the length of the array via l = list.length
.
Although the length
property is defined on the array itself, there is still an overhead for doing the lookup on each iteration of the loop. And while recent JavaScript engines may apply optimization in this case, there is no way of telling whether the code will run on one of these newer engines or not.
In fact, leaving out the caching may result in the loop being only half as fast as with the cached length.
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.
var foo = [1, 2, 3, 4, 5, 6];foo.length = 3;foo; // [1, 2, 3]foo.length = 6;foo.push(4);foo; // [1, 2, 3, undefined, undefined, undefined, 4]
Assigning a smaller length truncates the array. Increasing it creates a sparse array.
In Conclusion
For the best performance, it is recommended to always use the plain for
loop and cache the length
property. The use of for in
on an array is a sign of badly written code that is prone to bugs and bad performance.
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]; // 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‘]
In cases when there is only one argument passed to the Array
constructor and when that argument is a Number
, the constructor will return a new sparse array with the length
property set to the value of the argument. It should be noted that only the length
property of the new array will be set this way; the actual indexes of the array will not be initialized.
var arr = new Array(3);arr[1]; // undefined1 in arr; // false, the index was not set
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.
new Array(count + 1).join(stringToRepeat);
In Conclusion
Literals are preferred to the Array constructor. They are shorter, have a clearer syntax, and increase code readability.
JavaScript Garden