在JavaScript原型函数中保留对“this”的引用
我只是开始使用原型JavaScript,我无法弄清楚如何在范围更改时从原型函数内部保留对主对象的引用。 让我来说明我的意思(我在这里使用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
上有大量原型函数时会发生什么? 我必须在每个开始时保存这个参考吗? 似乎应该有一个更清洁的方式。 那么像这样的情况呢?
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;
创建对主对象的引用var myThis = this;
因为即使doSomething
的上下文中的原始值是一个jQuery
对象而不是MyClass
对象。
有人建议我使用一个全局变量来保存原始的this
参考,但这对我来说似乎是一个非常糟糕的主意。 我不想污染全局名称空间,这似乎阻止我实例化两个不同的MyClass
对象,而不会相互干扰。
有什么建议么? 有没有干净的方式来做我以后的事情? 或者是我的整个设计模式有缺陷?
为了保留上下文, bind
方法非常有用,它现在是最近发布的ECMAScript第5版规范的一部分,这个函数的实现很简单(只有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测试1,2,3,4,5”,因为我们调用了它,另外两个论点,它已经绑定了前三个。
对于你最后一个MyClass
例子,你可以这样做:
var myThis=this; this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });
在传递给each
的函数中, this
是指一个jQuery对象,就像你已经知道的那样。 如果在函数内部,你从myThis
得到了doSomething
函数,然后用参数数组(参见apply
函数和arguments
变量 )在该函数上调用apply方法,那么this
将在doSomething
设置为myThis
。
我意识到这是一个古老的线索,但我有一个更优雅的解决方案,除了我没有注意到的事实之外,没有什么缺点。
考虑以下几点:
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)。 正如你可能预测到的那样,当我们创建这个函数的一个实例,然后执行测试方法时,它不会返回局部变量,因为当我们将原型函数定义为主函数的成员时,它不在局部变量被定义。 这是创建函数然后添加原型的一个常见问题 – 您无法访问在主函数范围内创建的任何东西。
要创建局部变量范围内的方法,我们需要直接将它们定义为函数的成员,并摆脱原型引用:
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();
临的:使用这种方法来创建函数对象的好处是丰富的。
- 它使你的代码更容易阅读,因为它缩小了函数对象的方法,使得视觉更容易遵循。
- 它允许只在最初定义的方法中访问本地定义的变量,即使后来向函数对象添加原型函数或成员函数,它也不能访问局部变量,并且您在该级别上存储的任何功能或数据仍然存在从其他地方安全和无法访问。
- 它允许一个简单而直接的方式来定义单例。
- 它允许您存储对“this”的引用并无限期地维护该引用。
Con's:使用这种方法有一些缺点。 我不假装是全面的:)
-
因为方法被定义为对象的成员而不是原型 – 可以使用成员定义来实现继承,但是不能使用原型定义。这实际上是不正确的。 通过作用于f.constructor.prototype
可以实现相同的原型继承。
您可以使用call()和apply()函数来设置范围
由于您使用的是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
。
另一个解决方案(我在jQuery中最喜欢的方式)是使用jQuery提供的'e.data'来传递'this'。 那么你可以这样做:
this.element.bind('click', this, function(e) { e.data.myValue; //e.data now references the 'this' that you want });
您可以创建对该对象的引用,也可以使用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 } }); }