Here's What's Going On...
First, let's modify your code slightly -- but before we even do that, let's talk about what even influences this
: "CallSite".
CallSite
Where you call a function from will determine its this
value and there is an order of precedent which goes alongside such determination. Basically, there are 4 types of CallSite, however, there 1 or 2 caveats which arguably makes this 5 or 6 types.
The 4 Types of CallSite
- Global
- Implicit
- Explicit
- New
Global
This is fairly obvious & straightforward... Call a function detached from any object and this
will be the Global Scope:
function run() {
this === window === true;
}
Implicit
Also fairly straightforward. Call any function as a method of an object and this
will equal that object's instance.
function run() { return this; }
run(); // Global
({ id: 'test', run: run }).run(); // > { id: 'test', run: f() }
Explicit (See Caveat Below)
Straightforward. Calling a function using Function.prototype.call
or Function.prototype.apply
will force this
to equal the first argument of the prototype method. However, there is one caveat to this rule -- stay tuned.
(function run() { return this; }).call(new Klass()); // this instanceof Klass === true
New
As straightforward as it gets. Call a function and prefix the invocation with new
and this
will equal a new instance of the implied class. However, this does have one exception.
Caveats to CallSite Precedence
Explicit
Function.prototype.call
& Function.prototype.apply
cannot force a function's context if the function is the result of calling Function.prototype.bind
. Moreover, it is noteworthy to mention that recalling Function.prototype.bind
on an already bound function will not re-bind to another context; its this
value will remain the same as that of the original bind
call.
function run() { return this; }
let o = { };
let x = { };
let r = run.bind(o);
let result = r.call(x); // result === x === false && result === o === true
New
When calling a function that is prefixed with new
, you will not get an object-instance of the implied class if the function returns a different value.
class Class {}
var Klass = function Klass() {
return new Class;
};
var klass = new Klass(); // klass instanceof Class === true
Your Code (Modified)
In your original code, you get back the value of x
because Arrow-Functions always bleed the outermost scope into their own. That is, an arrow-function will always take its parent-scope's this
context. This behavior is no different returning the result of calling Function.prototype.bind
on a function and providing this
as its context.
var x = {
b: 5,
y: function a() {
console.log('a', this);
return function b() {
console.log('b', this); // window
}
return () => {
console.log('=>', this); // x
}
}
}
x.y()();
When we call x.y()
we are returning whatever that function returns. I have modified your code to present two return scenarios:
- Returning
function b() {...}
: x.y()
is a detached function. Therefore, its CallSite will be Global
- Returning
() => {...}
: x.y()
is a detached Arrow-Function. Its behavior will be as if you invoked bind
on a normative function and passed in the [parent] function's this
context as the first argument.
Closing
I hope this makes a lot more sense after further understanding CallSite. Knowing this, you realize it is possible to actually use new
as an invocation tactic. That is, try to figure out how the following code could run successfully:
Exercise 1 (The "Crockford Dog Balls Problem")
What behavior do the following 2 lines of code exhibit?
var a = new (function Klass() {})(); // a
var b = new (function Klass() {}()); // b
Exercise 2 ("What's New?")
Write a function that remediates the following code to run successfully.
var instance = new new new new Klass();
Hope this helps!