Created
July 21, 2017 17:13
-
-
Save lourd/8f450b609aab68d1de9b35228e784f30 to your computer and use it in GitHub Desktop.
Example of how "this" works in Javascript classes & functions
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
class Foo { | |
constructor() { | |
this.feeling = 'π€₯' | |
} | |
speak() { | |
console.log(this.feeling) | |
} | |
} | |
let f = new Foo() | |
f.speak() // we see "π€₯" in the console | |
let detachedSpeak = f.speak // When we use that function without its context, that's a problem | |
detachedSpeak() // TypeError: Cannot read property 'feeling' of undefined | |
// If we *bind* the Foo class's `speak` method to its context, then it will work. | |
// There are a few ways to accomplish that | |
//////////////////////////////// | |
// 1. CLASS PROPERTY INITIALIZER | |
//////////////////////////////// | |
// As of 7/21/17 this is at Stage 2 in the TC39 proposal process and is very | |
// widely used in the React community. You can't use this without a transpiler, i.e. Babel, yet. | |
// See more: | |
// http://babeljs.io/docs/plugins/transform-class-properties/ | |
// https://github.com/tc39/proposal-class-fields | |
// We could have declared `speak` with property initializer style. | |
class Foo { | |
constructor() { | |
this.feeling = 'π€₯' | |
} | |
speak = () => { // the property initializer syntax | |
console.log(this.feeling) | |
} | |
} | |
// This same syntax also allows us to declare `feeling` without needing a constructor function | |
class Foo { | |
feeling = 'π' | |
speak = () => { | |
console.log(this.feeling) | |
} | |
} | |
let f = new Foo() | |
let detachedSpeak = f.speak | |
detachedSpeak() // π | |
// Property initializers are simpler shorthands for doing the same thing in the constructor function | |
// This is the same as the function above | |
class Foo { | |
constructor() { | |
this.feeling = 'βοΈ' | |
this.speak = () => { | |
console.log(this.feeling) | |
} | |
} | |
} | |
let f = new Foo() | |
let detachedSpeak = f.speak | |
detachedSpeak() // βοΈ | |
//////////////////////////////// | |
// 2. BIND THE CLASS METHOD IN THE CONSTRUCTOR | |
//////////////////////////////// | |
class Foo { | |
constructor() { | |
this.feeling = 'π€' | |
// We reassign `this.speak` to be a bound version of the original `speak` function from the class | |
// This is very similar to the property initializer, but has one small difference. | |
// With this way, we're making a bound copy of the class's method whenever we make an instance of the class. | |
// When using the property initializer, there is no class method. We create a new function each time an object is created. | |
// So the only technical difference is that doing this has one more function created and saved in memory: the original class method. | |
// The amount of difference is negligible and you should not worry about it. | |
this.speak = this.speak.bind(this) | |
} | |
speak() { | |
console.log(this.feeling) | |
} | |
} | |
let f = new Foo() | |
f.speak() // π€ | |
//////////////////////////////// | |
// 3. BIND IT AFTER THE FACT | |
//////////////////////////////// | |
// We don't have to modify the original class at all. You can `bind` a function to whatever context you want. | |
class Foo { | |
constructor() { | |
this.feeling = 'π' | |
} | |
speak() { | |
console.log(this.feeling) | |
} | |
} | |
let f = new Foo() | |
// Every function has a `bind` method that will create a copy of the function with the given context. | |
// See more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind | |
let detachedYetBoundSpeak = f.speak.bind(thing) | |
detachedYetBoundSpeak() // π | |
// This doesn't affect the original method, it makes a copy. The original has the same behavior | |
let detachedSpeak = f.speak | |
detachedSpeak() // still causes "TypeError: Cannot read property 'feeling' of undefined" | |
//////////////////////////////// | |
// 4. CALL IT WITH CONTEXT | |
//////////////////////////////// | |
// Here's another way you can provide context to a function, using a function's "call" method | |
class Foo { | |
constructor() { | |
this.feeling = 'π' | |
} | |
speak() { | |
console.log(this.feeling) | |
} | |
} | |
let f = new Foo() | |
let detachedSpeak = f.speak | |
// Call is a lot like bind, but instead of making a new function, it invokes it | |
// See more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call | |
detachedSpeak.call(f) // π | |
// You can also provide your own context to `bind` or `call`. Context is a plain 'ol javascript object | |
let obj = { feeling: 'π¦' } | |
detachedSpeak.call(obj) // π¦ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment