Skip to content

Instantly share code, notes, and snippets.

@joelburton
Last active May 12, 2019 05:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joelburton/c93db99ea20386be0535391c7f5cd31d to your computer and use it in GitHub Desktop.
Save joelburton/c93db99ea20386be0535391c7f5cd31d to your computer and use it in GitHub Desktop.
"Simple" Guide to JS this

The "Simple" Guide to JS this

A. all functions all called with a context — "what was I called on?"

  1. to find the context, look to the "left of the dot" when the function is invoked

    • eg: foo.bar() means bar will get context of foo

    • (this only works at all if "bar" is a property of foo --- otherwise, foo.bar itself is just undefined!)

  2. if there is no dot, context is global this (see F, below)

    • eg: bar() = bar wiill get global this for context
  3. Exceptions to rules A1 & A2: fn.call (see D, below) and bound functions (see E, below)

B. normal functions [ie, non-arrow] provide that context as this

  1. for function bar() { console.log(this) }, foo.bar() will print foo

  2. this is a special name; it's not really a variable [ie, you can't assign to it: this = 7 gives error]

C. arrow functions never care about their context — they do not get assigned to this

  1. for baz = () => { console.log(this) }, foo.baz() will not print foo

  2. since arrow functions don't get their own this, normal scope rules apply --- will look in enclosing functions for definitions of this:

   function outer() {
     let inner = () => { console.log(this) }
         
     console.log(this)    // what outer got for context
     console.log(inner(this))  // prints same thing
   }
  1. if looking up in scope never finds this will use "global this" [see F, below]

    • this would happen if no enclosing functions, or if all enclosing functions are arrow functions

D. instead of "left of dot", you can force something to be the context with fn.call(context, ...other-args)

  1. foo.call("use me", 1, 2) calls foo with "use me" as context, passing 1 and 2 as args

    • it's like "use me".foo(1, 2), but doesn't require foo to be found as method on strings
  2. Remember C: arrow functions never care about their context — this has no effect on them

E. fn.bind(context, ...other-args) returns new function that is permanently bound to that context/args

  1. fooOnMe = foo.bind("use me") makes new function that will always get "use me" as context

  2. fooOnMe2 = foo.bind("use me", 2) makes new function that will always get "use me" as context and 2 as first arg.

  3. the original function isn't changed in any way

  4. Remember C: arrow functions never care about their context — this has no effect on them

F. what is considered the "global this" varies by environment

  1. in normal browser JS, global this is browser window object

  2. in normal node JS, global this is an object named global

  3. in JS "strict mode" [browser or node], global this is undefined

    • some JS bundlers, like Webpack, always put you in strict mode
  4. in JS classes [browser or node], you're always in strict mode, so global this is undefined

  5. in modern JS environments (like recent Chrome), globalThis is defined to be the "global this"

  6. it's critical you know how this works; it's much less important you understand what "global this" always is

G. Inside classes, how is this different?

  1. tiny, trivial difference in F3; otherwise, being inside a class doesn't change how all this works

  2. eg: fluffy.dance() provides fluffy context to dance method, but this isn't "because OO" — it's the normal rule of "look-left-of-dot"

  3. in a class constructor function, this is the object being constructed:

    • eg: class Cat { constructor() { console.log(this) } } will print the new cat on fluffy = new Cat()

    • you can read fluffy = new Cat() as if it read fluffy = empty Cat(); fluffy.constructor() (that's not legal syntax; just a visualization of what new does)

H. "Losing Context"

  1. remember A1: you find out the context by "left of the dot at time of invocation" [exceptions above: D fn.call, E fn.bind]

  2. so for dance = fluffy.dance; dance(), the context won't be fluffy — it's not "left of dot at invocation"

    • can fix by writing as fluffy.dance() or dance.call(fluffy), or using a bound version of dance
  3. for event handlers: elem.addEventListener("click", this.handleClick), JS won't call handleClick on this

  4. in React: <a onClick={this.handleClick}>, React won't call handleClick on component instance (this)

  5. you can fix by passing event listners a function that calls real function on desired context:

    • elem.addEventListener("click", (evt) => this.handleClick())

    • <a onClick={(evt) => this.handleClick()}>

  6. you can fix by passing event listeners a bound function

    • boundHC = this.handleClck.bind(this); elem.addEventListener("click", boundHC)

    • boundHC = this.handleClck.bind(this) then <a onClick={boundHC}>

    • this is often nicer, since you can create this bound version once, in the constructor — when you do, you'll often, for convenience/clarity, give it the same name as the method defined on the class: constructor () { this.handleClick = this.handleClick.bind(this) } — now, when you give the event listener this.handleClick, it will be the bound-to-this version you made in the constructor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment