What causes a decrease in speed when retrieving a variable through window.variable?

Many resources providing JavaScript performance tips emphasize the importance of reducing "scope chain lookup" for better efficiency. One common suggestion is to use Immediately Invoked Function Expressions (IIFEs) which are said to have an added advantage of minimizing scope chain lookup when accessing global variables. This concept seemed logical and unquestionable, leading me to adopt IIFEs happily to prevent polluting the global namespace and potentially gain a performance boost over global code.

Our expectation today:

(function($, window, undefined) {
    // it appears that variable access here is quicker than outside the IIFE
})(jQuery, window);

To simplify or extend this idea to a broader scenario, one would anticipate:

var x = 0;
(function(window) {
    // it should be faster to access window.x here
})(window);

Based on my understanding of JavaScript, there should be no difference between x = 1; and window.x = 1; in the global scope. Therefore, one might assume they would perform similarly, right? WRONG. Through some testing, I discovered significant differences in access times.

Perhaps placing window.x = 1; inside an IIFE could make it even faster (even if only slightly), correct? WRONG again.

Could it be due to Firefox? Let's try Chrome instead (known for its fast V8 engine in JS benchmarking). It should outperform Firefox when it comes to simple tasks like directly accessing a global variable, right? WRONG yet again.

I decided to investigate which method of access is truly the fastest in each browser. Let's start with one line of code: var x = 0;. Once x is declared and attached to window, which of these methods would be the speediest, and why?

  1. Directly in the global scope

    x = x + 1;
    
  2. Directly in global scope, but prefixed with window

    window.x = window.x + 1;
    
  3. Inside a function without qualification

    function accessUnqualified() {
        x = x + 1;
    }
    
  4. Inside a function with window prefix

    function accessWindowPrefix() {
        window.x = window.x + 1;
    }
    
  5. Inside a function, caching window as a variable for prefixed access (simulating local param of an IIFE)

    function accessCacheWindow() {
        var global = window;
        global.x = global.x + 1;
    }
    
  6. Inside an IIFE (window as parameter) with prefixed access

    (function(global){
         global.x = global.x + 1;
     })(window);
    
  7. Inside an IIFE (window as parameter) without qualification

    (function(global){
         x = x + 1;
     })(window);
    

Please consider the browser context, where window represents the global variable.

To test this, I conducted a quick comparison by looping the increment operation a million times. Here are the results I obtained:

                             Firefox          Chrome
                             -------          ------
1. Direct access             848ms            1757ms
2. Direct window.x           2352ms           2377ms
3. In function, x            338ms            3ms
4. In function, window.x     1752ms           835ms
5. Simulated IIFE global.x   786ms            10ms
6. IIFE, global.x            791ms            11ms
7. IIFE, x                   331ms            655ms

