Search code examples
javascriptfunctionthisexecutioncontext

JS: "this" in function context in strict mode, MDN spec doesn't match chrome 67 implementation


From MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this, it says:

In strict mode, however, the value of this remains at whatever it was set to when entering the execution context, so, in the following case, this will default to undefined:

function f2() {   
  'use strict'; // see strict mode   
  return this; 
}

f2() === undefined; // true

This suggests that if I (1) 'use strict'; and (2) define f2 inside another function that invoking f2 would bind the outside function's this for f2. BUT!

It doesn't work...

'use strict';

function AlarmClock(clockName) {
  this.clockName=clockName;
}

console.log("hello, let's see some weird stuff");

console.log("THIS in global", this);

AlarmClock.prototype.start = function(seconds) {
  console.log('outer', this);
  var testInside = function() {
    console.log('inner', this);
  }
  testInside();
}

var clock = new AlarmClock("Horizons")
clock.start(1);


// WITHOUT CONSTRUCTORS

function withoutOut() {
  console.log("withoutOut", this)
  this.howard="notSoBad";
  console.log("modifiedTheThis, should have Howard", this)
  function withoutIn1() {
   console.log("withoutIn1", this);
   console.log("Strict should set to the defining object's this");
  }
  var withoutIn2 = function() {
    console.log("withoutIn2", this);
    console.log("Strict should set to the defining object's this");
  }
  withoutIn1();
  withoutIn2();
}

withoutOut.bind({Moose: "genius"})();

console.log("DONE");

Gives this output:

hello, let's see some weird stuff
THIS in global {}
outer AlarmClock { clockName: 'Horizons' }
inner undefined
withoutOut { Moose: 'genius' }
modifiedTheThis, should have Howard { Moose: 'genius', howard: 'notSoBad' }
withoutIn1 undefined
Strict should set to the defining object's this
withoutIn2 undefined
Strict should set to the defining object's this
DONE

NOTE: I ran this with node v10.5.0, from a mac osx command line.

NOTE2: If you run in devtools, you have to follow these steps for use strict.

NOTE3: Basically, I want to find someway to get inner, withoutIn1 or withoutIn2 to NOT be undefined. And, I know you can do this with an explicit bind, but I want to specifically get the behavior specified in the MDN doc.

If you agree with me, MDN should change the "this" doc to just say, in function context with "use strict", this is set to undefined ALWAYS. (author likes)

Or, Chrome should change the implementation. (author dislikes)


Solution

  • The value of this when a function is called has nothing at all to do with how or where the function is defined. It has to do only with how the function is called. Calling your inner functions:

    AlarmClock.prototype.start = function(seconds) {
      console.log('outer', this);
      var testInside = function() {
        console.log('inner', this);
      }
      testInside();
    }
    

    like testInside() without any object reference means that there's nothing to bind to this, and in strict mode that means this is undefined. However, if you had written instead

      testInside.call(this);
    

    then there'd be a context value. The fact that a function is "inside" another function, again, has absolutely no impact on how this is bound. Alternatively:

      this.testInside = testInside;
      this.testInside();
    

    it'd also work, because once again there's a context.

    Oh, and also also, the new(ish) arrow function mechanism does let you create functions that effectively "inherit" the this value from their lexical environment. So:

    AlarmClock.prototype.start = function(seconds) {
      console.log('outer', this);
      var testInside = () => {
        console.log('inner', this);
      }
      testInside();
    }
    

    would work, because calling an arrow function does not involve any this binding; this behaves essentially like a variable in the closure.