在 JavaScript 原型函数中保留对“ this”的引用

我刚刚开始使用原型 JavaScript,并且在解决如何在作用域发生变化时从原型函数内部保留对主对象的 this引用方面遇到了麻烦。让我举例说明我的意思(我在这里使用 jQuery) :

MyClass = function() {
this.element = $('#element');
this.myValue = 'something';


// some more code
}


MyClass.prototype.myfunc = function() {
// at this point, "this" refers to the instance of MyClass


this.element.click(function() {
// at this point, "this" refers to the DOM element
// but what if I want to access the original "this.myValue"?
});
}


new MyClass();

我知道我可以通过在 myfunc的开头这样做来保留对主对象的引用:

var myThis = this;

然后使用 myThis.myValue访问主对象的属性。但是当我在 MyClass上有一大堆原型函数时会发生什么呢?我必须在每一个的开头保存对 this的引用吗?看来应该有更干净的办法。那像这样的情况呢:

MyClass = function() {
this.elements $('.elements');
this.myValue = 'something';


this.elements.each(this.doSomething);
}


MyClass.prototype.doSomething = function() {
// operate on the element
}


new MyClass();

在这种情况下,我不能用 var myThis = this;创建对主对象的引用,因为在 doSomething的上下文中,甚至 this的原始值也是 jQuery对象,而不是 MyClass对象。

有人建议我使用一个全局变量来保存对原始 this的引用,但对我来说这似乎是一个非常糟糕的主意。我不想污染全局名称空间,这似乎会阻止我实例化两个不同的 MyClass对象,而不让它们相互干扰。

有什么建议吗? 有没有一个干净的方法来做我想要做的事情? 或者我的整个设计模式有缺陷吗?

70336 次浏览

Since you're using jQuery, it's worth noting that this is already maintained by jQuery itself:

$("li").each(function(j,o){
$("span", o).each(function(x,y){
alert(o + " " + y);
});
});

In this example, o represents the li, whereas y represents the child span. And with $.click(), you can get the scope from the event object:

$("li").click(function(e){
$("span", this).each(function(i,o){
alert(e.target + " " + o);
});
});

Where e.target represents the li, and o represents the child span.

You can set the scope by using the call() and apply() functions

For preserving the context, the bind method is really useful, it's now part of the recently released ECMAScript 5th Edition Specification, the implementation of this function is simple (only 8 lines long):

// The .bind method from Prototype.js
if (!Function.prototype.bind) { // check if native implementation available
Function.prototype.bind = function(){
var fn = this, args = Array.prototype.slice.call(arguments),
object = args.shift();
return function(){
return fn.apply(object,
args.concat(Array.prototype.slice.call(arguments)));
};
};
}

And you could use it, in your example like this:

MyClass.prototype.myfunc = function() {


this.element.click((function() {
// ...
}).bind(this));
};

Another example:

var obj = {
test: 'obj test',
fx: function() {
alert(this.test + '\n' + Array.prototype.slice.call(arguments).join());
}
};


var test = "Global test";
var fx1 = obj.fx;
var fx2 = obj.fx.bind(obj, 1, 2, 3);


fx1(1,2);
fx2(4, 5);

In this second example we can observe more about the behavior of bind.

It basically generates a new function, that will be the responsible of calling our function, preserving the function context (this value), that is defined as the first argument of bind.

The rest of the arguments are simply passed to our function.

Note in this example that the function fx1, is invoked without any object context (obj.method() ), just as a simple function call, in this type of invokation, the this keyword inside will refer to the Global object, it will alert "global test".

Now, the fx2 is the new function that the bind method generated, it will call our function preserving the context and correctly passing the arguments, it will alert "obj test 1, 2, 3, 4, 5" because we invoked it adding the two additionally arguments, it already had binded the first three.

For your last MyClass example, you could do this:

var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });

In the function that is passed to each, this refers to a jQuery object, as you already know. If inside that function you get the doSomething function from myThis, and then call the apply method on that function with the arguments array (see the apply function and the this0), then this will be set to myThis in doSomething.

Another solution (and my favorite way in jQuery) is to use the jQuery provided 'e.data' to pass 'this'. Then you can do this:

this.element.bind('click', this, function(e) {
e.data.myValue; //e.data now references the 'this' that you want
});

You can create a reference to the this object or you can use the with (this) method. The later is extremely useful when your using event handlers and you have no way of passing in a reference.

MyClass = function() {
// More code here ...
}


MyClass.prototype.myfunc = function() {
// Create a reference
var obj = this;
this.element.click(function() {
// "obj" refers to the original class instance
with (this){
// "this" now also refers to the original class instance
}
});


}

I realize this is an old thread, but I have a solution that is much more elegant, and has few drawbacks apart from the fact that it is not generally done, as I have noticed.

Consider the following:

var f=function(){
var context=this;
}


f.prototype.test=function(){
return context;
}


var fn=new f();
fn.test();
// should return undefined because the prototype definition
// took place outside the scope where 'context' is available

In the function above we defined a local variable (context). We then added a prototypical function (test) that returns the local variable. As you have probably predicted, when we create an instance of this function and then execute the test method, it does not return the local variable because when we defined the prototypical function as a member to our main function, it was outside the scope where the local variable is defined. This is a general problem with creating functions and then adding prototypes to it - you cannot access anything that was created in the scope of the main function.

To create methods that are within the scope of the local variable, we need to directly define them as members of the function and get rid of the prototypical reference:

var f=function(){
var context=this;


this.test=function(){
console.log(context);
return context;
};
}


var fn=new(f);
fn.test();
//should return an object that correctly references 'this'
//in the context of that function;


fn.test().test().test();
//proving that 'this' is the correct reference;

You may be worried that because the methods are not being created prototypically, different instances may not really be data-separated. To demonstrate that they are, consider this:

var f=function(val){
var self=this;
this.chain=function(){
return self;
};


this.checkval=function(){
return val;
};
}


var fn1=new f('first value');
var fn2=new f('second value');


fn1.checkval();
fn1.chain().chain().checkval();
// returns 'first value' indicating that not only does the initiated value remain untouched,
// one can use the internally stored context reference rigorously without losing sight of local variables.


fn2.checkval();
fn2.chain().chain().checkval();
// the fact that this set of tests returns 'second value'
// proves that they are really referencing separate instances

Another way to use this method is to create singletons. More often than not, our javascript functions are not being instantiated more than once. If you know that you will never need a second instance of the same function, then there is a shorthand way to create them. Be warned, however: lint will complain that it is a weird construction, and question your use of the keyword 'new':

fn=new function(val){
var self=this;
this.chain=function(){
return self;
};


this.checkval=function(){
return val;
};
}


fn.checkval();
fn.chain().chain().checkval();

Pro's: The benefits to using this method to create function objects are plentiful.

  • It makes your code easier to read, since it indents the methods of a function object in a way that makes it visually easier to follow.
  • It allows access to the locally defined variables only in methods originally defined in this manner even if you later add prototypical functions or even member functions to the function-object, it cannot access the local variables and whatever functionality or data you store on that level remains safe and inaccessible from anywhere else.
  • It allows a simple and straight-forward way to define singletons.
  • It allows you to store a reference to 'this' and maintain that reference indefinitely.

Con's: There are some drawbacks to using this method. I don't pretend to be comprehensive :)

  • Because the methods are defined as members to the object and not prototypes - inheritance can be achieved using member definition but not prototypical definitions. This is actually incorrect. The same prototypical inheritance can be achieved by acting on f.constructor.prototype.