I studied JavaScript performance. I learned that when accessing more than once, it is best to copy the closure variables and class members to the local scope in order to speed things up. For example:
var i = 100; var doSomething = function () { var localI = i;
I understand it; it adds a shortcut to the interpreter, which searches for the value by name. This concept becomes very obscure when considering methods. At first I thought it would work the same way. For example:
var obj = { fn: function () {
However, this will not work. Access to a similar method removes it from the scope. When it reaches the line this.value, this refers to the window object, and this.value is likely to be undefined. Instead of directly calling objFn and losing the area, I could pass its volume back to it with objFn.call (obj), but does it do better or worse than the original obj.fn ()?
I decided to write a script to test this, and I got very confusing results. This script iterates through several tests that repeatedly scrolls the above function. The average time spent on each test is displayed on the body.
An object is created using many simple methods. Additional methods are there to determine if the interpreter should work much harder to find a specific method.
Test 1 just calls this.a (); Test 2 creates a local variable a = this.a then calls a.call (this); Test 3 creates a local variable using the YUI binding function to preserve the area. I commented on this. Additional function calls created by YUI make this slower.
Tests 4, 5, and 6 are copies of 1, 2, 3, except for using z instead.
The YUI later function is used to prevent runaway script errors. Timing is done in real testing methods, so setTimeouts should not affect the results. Each function is called 10,000,000 times. (Easy to configure if you want to run tests.)
Here is my entire XHTML document that I used for testing.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr"> <head> <script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script> <script> YUI().use('node', function (Y) { var o = { value: '', a: function () { this.value += 'a'; }, b: function () { this.value += 'b'; }, c: function () { this.value += 'c'; }, d: function () { this.value += 'd'; }, e: function () { this.value += 'e'; }, f: function () { this.value += 'f'; }, g: function () { this.value += 'g'; }, h: function () { this.value += 'h'; }, i: function () { this.value += 'i'; }, j: function () { this.value += 'j'; }, k: function () { this.value += 'k'; }, l: function () { this.value += 'l'; }, m: function () { this.value += 'm'; }, n: function () { this.value += 'n'; }, o: function () { this.value += 'o'; }, p: function () { this.value += 'p'; }, q: function () { this.value += 'q'; }, r: function () { this.value += 'r'; }, s: function () { this.value += 's'; }, t: function () { this.value += 't'; }, u: function () { this.value += 'u'; }, v: function () { this.value += 'v'; }, w: function () { this.value += 'w'; }, x: function () { this.value += 'x'; }, y: function () { this.value += 'y'; }, z: function () { this.value += 'z'; }, reset: function () { this.value = ''; }, test1: function (length) { var time = new Date().getTime(); while ((length -= 1)) { this.a(); } return new Date().getTime() - time; }, test2: function (length) { var a = this.a, time = new Date().getTime(); while ((length -= 1)) { a.call(this); } return new Date().getTime() - time; }, test3: function (length) { var a = Y.bind(this.a, this), time = new Date().getTime(); while ((length -= 1)) { a(); } return new Date().getTime() - time; }, test4: function (length) { var time = new Date().getTime(); while ((length -= 1)) { this.z(); } return new Date().getTime() - time; }, test5: function (length) { var z = this.z, time = new Date().getTime(); while ((length -= 1)) { z.call(this); } return new Date().getTime() - time; }, test6: function (length) { var z = Y.bind(this.z, this), time = new Date().getTime(); while ((length -= 1)) { z(); } return new Date().getTime() - time; } }, iterations = 100, iteration = iterations, length = 100000, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body'); body.set('innerHTML', '<span>Running ' + iterations + ' Iterations…</span>'); while ((iteration -= 1)) { Y.later(1, null, function (iteration) { Y.later(1, null, function () { o.reset(); t1 += o.test1(length); }); Y.later(1, null, function () { o.reset(); t2 += o.test2(length); }); Y.later(1, null, function () { o.reset(); t4 += o.test4(length); }); Y.later(1, null, function () { o.reset(); t5 += o.test5(length); }); if (iteration === 1) { Y.later(10, null, function () { t1 /= iterations; t2 /= iterations; </script> </head> <body> </body> </html>
I ran this using Windows 7 in three different browsers. These results are in milliseconds.
Firefox 3.6.8
Test 1: this.a(); 9.23 Test 2: a.call(this); 9.67 Test 4: this.z(); 9.2 Test 5: z.call(this); 9.61
Chrome 7.0.503.0
Test 1: this.a(); 5.25 Test 2: a.call(this); 4.66 Test 4: this.z(); 3.71 Test 5: z.call(this); 4.15
Internet Explorer 8
Test 1: this.a(); 168.2 Test 2: a.call(this); 197.94 Test 4: this.z(); 169.6 Test 5: z.call(this); 199.02
Firefox and Internet Explorer yielded results on how I expected. Test 1 and Test 4 are relatively close, Test 2 and Test 5 are relatively close, and Test 2 and Test 5 take longer than Test 1 and Test 4 because there is an additional function call for processing.
I don’t understand Chrome at all, but it’s much faster, maybe tuning the submillisecond performance is not needed.
Does anyone have a good explanation for the results? What is the best way to call JavaScript methods multiple times?