Skip to content

Instantly share code, notes, and snippets.

@lourd
Created July 21, 2017 17:13
Show Gist options
  • Save lourd/8f450b609aab68d1de9b35228e784f30 to your computer and use it in GitHub Desktop.
Save lourd/8f450b609aab68d1de9b35228e784f30 to your computer and use it in GitHub Desktop.
Example of how "this" works in Javascript classes & functions
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