Last active
January 25, 2017 12:09
-
-
Save dlukes/460034d970cc787106b67d28e3e8f5f8 to your computer and use it in GitHub Desktop.
A review of `this` in regular vs. arrow functions in JavaScript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* With regular functions, `this` is bound purely depending on how the function is called. If it's | |
* bound to an object (either explicitly or via the `instance.funcName()` sugar), `this` refers to | |
* that object. Otherwise, it's `undefined` (or `window`, depending on your exact environment and | |
* whether `"use strict";` is in effect). | |
* | |
* In particular, it doesn't matter whether the function is attached to the object itself as a | |
* property, i.e. each instance has its own version of the function created during initialization, | |
* (see `Class2`), or whether it exists on the object's `prototype` as a method and all instances | |
* share it (see `Class1`). `this` is always resolved dynamically, i.e. depending on the call site. | |
* Neither of these is well suited for yanking out of the context of the original object and using | |
* as a callback which relies on `this`, because `this` will be `undefined` when the function is | |
* called standalone. | |
* | |
* A way to alleviate this in the property approach (`Class2`) is to rebind `this` to a custom | |
* variable and refer to that instead inside the function (see `Class2b`). | |
*/ | |
console.log("Function method:"); | |
function Class1(data) { | |
this.data = data; | |
}; | |
Class1.prototype.callback = function(spec) { | |
console.log(`${spec}: ${this.data}`); | |
}; | |
let c = new Class1("some data"); | |
c.callback("Called via instance"); // Called via instance: some data | |
let callback = c.callback; | |
callback("Called standalone"); // Called standalone: undefined | |
// ------------------------- | |
console.log("Function property:"); | |
function Class2(data) { | |
this.data = data; | |
this.callback = function(spec) { | |
console.log(`${spec}: ${this.data}`); | |
}; | |
}; | |
c = new Class2("some data"); | |
c.callback("Called via instance"); // Called via instance: some data | |
callback = c.callback; | |
callback("Called standalone"); // Called standalone: undefined | |
// ------------------------- | |
console.log("Function property revisited:"); | |
function Class2b(data) { | |
this.data = data; | |
// rebind `this` to a custom variable... | |
let _this = this; | |
this.callback = function(spec) { | |
// ... and use that instead | |
console.log(`${spec}: ${_this.data}`); | |
}; | |
}; | |
c = new Class2b("some data"); | |
c.callback("Called via instance"); // Called via instance: some data | |
callback = c.callback; | |
callback("Called standalone"); // Called standalone: some data | |
/* By contrast, `this` in arrow functions is resolved lexically -- if it's defined in the | |
* surrounding lexical scope of the arrow function, then it takes that value inside it as well, | |
* irrespective of how the function is called. If it isn't defined in the surrounding lexical scope, | |
* then it's `undefined`. | |
* | |
* This means that using an arrow function as a method (defined on the object's prototype and shared | |
* by all instances) is not possible if you need to use `this` (see `Class3` -- there's no `this` at | |
* the toplevel). But when used as instance properties, arrow functions can easily refer to `this` | |
* without jumping through any additional hoops, because it gets bound to the value of `this` | |
* provided by the surrounding lexical scope, which is that of the constructor function (i.e. `this` | |
* will be the instance object, see `Class4`), irrespective of how the function is called. | |
*/ | |
console.log("Arrow method:"); | |
function Class3(data) { | |
this.data = data; | |
}; | |
Class3.prototype.callback = (spec) => { | |
console.log(`${spec}: ${this.data}`); | |
}; | |
c = new Class3("some data"); | |
c.callback("Called via instance"); // Called via instance: undefined | |
callback = c.callback; | |
callback("Called standalone"); // Called standalone: undefined | |
// ------------------------- | |
console.log("Arrow property:"); | |
function Class4(data) { | |
this.data = data; | |
this.callback = (spec) => { | |
console.log(`${spec}: ${this.data}`); | |
}; | |
}; | |
c = new Class4("some data"); | |
c.callback("Called via instance"); // Called via instance: some data | |
callback = c.callback; | |
callback("Called standalone"); // Called standalone: some data |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment