The 'this' keyword returns the window object within an object's prototype in Javascript?

对着背影说爱祢 提交于 2019-12-18 04:33:47

问题


I have the following function in a class:

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

This function is called from an external library that I don't have access to change. When it's called, the console is logging "this" as the window object instead of the actual instanced object. Upon searching stackoverflow I found this quote:

this is set according to how the method is called, and not according to how the method is written. So for obj.method(), this will be set to obj inside of method(). For obj.method.call(x), this inside of method() will be set to x. It is determined by how it's called. What that also means is that if you pass it as a callback to e.g. onclick, this will be set to the global window object rather than what you expect.

I'm assuming this is what is going on and I can't change the way it's called. My question is, is there anyway then to get the instance of the object its in regardless of how it's called?


回答1:


This is a common confusion with Javascript. It's easy to think of them as behaving like extension methods do in other languages, but in Javascript it's so easy to change the context of this that it is often done by accident.

So:

MyClass.prototype.myFunction = function(args) 
{
    // You expect [this] to refer to an instance of MyClass
    this.somePropertyOfMyClass;
};

Then you can call this with:

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

However the way that a function is called in Javascript can change what this refers to:

var y = somethingElse();
x.myFunction.call(y, args); // now in myFunction [this] refers to y

More likely is that a lot of libraries use the this context for chaining and events - making mistakes easy to make. For instance in jQuery:

var $thing = $('.selector');
$thing.click(x.myFunction); // now in myFunction [this] refers to $thing

It probably isn't obvious to the person writing the jQuery that calling x.myFunction in this way will break it. They can workaround that (assuming that they know about the implementation) with:

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

If you want your MyClass to be resilient to being called like this then don't use prototype - instead use a property of the object:

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

Note that the more modern browser Javascript engines are pretty good as optimising these kinds of calls, so I wouldn't use the prototype just as an optimisation unless you already identified that you need the additional performance.




回答2:


Presumably a function reference is passed to some other function to call and that the other function is something like:

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

To ensure the method gets the this it needs, you can do:

// 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)})

Now this in myMethod will be whatever instance references. Note that if you change the value of instance after the call to otherFunction and before the method is called, myMethod will get that new value.

You can deal with that too if it's an issue.

Oh, you can also deal with this in the constructor by giving each instance it's own method that has a closure to the instance:

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    



回答3:


The rule is:

When this is called from a function, it refers to the global objet (usually Window).
When this is called from a method, it refers to the owner object (ie. the current object in which you are using the method).

Example:

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'

What happens in the example?

When you call .method1() and you display this, you are currently in a method of the object myinstance. So this will refer to the object itself (ie. myinstance).

When you call .method2(), this method will call a local function within the object myinstance called func2(). This function func2() is a function and not a method, so this refers to the global object Window.

When you call .method3(), this method will call a local function within the object myinstance called func3(). This function func3() is a function and not a method, so this refers to the global object Window (as in func2()). But in func3(), we are displaying the content of that, which is a local variable within the myinstance object. that is an object which has been initialized with the value of this when this was referring to the owner object (ie. myinstance). In JavaScript, if you initialize object2 with the value of object1 (object2 = object1), then when you change the value of one object, the value of the other object will change too. So that will always refer to the value of this which is the one of myinstance, even when the value of this changes. Remember: this is a keyword which refers to an object, it's not a variable.

What happened in your case?

You called this from a function, so this refers to the global object (Window).

@Keith suggested what could be done, ie. creating an object in which you will create a self (or that) object which equals the this of the instance you're interested in, then using the self object in the function (which will refer to the this object you're interested in).

More information

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




回答4:


One fix would be to have a copy of myFunction() for each instance, i.e., create it in the constructor instead of having it on the prototype, because then you could have a reference to this stored as a local variable in the constructor:

function MyClass() {
    var self = this;
    this.myFunction = function(item, args) {
        // use self instead of this
        console.log(self);
    };
    // other constructor code here
}

But of course creating a function for every instance uses more memory - which may or may not be a problem depending on the size of your function. A compromise might be to put a wrapper for your function inside the constructor and define the actual function on the prototype:

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


来源:https://stackoverflow.com/questions/13224620/the-this-keyword-returns-the-window-object-within-an-objects-prototype-in-jav

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!