嵌套函数中的Javascript“this”指针

时间:2012-03-10 04:55:26

标签: javascript function nested this

我有一个关于如何在嵌套函数场景中处理“this”指针的问题。

假设我将以下示例代码插入到网页中。当我调用嵌套函数“doSomeEffects()”时出错。我检查了Firebug,它表明当我在嵌套函数中时,“this”指针实际上指向全局“窗口”对象 - 我没想到。我一定不能理解正确的东西,因为我认为既然我在对象的函数中声明了嵌套函数,它应该具有与函数相关的“局部”范围(即“this”指针将指向对象本身就像它是如何在我的第一个“如果”声明中。

任何指针(没有双关语)都会受到赞赏。

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();

8 个答案:

答案 0 :(得分:108)

在JavaScript中,this对象实际上是基于如何进行函数调用。

通常,有三种方法可以设置this对象:

  1. someThing.someFunction(arg1, arg2, argN)
  2. someFunction.call(someThing, arg1, arg2, argN)
  3. someFunction.apply(someThing, [arg1, arg2, argN])
  4. 在上述所有示例中,this对象将为someThing。 调用没有前导父对象的函数通常会获得 global 对象,在大多数浏览器中这意味着window对象。

答案 1 :(得分:28)

this不是闭包范围的一部分,它可以被认为是在调用站点绑定的函数的附加参数。如果该方法未作为方法调用,则全局对象将作为this传递。在浏览器中,全局对象与window相同。例如,请考虑以下功能,

function someFunction() {
}

和以下对象,

var obj = { someFunction: someFunction };

如果使用方法语法(如

)调用该函数
obj.someFunciton();

然后this绑定到obj

如果直接调用someFunction(),例如

someFunction();

然后this绑定到全局对象,即window

最常见的解决方法是将其捕获到闭包中,例如,

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();         
 }      

答案 2 :(得分:10)

机箱变量和“this”之间存在差异。 “this”实际上是由函数的调用者定义的,而显式变量在称为机箱的函数声明块中保持不变。请参阅以下示例:

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();

你可以在这里试试: http://jsfiddle.net/kSTBy/

你的函数中发生的事情是“doSomeEffects()”,被显式调用,这意味着上下文或函数的“this”是窗口。如果“doSomeEffects”是原型方法,例如this.doSomeEffects on say“myObject”,然后myObject.doSomeEffects()会导致“this”成为“myObject”。

答案 3 :(得分:5)

要了解此问题,请尝试获取以下代码段的输出

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();

以上代码将以下内容输出到控制台:

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

在外部函数中,this和self都引用myObject,因此两者都可以正确引用和访问foo。

但是在内部函数中,这不再是指myObject。因此,this.foo在内部函数中是未定义的,而对局部变量self的引用仍然在范围内并且可以在那里访问。 (在ECMA 5之前,内部函数中的这将引用全局窗口对象;而从ECMA 5开始,内部函数中的这将是未定义的。)

答案 4 :(得分:3)

正如Kyle所解释的那样,您可以使用callapply在函数中指定this

以下是适用于您的代码的概念:

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

答案 5 :(得分:3)

您也可以通过.bind()方法

来完成
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();   
  }
};

我们实际上可以使用call(),bind()和apply()显式设置它的值。这三者非常相似,但了解细微差别非常重要。

立即调用Call和Apply。调用需要任意数量的参数:this,后跟其他参数。 Apply只接受两个参数:this,后跟一个附加参数数组。

你还跟着我吗?一个例子应该使这更清楚。看下面的代码。我们正试图添加数字。将其复制到浏览器控制台并调用该函数。

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

添加功能记录NaN(不是数字)。那是因为this.a和this.b未定义。它们不存在。并且您无法为未定义的内容添加数字。

让我们向等式引入一个对象。我们可以使用call()和apply()来调用我们的对象函数:

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

当我们使用add.call()时,第一个参数应该绑定到这个参数。后续参数将传递给我们调用的函数。因此,在add()中,this.a引用ten.a,this.b引用ten.b,我们得到1 + 2 + 3 + 4返回,或10。

add.apply()是类似的。第一个参数是应该绑定的内容。后续参数是要在函数中使用的参数数组。

Bind怎么样? bind()中的参数与call()相同,但不立即调用bind()。相反,bind()返回一个具有此绑定上下文的函数。因此,当我们事先不知道所有参数时,bind()很有用。再一次,一个例子应该有助于你的理解:

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

冷却。这里没什么新鲜的。但是,如果我们想要使用large.a的值呢?我们可以使用call / apply:

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

现在,如果我们还不知道所有3个参数呢?我们可以使用bind:

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

如果我们上面的console.log我们的变量bindTest,我们可以看到我们正在使用的

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

请记住,使用bind返回已经有此绑定的函数!所以我们已经成功地绑定了我们的大对象。我们已经将第二个参数传递给了数字2.后来,当知道了其余的参数时,我们可以将它们传递给它们:

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

为清楚起见,这里是一个块中的所有代码。仔细查看,并将其复制到您的控制台,以真正了解正在发生的事情!

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

请记住以下几点:

这个值通常由函数执行上下文决定。

在全局范围内,这指的是全局对象(窗口对象)。

当使用new关键字(构造函数)时,它绑定到正在创建的新对象。

我们可以使用call(),bind()和apply()来显式设置它的值。

箭头函数不绑定它 - 而是以词法方式绑定(即基于原始上下文)

答案 6 :(得分:0)

我还收到警告“ 通过此字段可能对类字段的引用无效

class MyListItem {
    constructor(labelPrm) {
        this._flagActive = false;
        this._myLabel = labelPrm;
        labelPrm.addEventListener('click', ()=>{ this.onDropdownListsElementClick();}, false);
    }

    get myLabel() {
        return this._myLabel
    }
    get flagActive(){
        return this._flagActive;
    }

    onDropdownListsElementClick(){console.log("Now'this'refers to the MyListItem itself")}}//end of the class

答案 7 :(得分:0)

由于没有提及,我会提到使用.bind()是一种解决方案-


        doSomeEffects=doSomeEffect.bind(this);
        doSomeEffects();   
      }
    };

    std_obj.displayMe();

这是一个更简单的示例-

bad = { 
  name:'NAME', 
  z : function() { 
    function x() { console.log(this.name); }; 
    x() 
  } 
};
bad.z() // prints 'undefined'

good = { 
  name:'NAME', 
  z : function() { 
    function x() { console.log(this.name); }; 
    x=x.bind(this);
    x();
  } 
};
good.z() // prints 'NAME'

确实,使用箭头功能=>看起来更流畅,并且对于程序员来说很容易。但是,应该记住,与简单地通过{{1将函数的this与指针关联起来相比,词汇范围可能需要更多的处理和内存工作来建立和维护该词汇范围。 }}。

在JS中开发类的部分好处是,提供了一种方法,使.bind()更可靠地存在和可用,从而摆脱了函数编程和词法范围,从而减少了开销。

From MDN

性能注意事项 如果不需要为特定任务关闭文件,则在其他函数中不必要地创建函数是不明智的,因为这会在处理速度和内存消耗方面对脚本性能产生负面影响。