Skip to content

Instantly share code, notes, and snippets.

@jacklynrose
Created May 12, 2016 05:26
Show Gist options
  • Save jacklynrose/7a010d9af3795b529aa9079edca90825 to your computer and use it in GitHub Desktop.
Save jacklynrose/7a010d9af3795b529aa9079edca90825 to your computer and use it in GitHub Desktop.
// Set on window
this.foobar = "V1"
//=> (result) "V1"
console.log(this.foobar)
//=> (output) V1
//=> (result) undefined
// Uses window
(() => {
console.log(this.foobar);
})()
//=> (output) V1
//=> (result) undefined
// Uses window
(function() { console.log(this.foobar); })()
//=> (output) V1
//=> (result) undefined
// Uses window
(function() {
(function() { console.log(this.foobar); })();
})()
//=> (output) V1
//=> (result) undefined
// Uses window
(function() {
(() => { console.log(this.foobar); })();
})()
//=> (output) V1
//=> (result) undefined
// Sets on window, uses that
(function() {
this.foobar = "V2";
(() => { console.log(this.foobar); })();
})()
//=> (output) V2
//=> (result) undefined
// Resetting
this.foobar = "V1"
//=> (result) "V1"
// Sets and uses window
(function() {
this.foobar = "V2";
(function() { console.log(this.foobar); })();
})()
//=> (output) V2
//=> (result) undefined
// Now where functions get weird because constructors and methods
function MyThing1() {
this.foobar = "V3";
(function() { console.log(this.foobar); })();
}
//=> (result) undefined
// Run the constructor and oh wait what? Uses window
new MyThing1();
//=> (output) V2
//=> (result) MyThing1 {foobar: "V3"}
// What if we use arrow functions?
function MyThing2() {
this.foobar = "V4";
(() => { console.log(this.foobar); })();
}
//=> (result) undefined
// That makes more sense now, it uses the thing above it
new MyThing2();
//=> (output) V4
//=> (result) MyThing2 {foobar: "V4"}
// This is how we make them act the same by binding "this"
function MyThing3() {
this.foobar = "V5";
(function() { console.log(this.foobar); }).bind(this)();
}
//=> (result) undefined
// Now it acts the same as the arrow function
new MyThing3();
//=> (output) V5
//=> (result) MyThing3 {foobar: "V5"}
@Nicktho
Copy link

Nicktho commented May 12, 2016

To expand on and explain some of the weirdness:

Calling a "Function" calls it in the context its assigned, calling an "Arrow Function" calls it in the context of the current context of the surrounding scope.

Using this lets look at some of the examples.

this.foobar = "V2";

function MyThing1() {
  this.foobar = "V3";
  (function() { console.log(this.foobar); })();
}

new MyThing1();
//=> (output) V2
//=> (result) MyThing1 {foobar: "V3"}

Following what's happening:

Window, the default, global object, call's new MyThing1()

the new keyword creates a new object and the interpreter calls the constructor MyThing1() with the context as the new object.

The first line, this.foobar = "V3" sets the context's value foobar to "V3", in this case, the context is the new object, so it's foobar is now "V3".

The second line; we get a function declaration. The anonymous function is declared in the scope of MyThing1, yet it's not a method assigned to the new object that is the currently the context of the call.

Side note, if we assign this anon function to the context that is currently calling MyThing1, we'll get the expected behaviour, eg:

function MyThing1() {
  this.foobar = "V3";
  this.fn = function() { console.log(this.foobar); };
  this.fn(); // "V3"
}

Anyway, back to following what's happening...

The interpreter calls the IIFE, and because the function itself is called with no context (ie, object.fn() or fn.call(object)), it falls back to the default context, Window. The this inside the IIFE is now Window, so it log's it's foobar property, instead of the foobar assigned to the new object calling it, resulting in the console logging "V3".

Now if we look at the example uses an arrow function:

this.foobar = "V2";

function MyThing2() {
  this.foobar = "V4";
  (() => { console.log(this.foobar); })();
}

new MyThing2();
//=> (output) V4
//=> (result) MyThing2 {foobar: "V4"}

If we follow it through again:

Window, the default, global object, call's new MyThing2()

the new keyword creates a new object and the interpreter calls the constructor MyThing2() with the context as the new object.

The first line, this.foobar = "V4" sets the context's value foobar to "V4", in this case, the context is the new object, so it's foobar is now "V4".

The second line; we get an arrow function declaration. An arrow function can't be assigned to any particular context, instead it uses the context of the current scope where it was declared.

We then call the arrow function declaration, and since arrow function's use the same context as that of the current scope's context, the interpreter will pass the new object as the context!

The function then calls console.log with the foobar belonging to the new object, ie, "V4"!

Javascript, hey.

@Nicktho
Copy link

Nicktho commented May 12, 2016

Another example of weirdness is if we try to force an arrow function to a context. eg:

this.foobar = 'V1';

function MyThing1() {
  this.foobar = 'V2';
}

const thing = new MyThing1();


function MyThing2() {
  this.foobar = 'V3';
  thing.fn = function() {
    console.log(this.foobar)
  }
}

new MyThing2();
thing.fn()

Gives us "V2" in the console because we assign the function within MyThing2 to the first object, thing. But if we try to do it with arrow functions....

this.foobar = 'V1';

function MyThing1() {
  this.foobar = 'V2';
}

const thing = new MyThing1();


function MyThing2() {
  this.foobar = 'V3';
  thing.fn = () => {
    console.log(this.foobar)
  }   
}

new MyThing2();
thing.fn()

We get "V3". The arrow function basically says "No thanks" to your assignment and sticks with its guns. What a badass.

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