在Javascript中,“this”运算符为什么不一致?

30
在JavaScript中,“this”运算符在不同情况下可能指代不同的内容。
通常在JavaScript“对象”中的方法中,“this”指代当前对象。
但是当作为回调函数使用时,它变成了对调用对象的引用。
我发现这会导致代码中出现问题,因为如果您将JavaScript“对象”中的方法用作回调函数,您无法确定“this”是指当前“对象”还是指调用对象。
是否有人能够阐明如何避免这个问题的使用和最佳实践?
   function TestObject() {
            TestObject.prototype.firstMethod = function(){
                      this.callback();
                      YAHOO.util.Connect.asyncRequest(method, uri, callBack);

            }

            TestObject.prototype.callBack = function(o){
              // do something with "this"
              //when method is called directly, "this" resolves to the current object
              //when invoked by the asyncRequest callback, "this" is not the current object
              //what design patterns can make this consistent?
              this.secondMethod();
            }
            TestObject.prototype.secondMethod = function() {
             alert('test');
            }
        }

这里有一个基于上下文的神秘“this”行为的很好的解释 链接 - RBT
8个回答

88

在我详细介绍魔术变量this之前,给出一些关于最佳实践的快速建议。如果你希望在Javascript中实现面向对象编程(OOP),并且希望它紧密地反映更传统/经典的继承模式,请选择一个框架,学习其特点,并且不要尝试聪明地做什么。如果你想聪明一点,请将Javascript视为函数式语言,并且避免考虑类似类的东西。

这就提醒我们了解Javascript时需要铭记在心的最重要的一件事,并且在它不合理时要对自己反复强调。Javascript没有类。如果某些东西看起来像类,那只是一个聪明的技巧。Javascript拥有对象(不需要贬义引号)和函数。(这并不完全准确,因为函数只是对象,但有时将它们视为单独的东西可能会有所帮助)

this变量附加到函数上。每当你调用一个函数时,this都会被赋予一个特定的值,具体取决于你如何调用函数。这通常称为调用模式。

Javascript中有四种调用函数的方法。你可以将函数作为方法函数构造函数或使用apply来调用。

作为一个方法

方法是附加到对象上的函数

var foo = {};
foo.someMethod = function(){
    alert(this);
}

当作为方法调用时,this将绑定到函数/方法所属的对象。在这个例子中,this将被绑定到foo。

作为函数

如果你有一个独立的函数,this变量将绑定到“全局”对象,几乎总是在浏览器环境中的window对象。

 var foo = function(){
    alert(this);
 }
 foo();

这可能是你被绊倒的地方,但不要感到难过。许多人认为这是一个糟糕的设计决策。由于回调作为函数而非方法被调用,这就是为什么你看到的表现似乎不一致。

许多人通过做类似于这样的事情来解决问题。

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

你定义了一个指向this的变量that。闭包(一个独立的话题)使得that始终保留,所以如果你将它作为回调函数调用,它仍然具有参考值。

作为构造函数

你也可以将一个函数作为构造函数调用。根据你正在使用的命名约定(TestObject),这也可能是你正在做的事情,并且可能会导致出现问题

你可以使用new关键字来将一个函数作为构造函数调用。

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

当作为构造函数调用时,会创建一个新的对象,并将this绑定到该对象。同样,如果您有内部函数并且它们用作回调,则将它们作为函数调用,this将绑定到全局对象。使用var that = this;技巧/模式。

有些人认为构造函数/new关键字是向Java /传统OOP程序员提供的一种方式,以创建类似类的东西。

使用apply方法。

最后,每个函数都有一个名为apply的方法(是的,在Javascript中函数是对象)。Apply允许您确定this的值,并且还允许您传入参数数组。这里有一个无用的例子。

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

8
也许值得补充一下:还有一个叫做call的方法,它的功能类似于apply,但参数不是作为数组传递。 - eyelidlessness
@Alan,非常好的答案,但值得一提的是bind - João Pimentel Ferreira

12
在 JavaScript 中,this 总是指向调用正在执行的函数的对象。因此,如果函数被用作事件处理程序,this 将引用触发事件的节点。但是,如果你有一个对象并在其上调用一个函数,例如:
myObject.myFunction();

那么,在myFunction中的this将会指向myObject。这样说通了吗?

为了解决这个问题,您需要使用闭包。您可以按照以下方式更改代码:

function TestObject() {
    TestObject.prototype.firstMethod = function(){
        this.callback();
        YAHOO.util.Connect.asyncRequest(method, uri, callBack);
    }            

    var that = this;
    TestObject.prototype.callBack = function(o){
        that.secondMethod();
    }

    TestObject.prototype.secondMethod = function() {
         alert('test');
    }
}

2
以下是有关 JavaScript 闭包的一份非常好的演示文稿链接:http://tinyurl.com/5mrpjv。 - Jared
1
请记住,“在JavaScript中,‘this’始终指代调用正在执行的函数的对象”这个说法在技术上是不正确的。这可能取决于如何调用函数,有四种不同的上下文。 - Alana Storm
https://dev59.com/bHVD5IYBdhLWcg3wHn2d - Alana Storm
函数原型不应该放在构造函数之外吗?或者用“that”替换“TestObject.prototype”。 - Thai

3

this代表函数调用的上下文。对于不作为对象一部分调用的函数(没有.运算符),它是全局上下文(在Web页面中为window)。对于通过.运算符调用的对象方法,它是该对象。

但是,您可以根据需要进行更改。所有函数都有.call()和.apply()方法,可用于使用自定义上下文调用它们。因此,如果我像这样设置一个名为Chile的对象:

var Chile = { name: 'booga', stuff: function() { console.log(this.name); } };

如果调用Chile.stuff(),它会产生明显的结果:

booga

但是如果我想,我可以拿它来玩得很疯

Chile.stuff.apply({ name: 'supercalifragilistic' });

实际上,这非常有用...


1
如果您正在使用JavaScript框架,可能有一个方便的方法来处理这个问题。例如,在Prototype中,您可以调用一个方法并将其范围限定在特定的“this”对象上:
var myObject = new TestObject();
myObject.firstMethod.bind(myObject);

注意:bind()返回一个函数,因此您也可以使用它来在类内部预先定义回调函数的作用域:
callBack.bind(this);

http://www.prototypejs.org/api/function/bind


0

当回调方法从其他上下文中调用时,我通常使用称为回调上下文的东西:

var ctx = function CallbackContext()
{
_callbackSender
...
}

function DoCallback(_sender, delegate, callbackFunc)
{
 ctx = _callbackSender = _sender;
 delegate();
}

function TestObject()
{
   test = function()
  {
     DoCallback(otherFunc, callbackHandler);
  }

  callbackHandler = function()
{
 ctx._callbackSender;
 //or this = ctx._callbacjHandler;
}
}

0

0

你也可以使用 Function.Apply(thisArg, argsArray)...其中 thisArg 确定函数内部的 this 值...第二个参数是一个可选的参数数组,你也可以将其传递给函数。

如果你不打算使用第二个参数,请不要传递任何内容。如果你将 null(或任何不是数组的内容)传递给 function.apply() 的第二个参数,Internet Explorer 将抛出 TypeError...

以你提供的示例代码为例,它应该是这样的:

YAHOO.util.Connect.asyncRequest(method, uri, callBack.Apply(this));

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接