我刚刚开始使用原型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;
创建对主对象的引用,因为即使this
上下文中doSomething
的原始值为jQuery
} object而不是MyClass
对象。
有人建议我使用全局变量来保存对原始this
的引用,但这对我来说似乎是一个非常糟糕的主意。我不想污染全局命名空间,这似乎会阻止我实例化两个不同的MyClass
对象而不会相互干扰。
有什么建议吗?是否有一种干净的方式去做我想要的事情?或者我的整个设计模式是否有缺陷?
答案 0 :(得分:64)
为了保留上下文,bind
方法非常有用,它现在是最近发布的ECMAScript 5th Edition规范的一部分,这个函数的实现很简单(只有8行):
// 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)));
};
};
}
你可以使用它,在你的例子中这样:
MyClass.prototype.myfunc = function() {
this.element.click((function() {
// ...
}).bind(this));
};
另一个例子:
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);
在第二个例子中,我们可以更多地了解bind
的行为。
它基本上生成一个新函数,负责调用我们的函数,保留函数上下文(this
值),它被定义为bind
的第一个参数。
其余参数只是传递给我们的函数。
请注意,在此示例中,函数fx1
在没有任何对象上下文(obj.method()
)的情况下被调用,就像一个简单的函数调用一样,在这种类型的调用中,里面的this
关键字将引用Global对象,它将提醒“全局测试”。
现在,fx2
是bind
方法生成的新函数,它将调用我们的函数保留上下文并正确传递参数,它将警告“obj test 1,2,3 ,4,5“因为我们调用它添加了两个额外的参数,它已经绑定前三个。
答案 1 :(得分:10)
对于上一个MyClass
示例,您可以这样做:
var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });
在传递给each
的函数中,this
指的是jQuery对象,如您所知。如果在该函数内部,您从doSomething
获得myThis
函数,然后使用arguments数组调用该函数的apply方法(请参阅apply
function和arguments
variable),然后this
中的myThis
将设置为doSomething
。
答案 2 :(得分:7)
我意识到这是一个老线程,但我有一个更优雅的解决方案,除了一般不做的事情之外几乎没有什么缺点,正如我所注意到的那样。
请考虑以下事项:
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
在上面的函数中,我们定义了一个局部变量(context)。然后我们添加了一个返回局部变量的原型函数(test)。正如您可能预测的那样,当我们创建此函数的实例然后执行测试方法时,它不会返回局部变量,因为当我们将原型函数定义为主函数的成员时,它不在局部变量已定义。 这是创建函数然后向其添加原型的一般问题 - 您无法访问在main函数范围内创建的任何内容。
要创建局部变量范围内的方法,我们需要直接将它们定义为函数的成员并摆脱原型参考:
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;
您可能会担心因为这些方法不是原型创建的,所以不同的实例可能实际上并不是数据分离的。为了证明它们是,请考虑这一点:
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
使用此方法的另一种方法是创建单例。通常情况下,我们的javascript函数不会被多次实例化。如果你知道你永远不需要同一个函数的第二个实例,那么有一种创建它们的简便方法。但请注意:lint会抱怨这是一个奇怪的结构,并质疑你使用关键字'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的:强> 使用此方法创建函数对象的好处很多。
<强>反对的:强> 使用这种方法有一些缺点。我并不假装全面:)
f.constructor.prototype
行动可以实现相同的原型继承。 答案 3 :(得分:4)
答案 4 :(得分:1)
由于你正在使用jQuery,值得注意的是jQuery本身已经维护this
:
$("li").each(function(j,o){
$("span", o).each(function(x,y){
alert(o + " " + y);
});
});
在此示例中,o
代表li
,而y
代表孩子span
。使用$.click()
,您可以从event
对象中获取范围:
$("li").click(function(e){
$("span", this).each(function(i,o){
alert(e.target + " " + o);
});
});
e.target
代表li
,o
代表孩子span
。
答案 5 :(得分:1)
您可以创建对此对象的引用,也可以使用with (this)
方法。当您使用事件处理程序并且无法传入引用时,后者非常有用。
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
}
});
}
答案 6 :(得分:0)
另一种解决方案(以及我在jQuery中最喜欢的方式)是使用jQuery提供的'e.data'来传递'this'。然后你可以这样做:
this.element.bind('click', this, function(e) {
e.data.myValue; //e.data now references the 'this' that you want
});