Javascript “this” pointer within nested function

2019-01-01 15:48发布

问题:

I have a question concerning how the \"this\" pointer is treated in a nested function scenario.

Say I insert this following sample code into a web page. I get an error when I call the nested function \"doSomeEffects()\". I checked in Firebug and it indicates that when I am in that nested function, the \"this\" pointer is actually pointing to the global \"window\" object - which I did not expect. I must not be understanding something correctly because I thought since I declared the nested function within a function of the object, it should have \"local\" scope in relation to the function (i.e. the \"this\" pointer would be referring to the object itself like how it is in my first \"if\" statement).

Any pointers (no pun intended) would be appreciated.

var std_obj = {
  options : { rows: 0, cols: 0 },
  activeEffect : \"none\",
  displayMe : function() {

    // the \'this\' pointer is referring to the std_obj
    if (this.activeEffect==\"fade\") { }

    var doSomeEffects = function() {

      // the \'this\' pointer is referring to the window obj, why?
      if (this.activeEffect==\"fade\") { }

    }

    doSomeEffects();   
  }
};

std_obj.displayMe();

回答1:

In JavaScript the this object is really based on how you make your function calls.

In general there are three ways to setup the this object:

  1. someThing.someFunction(arg1, arg2, argN)
  2. someFunction.call(someThing, arg1, arg2, argN)
  3. someFunction.apply(someThing, [arg1, arg2, argN])

In all of the above examples the this object will be someThing. Calling a function without a leading parent object will generally get you the global object which in most browsers means the window object.



回答2:

this is not part of the closure scope, it can be thought of as an additional parameter to the function that is bound at the call site. If the method is not called as a method then the global object is passed as this. In the browser, the global object is identical to window. For example, consider the following funciton,

function someFunction() {
}

and the following object,

var obj = { someFunction: someFunction };

If you call the function using method syntax such as,

obj.someFunciton();

then this is bound to obj.

If you call someFunction() directly, such as,

someFunction();

then this is bound to the global object, that is window.

The most common work around is to capture this into the closure such as,

displayMe : function() {      

    // the \'this\' pointer is referring to the std_obj      
    if (this.activeEffect==\"fade\") { }      
    var that = this;  
    var doSomeEffects = function() {      

      // the \'this\' pointer is referring to global
      // that, however, refers to the outscope this
      if (that.activeEffect==\"fade\") { }      
    }      

    doSomeEffects();         
 }      


回答3:

Since this appears to be among the most upvoted questions of its kind, let me add, after all these years, the ES6 solution using arrow functions:

var std_obj = {
  ...
  displayMe() {
    ...
    var doSomeEffects = () => {
                        ^^^^^^^    ARROW FUNCTION    
      // In an arrow function, the \'this\' pointer is interpreted lexically,
      // so it will refer to the object as desired.
      if (this.activeEffect==\"fade\") { }
    };
    ...    
  }
};


回答4:

There\'s a difference between enclosure variables and \"this\". \"this\" is actually defined by the invoker of the function, while explicit variables remain intact inside the function declaration block known as the enclosure. See the example below:

function myFirstObject(){
    var _this = this;
    this.name = \"myFirstObject\";
    this.getName = function(){
       console.log(\"_this.name = \" + _this.name + \" this.name = \" + this.name);  
    }
}

function mySecondObject(){
    var _this = this;
    this.name = \"mySecondObject\";
    var firstObject = new myFirstObject();
    this.getName = firstObject.getName
}

var secondObject = new mySecondObject();
secondObject.getName();

you can try it out here: http://jsfiddle.net/kSTBy/

What\'s happening in your function is \"doSomeEffects()\", is being called explicitly, this means context or the \"this\" of the function is the window. if \"doSomeEffects\" was a prototype method e.g. this.doSomeEffects on say \"myObject\", then myObject.doSomeEffects() would cause \"this\" to be \"myObject\".



回答5:

To understand this question , try to get the output for the following snippet

var myObject = {
    foo: \"bar\",
    func: function() {
        var self = this;
        console.log(\"outer func:  this.foo = \" + this.foo);
        console.log(\"outer func:  self.foo = \" + self.foo);
        (function() {
            console.log(\"inner func:  this.foo = \" + this.foo);
            console.log(\"inner func:  self.foo = \" + self.foo);
        }());
    }
};
myObject.func();

The above code will output the following to the console:

outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

In the outer function, both this and self refer to myObject and therefore both can properly reference and access foo.

In the inner function, though, this no longer refers to myObject. As a result, this.foo is undefined in the inner function, whereas the reference to the local variable self remains in scope and is accessible there. (Prior to ECMA 5, this in the inner function would refer to the global window object; whereas, as of ECMA 5, this in the inner function would be undefined.)



回答6:

As explained by Kyle, you could use call or apply to specify this within the function:

Here is that concept applied to your code:

var std_obj = {
    options: {
        rows: 0,
        cols: 0
    },
    activeEffect: \"none\",
    displayMe: function() {

        // the \'this\' pointer is referring to the std_obj
        if (this.activeEffect == \"fade\") {}

        var doSomeEffects = function() {
            // the \'this\' pointer is referring to the window obj, why?
            if (this.activeEffect == \"fade\") {}
        }

        doSomeEffects.apply(this,[]);
    }
};

std_obj.displayMe();

JsFiddle



回答7:

you can also do it by .bind() method,

var std_obj = {
  options : { rows: 0, cols: 0 },
  activeEffect : \"none\", 

  displayMe : function() {

    // the \'this\' pointer is referring to the std_obj
    if (this.activeEffect==\"fade\") { }

    var doSomeEffects = function() {

      // now \'this\' pointer is referring to the std_obj when calling by bound function
      if (this.activeEffect==\"fade\") { }
          alert(this.activeEffect);
    }

    var newBoundFunction= doSomeEffects.bind(std_obj);
      newBoundFunction();   
  }
};

we can actually set the value of this explicitly with call(), bind(), and apply() . The three are very similar, but it’s important to understand the minor differences.

Call and Apply are each invoked immediately. Call takes any number of parameters: this, followed by the additional arguments. Apply takes only two parameters: this, followed by an array of the additional arguments.

You following me still? An example should make this clearer. Look at the code below. We’re trying to add numbers. Copy this into your browser console and call the function.

function add(c, d) {
  console.log(this.a + this.b + c + d);
}
add(3,4);
// logs => NaN

The add function logs NaN (not a number). That’s because this.a and this.b are undefined. They don’t exist. And you can’t add a number to something that is undefined.

Lets introduce an object to the equation. We can use call() and apply() to call the function with our object:

function add(c, d) {
  console.log(this.a + this.b + c + d);
}
var ten = {a: 1, b: 2};
add.call(ten, 3, 4);
// logs => 10
add.apply(ten, [3,4]);
// logs => 10

When we use add.call() the first parameter is what this should be bound to. The subsequent parameters are passed into the function we are calling. Thus, in add() , this.a refers to ten.a and this.b refers to ten.b and we get 1+2+3+4 returned, or 10.

add.apply() is similar. The first parameter is what this should be bound to. The subsequent parameter is an array of arguments to be used in the function.

What about Bind? The parameters in bind() are identical to call() but bind() is not invoked immediately. Instead, bind() returns a function with the context of this bound already. Because of this, bind() is useful when we don’t know all of our arguments up front. Again, an example should help with your understanding:

var small = {
  a: 1,
  go: function(b,c,d){
    console.log(this.a+b+c+d);
  }
}
var large = {
  a: 100
}

Copy the above into your console. Then call

small.go(2,3,4);
// logs 1+2+3+4 => 10

Cool. Nothing new here. But, what if we want to use the value of large.a instead? We can use call/apply:

small.go.call(large,2,3,4);
// logs 100+2+3+4 => 109

Now, what if we don’t know all 3 arguments yet? We can use bind:

var bindTest = small.go.bind(large,2);

If we console.log our variable above, bindTest , we can see what we’re working with

console.log(bindTest);
// logs => function (b,c,d){console.log(this.a+b+c+d);}

Remember, with bind a function is returned that already has this bound! So our this has been successfully bound to our large object. We’ve also already passed in our second argument as the number 2. Later, when know the rest of the arguments we can pass them in:

bindTest(3,4);
// logs 100+2+3+4 => 109

For clarity, here is all of the code together in one block. Look it over, and copy it into your console to really understand what is happening!

var small = {
  a: 1,
  go: function(b,c,d){
    console.log(this.a+b+c+d);
  }
}
var large = {
  a: 100
}
small.go(2,3,4);
// logs 1+2+3+4 => 10
var bindTest = small.go.bind(large,2);
console.log(bindTest);
// logs => function (b,c,d){console.log(this.a+b+c+d);}
bindTest(3,4);
// logs 100+2+3+4 => 109

Remember a few things:

The value of this is usually determined by a functions execution context.

In the global scope, this refers to the global object (the window object).

When the new keyword is used(a constructor), this is bound to the new object being created.

We can set the value of this explicitly with call(), bind(), and apply().

Arrow Functions don’t bind this — instead this is bound lexically (i.e. based on the original context)