What is the Meaning of `this` — Javascript Function Invocation and `this`
Anyone who has worked extensively with a strong OO language before writing their first line of Javascript is going to find out
very quickly that they are not in Kansas anymore. For starters, without using some of ES2015's goodness (ES6 previously)
Javascript does not have block scope—it has function scope. Before the days of let
, this means that variables cascade down the function chain.
For instance, if we declare a variable outside of any function or without specifying var
before instantiating a variable (aka implicit declaration)
for the first time, that variable is global. If that doesn't send chills down your spine I don't know what would.
Here is an example with jasmine tests:
describe("Global Variables are awesome", function() {
var a = 10;
// This is function hoisting :) Functions can be forward referenced in their scope but variables cannot
one();
function one() {
b = 15;
two();
function two() {
var c = 20;
it("I can see a, b and c", function() {
expect(a + b + c).toBe(45);
});
}
it("I can see a, b but not c", function() {
expect(a + b).toBe(25);
expect(typeof c == 'undefined').toBe(true);
});
}
it("I can see a, b but not c", function() {
expect(a + b).toBe(25);
expect(typeof c == 'undefined').toBe(true);
});
});
That is all fun and games until the unsuspecting dev on your team starts spewing global variables around and misusing this
like it's going out of
style. That brings us back to our topic of discussion. Function invocations and their relation to this
. Seasoned OO dev's are used to this
being
a reference to the current object. What a surprise they will be in for when they make their way to Javascript. In Javascript, this
might refer to window
,
the current object, or even undefined
. Talk about confusing.
What really determines the scope of this
is how a function is invoked. There are a few ways to invoke a function in Javascript: function, method, constructor,
apply, and call. Each one has their quirks
Let's look at an example for function invocation:
function functionInvocation() {
return 'function invocation';
}
function functionInvocationThisReturn() {
return this;
}
describe("Function Invocation", function() {
it("can be called as a function", function() {
expect(functionInvocation()).toBe('function invocation');
});
it("is attached to the window object and thus global", function() {
expect(window.functionInvocation()).toBe('function invocation');
});
it("'this' equals the window object", function() {
// in strict mode, the return would be undefined. See example below
expect(functionInvocationThisReturn()).toBe(window);
});
(function () {
"use strict";
var that = this;
function functionInvocationStrictMode() {
return this;
}
it("`this` is undefined since function was not called as a method in strict mode", function() {
// see here for an explanation: http://stackoverflow.com/questions/9822561/why-is-this-in-an-anonymous-function-undefined-when-using-strict
expect(functionInvocationStrictMode()).toBe(undefined);
expect(that).toBe(undefined);
});
})();
});
Take a look at them apples. Function invocation has its drawbacks for sure when it comes to this
. When the function is defined in the global scope (in a browser it
would be window
), this
would refer to window
aka the global scope. Yikes. What's even more confusing is when you throw "use strict"
(though a very good thing) into the mix, then
it returns undefined
. That isn't confusing (the foregoing statement just might need to be wrapped in the new html5 <sarcasm>
tags).
Moving on we make a stop at method invocation and things get a bit easier:
describe("Method Invocation", function() {
var obj = {
run: methodToInvoke
};
var obj3 = {
run: methodToInvoke
};
function methodToInvoke() {
return this;
}
it("will have a `this` context of obj", function() {
expect(obj.run()).toBe(obj);
});
it("will have a `this` context of obj3", function() {
expect(obj3.run()).toBe(obj3);
});
(function () {
'use strict';
var obj2 = {
run: useStrictMethodToInvoke
};
function useStrictMethodToInvoke() {
return this;
}
it("will have a `this` context of obj2", function() {
expect(obj2.run()).toBe(obj2);
});
})();
});
This is functionality an object oriented dev would be more likely to expect. As you can see from the code examples, method invocation steals its this
context from the object to which the method or function is bound. Pretty straightforward stuff.
A similar invocation method is constructor invocation. Constructor functions are no different than other functions in how they are defined.
Their main difference is in how they are invoked, using the similar-to OO languages new
keyword. The idea behind constructor invocation is quite simple.
You say new functionName()
it gives back a new empty object—which is important since objects are passed by reference in Javascript. That new object becomes
the context for this
. In addition, in lieu of a defined return statement in the function a constructor function will return this
Here are a few examples:
describe("Constructor Invocation", function() {
var obj = new BaseConstructor();
var obj1 = new BaseConstructor();
var returned = new ReturnThis();
function BaseConstructor () {
this.run = function () { return this; };
}
function ReturnThis() { this.a = 50; }
it("will have a `this` context of obj", function() {
expect(obj.run()).toBe(obj);
});
it("will have a `this` context of obj2", function() {
expect(obj1.run()).toBe(obj1);
});
it("will automatically return this", function() {
expect(returned.a).toBe(50);
});
});
Now here come the hat tricks. Next up is is .call()
invocation.
... to be continued