“this”关键字在JavaScript中原型对象中返回窗口对象?

16

我在一个类中有以下函数:

MyClass.prototype.myFunction = function(item, args) 
{       
    console.log(this);
}

这个函数是从一个我无法改变的外部库中调用的。当它被调用时,控制台将“this”记录为窗口对象,而不是实际的实例化对象。在搜索stackoverflow后,我找到了以下引用:

this根据方法的调用方式而设置,并不是按照方法的编写方式来设置的。因此,对于obj.method(),method()内的this将设置为obj。对于obj.method.call(x),method()内的this将设置为x。它是由调用方式确定的。这也意味着,如果您将其作为回调传递给例如onclick,则this将设置为全局窗口对象,而不是您所期望的。

我假设这就是问题所在,我无法改变它的调用方式。我的问题是,是否有任何方法可以获取包含它的对象的实例,而不管它是如何被调用的?


如果可以的话,请使用“new”关键字来实例化类。 - Matthew Blancarte
4个回答

8
这是Javascript中的一个常见误解。很容易认为它们的行为类似于其他语言中的扩展方法,但在Javascript中,改变this的上下文非常容易,经常会发生意外情况。
因此:
MyClass.prototype.myFunction = function(args) 
{
    // You expect [this] to refer to an instance of MyClass
    this.somePropertyOfMyClass;
};

然后你可以通过以下方式调用:

var x = new MyClass();
x.myFunction(args)

然而,在Javascript中调用函数的方式可能会改变this所指向的对象:
var y = somethingElse();
x.myFunction.call(y, args); // now in myFunction [this] refers to y

更可能的是,许多库使用“this”上下文进行链接和事件操作,这样易于出错。例如在jQuery中:
var $thing = $('.selector');
$thing.click(x.myFunction); // now in myFunction [this] refers to $thing

也许对于编写jQuery的人来说,以这种方式调用x.myFunction可能并不明显,但实际上会导致它出错。他们可以通过以下方式进行解决(假定他们知道实现方法):

$thing.click(function() { x.myFunction(); }); 

如果你希望你的MyClass被调用时具有弹性,那么就不要使用prototype - 而是使用对象的属性:

function MyClass() {
    var self = this;
    // ...
    this.myFunction = function(args) 
    {
        // [self] will always refer to the current instance of MyClass
        self.somePropertyOfMyClass;
    };
}

请注意,现代浏览器的JavaScript引擎在优化这些类型的调用方面非常出色,因此我不会仅将prototype用作优化,除非您已确定需要额外的性能。

为什么原型应该提供额外的性能。原型在对象的属性之后被调用,只有在对象没有这样的属性的情况下才会被调用?当您拥有多个对象时可以节省RAM,但这并不意味着您一定会获得性能提升。 - Ini

4

假设一个函数的引用被传递给另一个函数进行调用,而另一个函数类似于:

function otherFunction(args, fn) {
    ...
    fn();
    ...
}

为确保方法获取所需的this,您可以这样做:
// Create a local variable referencing the `this` you want
var instance = this;

// Pass a function that has a closure to the variable
// and sets the appropriate this in the call
otherFunction(args, function(){myMethod.call(instance)})

现在,在我的方法中,this将是instance引用的任何内容。请注意,如果在调用otherFunction之后并在调用该方法之前更改了instance的值,则myMethod将获取该新值。
如果这是一个问题,您也可以处理它。
噢,在构造函数中,您还可以通过为每个实例提供自己的具有对该实例进行闭包的方法来处理此问题:
function MyObj(name) {
  var instance = this;
  instance.name = name;
  instance.getName = function() {
    return instance.name;
  }
}

var anObj = new MyObj('fred');

// Call as a method of anObj
alert(anObj.getName());  // fred 

// Pass method as a reference
var x = anObj.getName;

// Unqualified call
alert(x());  // fred    

4
规则是:
当从函数中调用 this 时,它指向全局对象(通常为 Window)。
当从方法中调用 this 时,它指向拥有者对象(即使用该方法的当前对象)。
示例:
function Tryme() {

    var that = this

    function func2() {
        console.log("'this' from 'func2':", this)
    }

    function func3() {
        console.log("'that' from 'func3':", that)
    }

    this.method1 = function () {
        console.log("'this' from 'method1':", this)
    }

    this.method2 = function () {
        func2()
    }

    this.method3 = function () {
        func3()
    }
}

myinstance = new Tryme()

myinstance.method1() // displays the object 'myinstance'
myinstance.method2() // displays the object 'Window'
myinstance.method3() // displays the object 'myinstance'

这个例子发生了什么?

当你调用 .method1() 并显示 this 时,你当前在对象 myinstance 的方法中。因此,this 将引用对象本身(即 myinstance)。

当你调用 .method2() 时,该方法将在对象 myinstance 中调用一个名为 func2() 的本地函数。函数 func2() 是一个函数而不是一个方法,所以 this 指的是全局对象 Window

当你调用 .method3() 时,该方法将在对象 myinstance 中调用一个名为 func3() 的本地函数。函数 func3() 是一个函数而不是一个方法,所以 this 指的是全局对象 Window(就像 func2() 中那样)。但在 func3() 中,我们正在显示 that 的内容,它是 myinstance 对象内的局部变量。 that 是一个对象,它被初始化为 this 的值,当 this 引用拥有者对象(即 myinstance)时。在 JavaScript 中,如果你用 object1 的值初始化 object2object2 = object1),那么当你改变一个对象的值时,另一个对象的值也会改变。因此,that 将始终引用 this 的值,即 myinstance 的值,即使 this 的值发生了更改。 记住this 是一个关键字,它引用一个对象,不是一个变量。

在你的情况下发生了什么?

你从一个函数中调用了 this,所以 this 指的是全局对象(Window)。

@Keith 建议可以做些什么,即创建一个对象,在该对象中创建一个等于感兴趣的实例的 self(或 that)对象,然后在该函数中使用 self 对象(它将引用你感兴趣的 this 对象)。

更多信息

这里:https://www.w3schools.com/js/js_this.asp
这里:https://crockford.com/javascript/private.html


1
一个解决方法是为每个实例都创建myFunction()的副本,即在构造函数中创建它,而不是将其放在原型上,因为这样你可以将对this的引用存储为构造函数中的局部变量:
function MyClass() {
    var self = this;
    this.myFunction = function(item, args) {
        // use self instead of this
        console.log(self);
    };
    // other constructor code here
}

当然,为每个实例创建一个函数会使用更多的内存 - 这可能是一个问题,也可能不是,这取决于您的函数的大小。一种折衷方案是在构造函数中放置函数的包装器,并在原型上定义实际函数:
function MyClass() {
    var self = this;
    this.myFunction = function(item, args) {
        return self.wrappedMyFunction(item, args);
    };

    // your other constructor code here
}    
MyClass.prototype.wrappedMyFunction = function(item, args) 
{       
    console.log(this);
}

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