Click here to Skip to main content
15,886,110 members
Articles / Programming Languages / Javascript

JavaScript WTF #2: The this Keyword (Part 2)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
10 Jan 2017Apache2 min read 7.3K  
this keywork in JavaScript

…Continued from Part 1.

Arrow Functions

ES6 introduces arrow functions, which can take several forms:

JavaScript
()=>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:

JavaScript
'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:

JavaScript
'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:

JavaScript
Incremented value: 43
{ value: 43, delayInc: [Function: delayInc] }

With arrow functions, we can get rid of the extra variable and shorten the code:

JavaScript
'use strict'

var obj = {
    value: 42,
    delayInc() { 
         setTimeout(() => { 
             console.log(this);
             console.log(++this.value); 
	 }, 10);
    },
}

obj.delayInc();

This snippet behaves exactly like the 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:

JavaScript
'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:

JavaScript
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.

JavaScript
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 function 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.

This article was originally posted at http://www.ikriv.com/blog?p=2297

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Technical Lead Thomson Reuters
United States United States
Ivan is a hands-on software architect/technical lead working for Thomson Reuters in the New York City area. At present I am mostly building complex multi-threaded WPF application for the financial sector, but I am also interested in cloud computing, web development, mobile development, etc.

Please visit my web site: www.ikriv.com.

Comments and Discussions

 
-- There are no messages in this forum --