…Continued from Part 1.
Arrow functions
ES6 introduces arrow functions, which can take several forms:
()=>expression x=>expression (x,y)=>expression ()=>{operators} x=>{operators} (x,y)=>{operators}
Besides being a shorter notation for function()
, arrow functions have special rule for this
. They inherit this
from their immediate lexical context. This makes arrow functions ideal for callbacks. Consider the following code:
'use strict' var obj = { value: 42, delayInc() { setTimeout(function() { console.log("Incremented value: " + ++this.value); // oops... console.log(this); }, 10); }, } obj.delayInc();
This prints
Incremented value: NaN Timeout { _called: true, _idleTimeout: 10, _idlePrev: null, _idleNext: null, _idleStart: 96, _onTimeout: [Function], _timerArgs: undefined, _repeat: null, value: NaN }
Why NaN
? The hint is in the subsequent lines: this
value of the callback is (somewhat arbitrarily) set to a Timeout
object. In other languages, the Timeout
object would probably have been passed as a parameter, but in JavaScript we have this
, which can be set to anything, so why not use it? Consequently, this.x
in the callback refers to Timeout.x
, which is undefined
, and not to obj.x
as one might have expected. Incrementing undefined
yields NaN
, and obj.x
remains unchanged.
Pre-EcmaScript 6 fix to this problem was to introduce an extra variable:
'use strict' var obj = { value: 42, delayInc() { var $this = this; setTimeout(function() { console.log("Incremented value: " + ++$this.value); console.log($this); }, 10); }, } obj.delayInc();
This works as expected:
Incremented value: 43 { value: 43, delayInc: [Function: delayInc] }
With arrow functions we can get rid of the extra variable and shorten the code:
'use strict' var obj = { value: 42, delayInc() { setTimeout(() => { console.log(this); console.log(++this.value); }, 10); }, } obj.delayInc();
This snippet behaves exactly like previous one, printing
Incremented value: 43 { value: 43, delayInc: [Function: delayInc] }
Unfortunately, inheriting this
from the lexical context makes arrow functions unsuitable in some other contexts. Consider:
'use strict' var obj = { value: 42, inc: ()=>++this.value, inc2() { return ++this.value; } } console.log(obj.inc(), obj.inc2());
This prints
NaN 43
The arrow function inc()
takes this
from the surrounding context, setting it to the global object. Similarly looking regular member function inc2()
has its this
set to obj
at the time of invocation.
Unlike Java and C#, where lambdas are nearly equivalent to regular methods, in JavaScript they behave quite differently.
This in static context
In most languages “static” methods don’t have this. JavaScript is, again, different:
class Foo { static f() { console.log(this, this === Foo); } } Foo.f();
This prints
[Function: Foo] true
In static contexts this
points to the surrounding class, which is actually a function.
Conclusion
Simply put, there are two many rules governing the value of this
in JavaScript. If I write a simple function printing this
, it can print virtually anything, depending on how we invoke it, and it is by design.
function f() { console.log(this); } f(); // 'global object' or undefined in strict mode var obj = { f }; obj.f(); // obj new f(); // newly created object of type f f.call({x:42}); // { x: 42 } f.bind({x:42})(); // { x: 42 }
ES6 arrow and static functions add even more rules and special cases.
Such versatility makes JavaScript this
a really powerful mechanism. Think through how many hoops you would have to jump to call method of one C# class on an instance of another C# class. On the other hand, why would you want to do it? Even if C# had such a feature, it probably would have been considered extremely dangerous and not suitable for use under normal circumstances. Anyway, huge power and flexibility of JavaScript this
comes at the expense of simplicity and safety: it provides a number of excellent devices to shoot oneself in the foot.
Permalink
Very good/useful summary of difficult JS features. Thank you!
Permalink
Thanks. I only wish there were less difficult features to write about 🙂