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

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

In Javascript, why is the “this” operator inconsistent?

在JavaScript中,"this"运算符可以在不同的场景下引用不同的东西。

通常在JavaScript"对象"内的方法中,它引用当前对象。

但是当用作回调时,它将成为对调用对象的引用。

我发现这会导致代码出现问题,因为如果你在JavaScript"对象"中使用一个方法作为回调函数,你无法判断"this"是指当前的"对象"还是"this"是指 调用对象。

有人可以澄清如何解决这个问题的用法和最佳实践吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   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() {
             ;
            }
        }

关于最佳实践的快速建议,然后我就这个变量的魔法喋喋不休。如果你想在Javascript中使用面向对象编程(OOP)来密切反映更传统/经典的继承模式,那么选择一个框架,学习它的怪癖,不要试图变得聪明。如果你想变得聪明,学习javascript作为一种功能语言,并避免考虑像类这样的事情。

这引出了关于Javascript的最重要的事情之一,并在没有意义时重复自己。 Javascript没有类。如果看起来像一个类,这是一个聪明的把戏。 Javascript有对象(不需要嘲弄引用)和函数。 (这不是100%准确,函数只是对象,但将它们视为单独的东西有时会有所帮助)

此变量附加到函数。无论何时调用函数,都会给出一定的值,具体取决于您调用函数的方式。这通常称为调用模式。

有四种方法可以在javascript中调用函数。您可以将函数作为方法,函数,构造函数和apply来调用。

作为一种方法

方法是附加到对象的函数

1
2
3
4
var foo = {};
foo.someMethod = function(){
    alert(this);
}

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

作为一个功能

如果你有一个独立的函数,这个变量将被绑定到"全局"对象,几乎总是在浏览器的上下文中的窗口对象。

1
2
3
4
 var foo = function(){
    alert(this);
 }
 foo();

这可能是你绊倒的,但不要感觉不好。很多人认为这是一个糟糕的设计决定。由于回调是作为函数而不是作为方法调用的,因此您可以看到看似不一致的行为。

很多人通过这样做来解决这个问题

1
2
3
4
5
6
7
var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

您定义一个指向此的变量。 Closure(一个它自己的主题)保持that,所以如果你把bar称为回调,它仍然有一个引用。

作为构造函数

您还可以将函数作为构造函数调用。根据您正在使用的命名约定(TestObject),这也可能是您正在做的事情,也是您绊倒的原因。

使用new关键字将函数作为构造函数调用。

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

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

有些人认为构造函数/ new关键字是Java /传统OOP程序员抛出的骨骼,作为创建类似于类的东西的方法。

使用Apply方法。

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

1
2
3
4
5
6
7
function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

在JavaScript中,this始终引用调用正在执行的函数的对象。因此,如果函数被用作事件处理程序,this将引用触发事件的节点。但如果你有一个对象并在其上调用一个函数,如:

1
myObject.myFunction();

然后myFunction内的this将引用myObject。是否有意义?

要解决它,你需要使用闭包。您可以按如下方式更改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() {
         ;
    }
}


this对应于函数调用的上下文。对于未作为对象一部分调用的函数(无.运算符),这是全局上下文(网页中的window)。对于称为对象方法的函数(通过。运算符),它是对象。

但是,无论你想要什么,你都可以做到。所有函数都有.call()和.apply()方法,可用于使用自定义上下文调用它们。所以,如果我像这样建立一个智利对象:

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

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

1
booga

但如果我想,我可以采取并真正搞砸它:

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

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


如果您正在使用javascript框架,可能有一个方便的方法来处理这个问题。例如,在Prototype中,您可以调用方法并将其范围限定为特定的"this"对象:

1
2
var myObject = new TestObject();
myObject.firstMethod.bind(myObject);

注意:bind()返回一个函数,因此您也可以使用它来预先调整类中的回调:

1
callBack.bind(this);

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


如果你正在使用Prototype,你可以使用bind()和bindAsEventListener()来解决这个问题。


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

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

使用示例代码,它看起来像:

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


一旦从其他上下文调用回调方法,我通常会使用我称之为回调上下文的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}
}

我相信这可能是由于[闭包]的想法(http://en.wikipedia.org/wiki/Closure_(computer_science)如何在Javascript中工作。

我自己就是要掌握封口。阅读链接的维基百科文章。

这是另一篇有更多信息的文章。

那里的任何人都能证实这一点吗?


推荐阅读