Preserving a reference to “this” in JavaScript prototype functions
我刚开始使用原型Javascript,我很难弄清楚当作用域发生变化时,如何从原型函数内部保留对主对象的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); |
我知道,我可以在
1 | var myThis = this; |
然后使用
1 2 3 4 5 6 7 8 9 10 11 12 | MyClass = function() { this.elements $('.elements'); this.myValue = 'something'; this.elements.each(this.doSomething); } MyClass.prototype.doSomething = function() { // operate on the element } new MyClass(); |
在这种情况下,我不能用
有人建议我使用一个全局变量来保存对原始
有什么建议吗?有没有一个干净的方法来做我想要的?或者我的整个设计模式有缺陷?
为了保存上下文,
1 2 3 4 5 6 7 8 9 10 11 | // 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))); }; }; } |
您可以使用它,在您的示例中如下所示:
1 2 3 4 5 6 | MyClass.prototype.myfunc = function() { this.element.click((function() { // ... }).bind(this)); }; |
另一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var obj = { test: 'obj test', fx: function() { alert(this.test + ' ' + 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); |
在第二个例子中,我们可以更多地观察到
它基本上生成了一个新的函数,负责调用我们的函数,保留函数上下文(
其余的参数只是简单地传递给我们的函数。
注意在这个例子中,函数
现在,
对于上一个
1 2 | var myThis=this; this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); }); |
在传递给
我意识到这是一条旧线,但我有一个更优雅的解决方案,除了我注意到的一般不做之外,它几乎没有缺点。
考虑以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
在上面的函数中,我们定义了一个局部变量(上下文)。然后我们添加了一个返回局部变量的原型函数(test)。正如您可能预测的那样,当我们创建这个函数的实例,然后执行测试方法时,它不会返回局部变量,因为当我们将原型函数定义为主函数的成员时,它不在定义局部变量的范围之内。这是创建函数然后向其添加原型的一般问题——您不能访问在主函数范围内创建的任何内容。
要创建在局部变量范围内的方法,我们需要直接将它们定义为函数的成员,并去掉原型引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; |
您可能会担心,因为这些方法不是以原型方式创建的,所以不同的实例可能不会真正被数据分离。要证明它们是,请考虑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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"的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 | fn=new function(val){ var self=this; this.chain=function(){ return self; }; this.checkval=function(){ return val; }; } fn.checkval(); fn.chain().chain().checkval(); |
Pro的:使用这种方法创建函数对象的好处是丰富的。
- 它使您的代码更容易阅读,因为它以一种更容易在视觉上跟随的方式缩进函数对象的方法。
- 它只允许在最初以这种方式定义的方法中访问本地定义的变量,即使您后来向函数对象添加原型函数甚至成员函数,它也无法访问本地变量,并且您在该级别上存储的任何功能或数据在其他任何地方都是安全的和不可访问的。
- 它允许一种简单而直接的方式来定义单例。
- 它允许您存储对"this"的引用并无限期地维护该引用。
Con:使用这种方法有一些缺点。我不会假装很全面。)
因为方法被定义为对象的成员而不是原型-继承可以通过使用成员定义而不是原型定义来实现。这实际上是不正确的。同样的原型继承可以通过作用于f.constructor.prototype 来实现。
可以使用call()和apply()函数设置作用域。
您可以创建对此对象的引用,也可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 } }); } |
由于您使用的是jquery,因此值得注意的是,jquery本身已经维护了
1 2 3 4 5 | $("li").each(function(j,o){ $("span", o).each(function(x,y){ alert(o +"" + y); }); }); |
在本例中,
1 2 3 4 5 | $("li").click(function(e){ $("span", this).each(function(i,o){ alert(e.target +"" + o); }); }); |
其中,
另一个解决方案(也是我在jquery中最喜欢的方法)是使用jquery提供的"e.data"传递"this"。然后你可以这样做:
1 2 3 | this.element.bind('click', this, function(e) { e.data.myValue; //e.data now references the 'this' that you want }); |