What scope is 'this' of a node.js object w

2019-03-03 21:28发布

问题:

I read "Why and how to bind methods in your React component classes?" and grabbed the basic idea of how different is this by the scopes it gets called.

I made a simple class to test this.

class Something {
  constructor(some) {
    this.some = some;
  }

  print() {
    console.log(
      "print from ",
      this,
      this === undefined ? "!!this is undefined!!" : this.some
    );
  }
}

From what I understood, the scope of this can be different although it is from the same object (instance).

const some = new Something("is");
some.print(); // 'this' is defined. 'this.some' is 'is'.

const going = some.print;
going(); // 'this' is undefined.
going.call(some); // 'this' is defined. 'this.some' is 'is'.

But then, why is this (which should be the object itself) undefined in the latter case? Shouldn't it be the top-level this, which is an empty object {}?

回答1:

there's a function print

when you call it like this

some.print();

that effectively gets translated to this

print.call(some, ...args)

When you just pull it out

const going = some.print

You've just gotten a reference to the standalone function so calling it with

going()

Is the same as calling

print();

It's the . between some and print that is magically passing some as this to print. going() has no period so no this is passed.

Note that you could assign going to an object and use the period operator to do the magic "pass the thing on the left as this" operation

function print() {
  console.log(this);
}

const someObj = {
  foo: print,
};

someObj.foo();

all the class keyword does is help assign print to Something's prototype

class Something {
  print() { console.log(this); }
}

is the same as

function Something() {}  // the constructor
Something.prototype.print = function() { console.log(this} );

which is also effectively the same as this

function print() { console.log(this); }
function Something() {}  // the constructor
Something.prototype.print = print;

Saikat showed using bind. Bind effectively makes a new function that wraps your old function. Example

const going = print.bind(foo);

Is nearly the same as

function createAFuncitonThatPassesAFixedThis(fn, objectToUseAsThis) {
  return function(...args) {
    return fn.call(objectToUseAsThis, ...args);
  }
}

const going = createAFunctionThatPassesAFixedThis(print, foo);
going();  // calls print with 'foo' as this

Some of the other answers have appear to have a fundamental mis-understanding of this. this is not the "owner" nor is this the current object. this is just effectively another variable inside a function. It gets set automatically if you use the . operator with a function call. so a . b() sets this to a. if the dot operator did not exist you could still set this by using call or apply as in somefuntion.call(valueForThis, ...args) or somefunction.apply(valueForThis, [...args]);

function print() {
  console.log(this.name);
}

print.call({name: "test"});  // prints test

const foo = {
  name: "foo",
  bar: print,
};

foo.bar();  // prints foo

function Something(name) {
  this.name = name;
}
Something.prototype.someFunc = print;

const s = new Something("something");
s.someFunc();  // prints something

s.foobar = print;
s.foobar();   // prints something

Also note that ES6 added the => arrow operator which binds this to whatever it was when the function is created. In other words

const foo = () => { console.log(this); }

Is the same as

const foo = function() { console.log(this); }.bind(this);

it should also be clear that both functions made with bind and arrow functions you can not change this using call or apply since effectively they made a wrapper that always sets this to what it was when the wrapper was made just like createAFuncitonThatPassesAFixedThis did above.

Let's add some comments to show what I mean

function createAFuncitonThatPassesAFixedThis(fn, objectToUseAsThis) {

  // return a function that calls fn with objectToUsAsThis as this
  return function(...args) {

    // when we arrive here from the "going.call" line below
    // `this` will be "someObject"
    // but on the next line we're passing "objectToUseAsThis" to fn as this
    // so "someObject" is effectively ignored.

    return fn.call(objectToUseAsThis, ...args);
  }
}

const going = createAFunctionThatPassesAFixedThis(print, foo);
going.call(someObject);  // pass some object as `this`

One more thing. It's very common to use bind to make a function for an event listener or callback. It's very common in React. Example

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    // 'this' will be the current component since we bound 'this' above
    console.log("was clicked");
  }
  render() {
    return (<button onClick={this.handleClick}>click me</button>);
  }
}

This would be handled in at least 3 common ways.

  1. Using bind in the constructor (see above)

  2. Using bind in render

      render() {
        return (<button onClick={this.handleClick.bind(this)}>click me</button>);
      }
    
  3. Using arrow functions in render

      render() {
        return (<button onClick={() => { this.handleClick(); }}>click me</button>);
      }
    

Above I showed what bind and => actually do with createAFuncitonThatPassesAFixedThis. They create a new function. So that should make it clear that if use style 2 or 3 above when every single time render is called a new function is created. If you use style 1 then a new function is only created in the constructor. It's a style issue as to whether or not that's important. Creating more functions means more garbage which means a possibly slower webpage but in most cases unless you have a crazy complicated webpage that is rendering constantly it probably doesn't matter which of the 3 ways you do it.



回答2:

you should correct this line to

const going = some.print.bind(some);

Otherwise going is getting a different scope and its not an member of some object so this is undefined

NOTE

this refers to the current class object. And going is not a member of any class so this is undefined



回答3:

const going = some.print;
going(); // 'this' is undefined.

Here, in the first line, you're storing a reference to the print function (property) of the some object in the variable going.

Note, you're just storing the reference to that particular property (print), not along with the owner (some).

this refers to the 'owner' always.

So when you execute going, the owner (this) is unknown (or call it undefined).