可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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);
}