Javascript: variable assignment in functions
Consider these two blocks:
Block A
obj = {
a: 1,
b: 2,
c: 3,
f: function() {
alert(this.a);
alert(this.b);
alert(this.c);
}
}
Block B
obj = {
a: 1,
b: 2,
c: 3,
f: funct开发者_开发百科ion() {
var a = this.a;
var b = this.b;
var c = this.c;
alert(a);
alert(b);
alert(c);
}
}
Is one way more correct/efficient than the other? Of course, this is a condensed example - in my code there are more variables, and what I am trying to do is to save time by not typing this.varName
every time in the functions by reassigning the variables for the current function's scope. It works, but is it correct?
EDIT: Just to clarify, the variables will be used extensively throughout the function(s). General consensus seems to be that for this, reassignment through local scope is the way to go.
Depends. If you're only going to use the value one time, it doesn't make sense to add the overhead of storing and then retrieving the value. If, on the other hand, you reference the value several times in the scope of the function, it makes sense to only fetch it once.
In
f: function() {
a = this.a;
b = this.b;
c = this.c;
alert(a);
alert(b);
alert(c);
}
not only is the global assignment and lookup less efficient, but you are polluting the global scope because a = this.a
is assigning to the global a
.
EDIT:
Let's assume that this.a
and this.b
cause a getter to fire, and alert(a)
causes a call to the toString
method of the value of a
.
There is an order of operation difference between
var a = this.a, b = this.b;
alert(a); alert(b);
which does (get a, get b, a toString, b toString) and
alert(this.a); alert(this.b);
which does (get a, a toString, get b, b toString) and
There may be a good reason to prefer one order of operations to the other, but efficiency-wise, the second is probably better.
Because of the order of operations difference, you should not rely on a semantics-preserving JavaScript minifier to optimize the first to the second when there is only one use of the member.
It all depends. If you only access the property once in your function, the first is faster. If you access it more than once, it is faster to use modified version the second code chunk.
Changing the second version to declare a
, b
, and c
as local vars of f()
will avoid multiple scans of the scope chain and traversal of this
- again, this is if you need to access those properties multiple times.
Nicholas Zakas mentions this as a way to speed up your JavaScript. From the summary of the video:
Similar to global variables, performance can be improved by creating local variables to hold object properties and array items that are referenced multiple times. Also, keep in mind that deeper object property and array item lookup (e.g., obj.name1.name2.name3) is slower.
The first way if more Efficient relatively, since in the second way, you are making a copy of the variables, and hence one extra statement for all the variables and more memory space will be occupied(both by the code and the variables).
You're missing a few key things in your examples. I'm only going to focus on the f
function, assume the rest of the code is the same:
If you're simply accessing values stored on the object, there's no reason to store a temporary variable, it'll just gunk up the works:
function () {
//use the values as they are
alert( this.a );
alert( this.b );
alert( this.c );
}
However, if you're performing calculations and need to temporarily cache the results for reuse, you should use local variables. Be sure that they don't pollute global scope (the window
object); use var
to make the variable only persist locally.
function () {
var foo;
foo = this.a / this.b + this.c;
alert( this.a * foo );
alert( this.b / foo );
alert( this.c + foo );
}
Edit to add:
There are two different types of variables being referenced. Variables attached to the object (which are accessed with this.varname
or this['varname']
) and variables which exist only within local scope (which are declared with var varname
and accessed with varname
).
Any variable attached to the object is accessible publicly and should be used for revealing data or persistence across function calls. Any variable declared within the function is accessible only within the context of the function, and are therefor private to the function. They do not retain values across calls, however they can be used to retain data across calls to sub-functions.
Between Block A and Block B, Block A is the preferred method for interacting with an object's data, however in most cases a function performs a larger series of operations that often involve more complex behaviors. If the function contained a callback that needed the values of this.a
, this.b
and this.c
, aliases would need to be used to pass the data, as this
would change between contexts.
This is not going to alert 1
, 2
and 3
as might be expected
f:function ()
{
$(foo).click(function g(){
//`this` does not refer to the object `f` belongs to, but the element being clicked on
//and therefor is not likely to work as expected
alert( this.a );
alert( this.b );
alert( this.c );
});
}
This version will:
f:function()
{
var a,b,c;
a = this.a;
b = this.b;
c = this.c;
$(foo).click(function g(){
alert( a );
alert( b );
alert( c );
});
}
If you can change the object structure slightly, try putting the properties you need in a container (z) like so:
obj = {
z: {
a: 1,
b: 2,
c: 3
},
f: function() {
var z = this.z;
for(var prop in z) {
alert(z[prop]);
}
}
}
1) You should user var
to declare the variables, otherwise they will be globals.
2) Yes, copying the value can save you time in typing AND will perform faster, since accessing object properties is not a really cheap operation. JavaScript doesn't implement arrays; they're always hashes.
var obj = {
a: 1,
b: 2,
c: 3,
f: function () {
for (var property in this) {
if (property != "f")
alert(property + "=" + this[property]);
}
}
};
obj.f();
I am expecting that you meant to place var in front of each of your variable declarations in Block B. Otherwise you are doing something that you probably didn't intend. Instead of setting a the value to a variable exclusive to the anonymous function you are assigning to the property f you would be setting the value on the properties a,b,c on the global object.
I expect that you mean to place var in front of each variable a,b and c in the anonymous function.
According to the book High Performance Javascript written by Nicholas Zakas, he mentions a cost involved with identifier resolution(looking up the variable name you are using). He states that local variables are the least expensive while Global variables are the most expensive.
So in your example (Block B) given without the var, what you are doing is expensive. If you have var in place, what you are doing is optimal for identifier look up.
If you are interested in performance of JS, I suggest you buy Nicholas' book.
If Donald Knuth, who said "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil", were dead, he'd be turning over in his grave. For myself, I'm starting to feel like this.
Pay attention, boys and girls. Efficiency does not matter! It just doesn't!
Let's forget about outright insanity like @Pheonix -- it's spelled "Phoenix" BTW! -- actually worrying about the space taken up by local variables, which are reclaimed as soon as the function ends for crying out loud and just focus on the original question. Consider the following bit of code:
$(function() {
var b;
var d = { b : 1 }
var n = 100000;
var now = function() {
return new Date().getTime();
};
var doTime = function(f) {
var t = now();
f();
return now() - t;
};
var tm1 = doTime(function() {
for (var i=0; i<n; i++) {
b = 1;
}
});
var tm2 = doTime(function() {
for (var i=0; i<n; i++) {
b = d.b;
}
});
$('body').empty().html("<table><tr><tr><td>Time without</td><td>"
+ tm1 + "ms</td><tr>"
+ "<tr><tr><td>Time with</td><td>"+ tm2 + "ms</td><tr>"
+ "<tr><tr><td>Total diff</td><td>"+ (tm2 - tm1) + "ms</td><tr>"
+ "<tr><tr><td>Avg. diff</td><td>"+ ((1000000.0 * (tm2 - tm1)) / n)
+ "ns</td><tr>"
+ "</table");
});
On my pathetic five-year-old laptop, it says that a object dereference like that takes 90 nanoseconds. That means, you can do it eleven million times a second. Are you going to do it eleven million times? If not, don't spend one second worrying about it.
Famously, it's easier to optimize correct code than to correct optimized code. Write your code well, then benchmark it. If it's too slow, identify the bottlenecks (which I guarantee you will not be failure to stash the results of object dereferences in local values), and eliminate them.
We now return you to your regularly scheduled programming.
As is. No. Since you are not using var
to declare your variables you are declaring these variables in the global scope and not the local scope.
If you repeatedly access a variable then it is definitely worthwhile caching a copy in the local scope. This is because functions and objects have a chain of scopes to search through if the variable isn't available in the immediate scope. In the following example, the innermost function must first search in its local scope, then the scope of its parent function, and then finally in the outermost function's scope to find i
. If i
wasn't present in this scope then it would have to search the global scope as well.
(function () {
var i = 10;
(function () {
(function() {
console.log(i); // i is 10
})();
})();
})();
The problem with caching variables in the local scope is that sometimes code can become hard to read, and in pretty much any situation readability trumps efficiency (a computer is much better at dealing with inefficient code than a human is with reading code that is poorly written).
Declare your variables at the top of your function
If you're really concerned about efficiency/correctness then declare all your variables at the beginning of the function. This allows javascript to be very efficient when creating the the local scope of the function. Declarations deep inside a function are inefficient as javascript must first check there is available space in the local scope, and resize the scope if needed. Be warned, even if you don't declare your variables at the top of a function, some compilers will do it for you as it is part of the ECMA spec -- which can result in some gotchas). source
eg.
var i = 1;
function test() {
console.log(i); // undefined
var i = 10;
}
test();
Essentially the javascript engine has compiled your code as:
var i = 1;
function test() {
var i;
console.log(i); // undefined
i = 10;
}
test();
Fixed version of your code (if you were to do further operations with a, b or c).
var obj = {
a: 1,
b: 2,
c: 3,
f: function() {
var a, b, c;
a = this.a;
b = this.b;
c = this.c;
alert(a);
alert(b);
alert(c);
}
}
精彩评论