•
this
Keyword explained
The this
keyword is one of the confusing mechanism in JavaScript. Its confusing as this
in JS isn't the same as this
in conventional OOP languages. In JS, this
could have different meanings depending on the call-site. There are 4 rules for this
binding and by inspecting the call-site we can identify the binding.
Let us explore the 4 rules, but before we get started we need some helper functions to understand.
function print_this_a() {
console.log(this.a);
}
function call_fn(fn) {
// take a fn as parameter and execute it!
fn();
}
a = "Global a";
Rule 1 - Default Binding
The first rule we will examine comes from standalone function invocation. Think of this rule as the default catch-all rule when none of the other rules applies. This resolves to the global object, in the browser its the window object.
console.log("(1.0) Default Binding");
print_this_a(); // Output - "Global a"
Rule 2 - Implicit Binding
This rule is applicable when the call-site has a context object or owning object, ie the object owns the function reference at the time the function is called.
obj1 = {
a: "obj1 a",
print_this_obj1_a: print_this_a
}
console.log("(2.0) Implicit Binding");
obj1.print_this_obj1_a(); // Output - "obj1 a"
Since the object obj1
owns a reference to print_this_obj1_a
and that particular function is invoked through the object reference, this
is resolved to obj1
**Implicitly losing context **
console.log("(2.1) Implicitly lost");
call_fn(obj1.print_this_obj1_a);
// this is no longer obj1 as the function no longer owns the reference
A way to fix this - explicit binding
console.log("(2.2) Calling with call");
print_this_a.call(obj1, "var1", "var2");
// This is a way to specify the object you want 'this' to resolve to
console.log("(2.3) Calling with apply");
print_this_a.apply(obj1, ["var1", "var2"]);
// Alternative way
Rule 3 - Hard Binding
// Helper fn which allows us to bind functions to specific objects, permanently
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}
The above function uses the "apply" property discussed in the previous rule. However, this can now be passed across functions and be invoked with the binding intact.
console.log("(3.0) Hardbinding using Binding Helper");
var hard_binded_fn = bind(print_this_a, obj1);
call_fn(hard_binded_fn); // Output - "obj1 a"
ES6 has a built-in bind function, which we can use as follows
console.log("(3.1) Hardbinding using ES6 bind");
var hard_binded_fn_ES6 = print_this_a.bind(obj1);
call_fn(hard_binded_fn_ES6);
Rule 4 - New Binding
JavaScript has a new operator, and the code pattern to use it looks basically identical to what we see in those class-oriented languages. In JS, constructors are just functions that happen to be called with the new operator in front of them. We will discuss more about this in prototypes
function object_constructor(name) {
this.a = name;
this.print_this_object_a = print_this_a;
}
object_constructor.prototype.change_a = function(a) {
this.a = a;
}
obj2 = new object_constructor("obj2_a");
obj3 = new object_constructor("obj3_a");
console.log("(4.0) Calling with new binding");
obj2.print_this_object_a(); //obj2_a"
obj3.print_this_object_a(); //obj3_a"
obj2.change_a("obj2_a_new")
obj3.change_a("obj3_a_new")
obj2.print_this_object_a(); //obj2_a_new"
obj3.print_this_object_a(); //obj3_a_new"
Ignored this
This is unrelated to the above rules, however its a handy feature in JS. It can be useful to create multiple functions which have small differences with each other.
console.log("< --- IGNORED this --- >");
// This can be useful to create functions with pre populated arguments
function print_arguments(){
console.log( Array.prototype.slice.call(arguments, 0) );
}
console.log("print_arguments in action");
print_arguments(1,2,3); // 1 2 3
console.log("Calling with pre populated arguements");
var fn1 = print_arguments.bind(null,1,2,3);
fn1(); // 1 2 3
fn1(4,5,6); // 1 2 3 4 5 6
Further Reading - “You Don’t Know JS: this & Object Prototypes.”