Scoping of “this” – client side Javascript versus server side NodeJS

Every function in Javascript has a “this” object that behaves differently depending on the execution context. Everytime a function is executed in Javascript, there is an object context associated with it; “this” provides a way to reference that object. When calling an object method, such as Foo.bar(), “this” refers to the Foo object. When calling a method in a global context, such as bar(), “this” refers to the Javascript global object. In most browsers, that would be the window object. bar() is essentially shorthand for window.bar().

Now, the behavior in NodeJS is quite different. In order to prevent pollution of the global namespace, all functions running in the top level scope are not scoped to the global object, but to a unique local object instead. That means every function in Node has its own “this” variable. According to the official NodeJS documentation:
In browsers, the top-level scope is the global scope. That means that in browsers if you’re in the global scope var something will define a global variable. In Node this is different. The top-level scope is not the global scope; var something inside a Node module will be local to that module.

Consider the following code:


function somefunc() {
    this.value = 100;
    console.log("this.value inside somefunc() is " + this.value);
}

this.value = 1;
somefunc();
console.log("this.value in top level scope is " + this.value);

The nodeJS output is:

this.value inside somefunc() is 100
this.value in top level scope is 1

The output from the javascript console in a browser such as Chrome or Firefox is:

this.value inside somefunc() is 100 
this.value in top level scope is 100 

Because somefunc is attached to the global object, “this.value” actually refers to window.value. Thus, changing the value of “this.value” inside somefunc() changes window.value, so “this.value” is still 100 even after somefunc() has finished execution. In NodeJS however, “this.value” inside somefunc is not the same “this.value” outside of somefunc. Again, every function has its own “this” variable, scoped to the object that it is referenced in. In NodeJS, the same holds true for anonymous functions as well:

 
function somefunc(cb) {
     this.value = 100;
     console.log("this.value inside somefunc() is: " + this.value);
     cb();
}

this.value = 1;
somefunc(function() {
    this.value = 50;
    console.log("this.value inside the anonymous function is: " + this.value);
});

console.log("this.value in top level scope is " + this.value); 

NodeJS outputs:

this.value inside somefunc() is: 100
this.value inside the anonymous function is: 50
this.value in top level scope is 1 

The browser outputs:

this.value inside somefunc() is: 100 
this.value inside the anonymous function is: 50  
this.value in top level scope is 50 

As before, the browser behavior is a result of somefunc being executed in a global context. In the preceding code, this.value always refers to window.value, even when it appears inside the anonymous function callback (more on this later). In NodeJS, the anonymous function has its own “this” object that is different from the “this” object inside somefunc. This results in the three different values for “this.value”.

Now let’s take a look at what happens when we explicitly set the execution context of somefunc to that of a local object:

function somefunc(cb) {
     this.value = 100;
	 console.log("this.value within somefunc is: " + this.value);
     cb();
}

var obj = {
    method:somefunc
};

this.value = 1;

obj.method(function() {
    this.value = 50;
	console.log("this.value within anonymous function is: " + this.value);
});

console.log("obj.value is: " + obj.value);
console.log("this.value in top level scope is: " + this.value);               

The browser prints out:

this.value within somefunc is: 100  
this.value within anonymous function is: 50  
obj.value is: 100  
this.value in top level scope is: 50 
this.value within somefunc is: 100
this.value within anonymous function is: 50
obj.value is: 100
this.value in top level scope is: 1 

When run from a browser, obj.value is set to 100 and window.value is set to 50. Since somefunc is no longer being called from a global context, “this” inside somefunc is owned by the object that calls it (imaginatively named obj in this example). “this.value” is actually referencing obj.value. Note that the anonymous callback function, “this” is not scoped to obj, but rather the global object. Because the anonymous function is not explicitly called via an object context, it still defaults to a global context!

The behavior in Node is consistent with what we observed before. When called in an object context, the “this” object inside of somefunc refers to obj. The anonymous function has its own “this” object, and any modifications to it are not reflected in obj nor in the top level scope. As a result, obj.value is 100 whereas this.value remains 1 in the top level scope – it was initialized once and never modified again.

The key takeaway here is that “this” can be incredibly confusing and unintuitive. Javascript provides methods such as apply() and call() methods for a reason: so that you can explicitly set “this” and avoid all the headache second-guessing yourself trying to figure out what “this” actually is.

Leave a Reply

Your email address will not be published. Required fields are marked *