After repeating the tests multiple times, the numbers seem consistent although somewhat perplexing. They indicate:

  • Using window prefix is considerably slower (#2 vs #1, #4 vs #3). But WHY?
  • Accessing a global variable within a function (allegedly involving extra scope lookup) is actually faster (#3 vs #1). WHY??
  • Why do the results for #5, #6, #7 differ so much between the two browsers?

While some may argue that such tests are irrelevant for performance optimization, I believe they can enhance our understanding of fundamental concepts like variable access and scope chain. So please indulge me and help clarify these queries for the sake of knowledge.

If you've reached this point, thank you for your patience. I apologize for the lengthy post and for potentially combining several questions into one - as I believe they are all interconnected.


Edit: Here is the benchmark code I used as requested.

var x, startTime, endTime, time;

// Test #1: x
x = 0;
startTime = Date.now();
for (var i=0; i<1000000; i++) {
   x = x + 1;
}
endTime = Date.now();
time = endTime - startTime;
console.log('access x directly    - Completed in ' + time + 'ms');

// Test #2: window.x
x = 0;
startTime = Date.now();
for (var i=0; i<1000000; i++) {
  window.x = window.x + 1;
}
endTime = Date.now();
time = endTime - startTime;
console.log('access window.x     - Completed in ' + time + 'ms');

// Test #3: inside function, x
x =0;
startTime = Date.now();
accessUnqualified();
endTime = Date.now();
time = endTime - startTime;
console.log('accessUnqualified() - Completed in ' + time + 'ms');

// Test #4: inside function, window.x
x =0;
startTime = Date.now();
accessWindowPrefix();
endTime = Date.now();
time = endTime - startTime;
console.log('accessWindowPrefix()- Completed in ' + time + 'ms');

// Test #5: function cache window (simulate IIFE), global.x
x =0;
startTime = Date.now();
accessCacheWindow();
endTime = Date.now();
time = endTime - startTime;
console.log('accessCacheWindow() - Completed in ' + time + 'ms');

// Test #6: IIFE, window.x
x = 0;
startTime = Date.now();
(function(window){
  for (var i=0; i<1000000; i++) {
    window.x = window.x+1;
  }
})(window);
endTime = Date.now();
time = endTime - startTime;
console.log('access IIFE window  - Completed in ' + time + 'ms');

// Test #7: IIFE x
x = 0;
startTime = Date.now();
(function(global){
  for (var i=0; i<1000000; i++) {
    x = x+1;
  }
})(window);
endTime = Date.now();
time = endTime - startTime;
console.log('access IIFE x      - Completed in ' + time + 'ms');


function accessUnqualified() {
  for (var i=0; i<1000000; i++) {
    x = x+1;
  }
}

function accessWindowPrefix() {
  for (var i=0; i<1000000; i++) {
    window.x = window.x+1;
  }
}

function accessCacheWindow() {
  var global = window;
  for (var i=0; i<1000000; i++) {
    global.x = global.x+1;
  }
}

Answer №1

Optimizing with Javascript can be challenging due to the presence of eval, which has access to the local frame.

However, if compilers are intelligent enough to recognize that eval is not a factor, performance improvements can be significant.

Assuming only local variables, captured variables, and global variables are in play, and there is no interference with eval, theoretically:

  • Accessing a local variable involves direct memory access with an offset from the local frame
  • Accessing a global variable is a straightforward operation
  • Accessing a captured variable requires double indirection

This means that when looking up a variable like x, it will always be either local or global, allowing for direct access without any lookup needed at runtime.

For captured variables, additional complexity arises due to lifetime considerations. In a typical implementation where captured variables are stored in cells and cell addresses are copied when closures are created, two extra indirection steps are necessary.

mov rax, [rbp]      ; Load closure data address into rax
mov rax, [rax+0x12] ; Load cell address into rax
mov rax, [rax]      ; Load actual value of captured var into rax

The differences in timing observed are influenced by various factors beyond just variable access, such as caching mechanisms and implementation specifics like garbage collector behavior.

It's worth noting that accessing a global variable using the window object can introduce additional overhead, especially considering that window is designed to function as a regular object as well.

Answer №2

Testing micro-optimizations has become increasingly challenging nowadays due to the optimization process carried out by JavaScript engines' JIT compilers. It's possible that some of the tests yielding incredibly fast results are influenced by the compiler removing "unused" code and optimizing loops.

There are two main concerns when it comes to this issue: "scope chain lookup" and code that hinders the JIT compiler from effectively compiling or simplifying the code. (The latter is quite intricate, so delving into some expert tips would be beneficial.)

The challenge with the scope chain lies in how the JS engine handles variables like x, searching through different scopes such as:

  • local scope
  • closure scopes (e.g., those created by IIFE)
  • global scope

The "scope chain" functions as a linked list of these scopes. When looking up x, the engine first checks if it's a local variable. If not, it traverses up any closure scopes before ultimately checking the global context if needed.

In the given code snippet, console.log(a); initially seeks to find a within the local scope of innerFunc(). Upon not locating a local variable a, it progresses to search within its enclosing closure without success as well. In scenarios where additional nested callbacks introduce more closures, each one must be examined. Only after exhausting these options does the engine resort to verifying a within the global scope, where it does discover the variable.

var a = 1; // global scope
(function myIife(window) {
    var b = 2; // scope in myIife with closure implications due to reference within innerFunc
    function innerFunc() {
        var c = 3;
        console.log(a);
        console.log(b);
        console.log(c);
    }
    // invoking innerFunc
    innerFunc();
})(window);

Answer №3

Running the code snippet in Chrome shows that every alternative has minimal execution time, except for directly accessing window.x. The use of object properties is slower compared to using variables, raising questions about why window.x is even slower than x and other alternatives.

The assumption that x = 1; is equivalent to window.x = 1; is incorrect. It's important to note that window is not solely the global object; it acts as both a property and a reference to it, as demonstrated by window.window.window....

Exploring Environment Records

Each variable must be "registered" in an environment record, with two primary types: Declarative and object.

Declarative environment records are used in function scope, while global scope utilizes an object environment record. This means every variable in the global scope is also a property of the global object.

It's worth noting that although objects can be accessed through identifiers with matching names, they may not necessarily represent variables. The use of the with statement exemplifies this behavior.

Distinguishing Between x=1 and window.x = 1

Creating a variable differs from adding a property to an object, even if that object functions as an environment record. Running

Object.getOwnPropertyDescriptor(window, 'x')
in both scenarios reveals that when x is a variable, the property x is not configurable, restricting deletion.

In instances where only window.x is visible, distinguishing between a variable and a property becomes challenging without additional context. Variables reside in scopes, on stacks, or elsewhere. While a compiler could verify the presence of a variable x, the cost may outweigh simply executing window.x = window.x + 1. Additionally, the existence of window is exclusive to browsers; JavaScript engines operate in diverse environments with varying property structures.

The discrepancy in performance between window.x across browsers is intriguing—Chrome experiences significant lag compared to Firefox and Safari. While Firefox and Safari optimize object accesses efficiently, Chrome may face challenges accessing environment record objects, potentially explaining its slower performance in this scenario.

Answer №4

In my personal opinion (although I have not found a way to definitively prove any theory right or wrong), this issue is related to the fact that window serves as both the global scope and a native object with an extensive list of properties.

I've noticed that scenarios run more efficiently when the reference to window is stored once and then accessed through that reference within a loop. On the other hand, cases where window is involved in Left-hand Side (LHS) lookups during each iteration of the loop tend to be slower.

The question of why there are variations in timing across different cases remains unanswered, but it appears to be attributed to optimizations by the JavaScript engine. One plausible explanation is that different browsers exhibit varying time proportions. The unexpected superior performance of scenario #3 could possibly be explained by the assumption that it has been heavily optimized due to its widespread usage.

After conducting tests with certain modifications, the results were as follows: Moving window.x to

window.obj.x</code produced identical results. However, when <code>x
was placed in
window.location.x</code (given that <code>location
is another significant native object), the timings dramatically changed:


1. access x directly    - Completed in 4278ms
2. access window.x     - Completed in 6792ms
3. accessUnqualified() - Completed in 4109ms
4. accessWindowPrefix()- Completed in 6563ms
5. accessCacheWindow() - Completed in 4489ms
6. access IIFE window  - Completed in 4326ms
7. access IIFE x      - Completed in 4137ms

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can we delete data from an array in Angular by using checkboxes?

I am working with a mat-table that displays data fetched from an API. Here's a snippet of the table structure: <mat-table [dataSource]="dataSource$"> <ng-container matColumnDef="process"> <mat-header-cel ...

Can we integrate the Node.js API into Meteor?

Being new to Meteor.js, I've been wondering if it's possible to utilize the node API in my project. I've hit a roadblock and haven't been able to find any information on this topic despite spending a significant amount of time researchi ...

How can I fix the 'Null is not an object' error that occurs while trying to assess the RNRandomBytes.seed function?

Currently, I am in the process of creating a mobile application using expo and react-native. One of the features I am working on involves generating a passphrase for users on a specific screen. To achieve this task, I have integrated the react-native-bip39 ...

Can we save javascript-generated HTML audio as a file on the back-end server?

In my latest project, I am building a JavaScript sequencer that controls HTML audio using intervals and timeouts. The goal is to handle all the processing and recording on the back-end while displaying a "Processing..." message to the user, and then utili ...

Issues with setting up gulp and implementing pdf.js

Currently, I am trying to integrate pdf.js library from this source. After downloading and extracting the library, I attempted to install gulp globally as per the instructions on the page. However, when I ran the command: C:\Users\xx\Deskto ...

Merging multiple observables with RxJs forkJoin

UPDATE : I'm currently facing a challenging issue that I can't seem to resolve. Within my code, there is a list of objects where I need to execute 3 requests sequentially for each object, but these requests can run in parallel for different obje ...

Passing image source from parent component to child component in Vue.js

I encountered an issue where I stored the image file name in a variable within the parent component and passed it to the child component using props. However, despite this setup, the child element is not displaying the image as expected. Here is the data ...

Uncertain about the scope of a jQuery extension?

I am facing an issue with a jQuery extension that is not functioning as expected. This is the function I have defined: $.fn.poster = function() { this.each(function() { self = $(this); self.click(function() { /* does stuff ...

Crafting a website complete with fixed tabs for seamless navigation

I am in the process of developing a website with multiple tabs on a single page. My goal is to ensure that these tabs remain visible even after refreshing the page, and also be able to direct users to a specific tab when they visit the page. Currently, I a ...

Vue Method always executed (regardless of click event)

I'm fairly new to vue and still getting a grasp on the fundamentals of my code. Currently, I am facing an issue with a Method. It should only trigger when the user clicks on the button, but it seems to be triggered all the time. I even tried adding ...

Creating an illuminated atmosphere: How to place a light source within a container using ThreeJS

Attempting to replicate the luminous box from Beat Saber in ThreeJS: https://i.sstatic.net/6IUmp.png Initiated by crafting a shape in Blender and exporting an OBJ. Imported it into Three, simply as geometry: https://i.sstatic.net/BGA1n.png Subsequently ...

Send two arguments to a personalized validation function using express-validation

I have a requirement to input two values in order to search for a subdocument using the main document ID and then another ID of the subdocument. These IDs are received as parameters, and I am utilizing express-validator with a custom function to ensure th ...

Verify whether the element within an iFrame contains any content

After conducting extensive research, I have been unable to find a satisfactory answer to my question. Therefore, I am reaching out to see if anyone has the knowledge I seek. The Goal: I aim to check the contents within an iFrame and determine whether it ...

Tips for maximizing the efficiency of loop operations

I am facing a performance issue with my R code that involves checking a data frame for combinations that meet specific conditions. Each row in my data frame needs to be checked for combinations where the value of variable "A" in that row is greater than or ...

What are some ways to implement bounds checking without using branching statements?

I am currently working on developing a secure buffer that can handle overflow automatically without needing any branching. The size of the buffer is a power of two and will only contain valid positive indices (excluding zero). Additionally, it allows for c ...

Material UI AppBar displaying custom colors instead of theme colors

Currently, I am utilizing Material UI version 4.7.0. In my project, I have established a customized theme (shown below) by utilizing createMuiTheme() method with personalized primary and secondary colors set. Within my AppBar component (also displayed be ...

Adjust the positioning of the cube in ThreeJS window

Currently, I am using Three.JS and facing an issue with adjusting the cube to fit the window size perfectly. The challenge is that the cube needs to maintain aspect ratio relative to the window (window.innerWidth and window.innerHeight), while also adjusti ...

Is Memory-Mapping Losing Its Speeds over Time? Any Potential Replacements?

I am dealing with a large number of matrices saved on disk, each containing thousands of rows and hundreds of columns. My task involves loading small portions of these matrices quickly, specifically around 1k rows from each matrix into a separate matrix s ...

Transmitting a sequence of JSON information from php to JavaScript,

I am struggling to fetch a series of JSON data from PHP to my JavaScript file. Initially, I have multiple JSON data stored in an array in PHP, and I am echoing each one by looping through the array in my JavaScript file. <?php $result = array('{ ...

JavaScript code to automatically pause/play HTML5 videos when scrolling

I'm working on creating a video gallery using HTML5 and JS. I've managed to set it up so that you can scroll horizontally between autoplaying muted HTML5 videos, and the videos play or pause when scrolled into or out of view. Everything is functi ...