2010-01-08 44 views
83

我刚开始使用原型JavaScript,并且无法在范围更改时从原型函数内部保留对主对象的引用this 。让我来说明我的意思(我使用jQuery在这里):在JavaScript原型函数中保留对“this”的引用

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;主要对象的引用,因为thisdoSomething范围内,即使原来的值是jQuery对象,而不是一个MyClass对象。

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

有什么建议吗?有没有干净的方式来做我以后的事情?或者是我的整个设计模式有缺陷?

回答

60

对于维护上下文中设置的范围内,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行为的第二个例子。

它基本上会生成一个新函数,它将负责调用我们的函数,保留定义为bind的第一个参数的函数上下文(this值)。

其余的参数只是传递给我们的函数。

注意在这个例子中,该功能fx1,调用没有任何对象上下文obj.method()),只是作为一个简单的函数调用,在这种类型invokation中,this关键字内部将指全局对象,它将提醒“全球测试”。

现在,fx2bind方法生成的新函数,它会调用我们的函数来保存上下文并正确传递参数,它会提醒“obj测试1,2,3,4,5”,因为我们调用它添加两个额外的参数,它已经有绑定前三个。

+0

我真的很喜欢这个功能,但是在jQuery环境中,我倾向于在给定现有jQuery.bind方法的情况下命名它(即使没有实际的命名冲突)。 –

+0

@Rob,创建一个与'Function.prototype.bind'相同的'jQuery.bind'方法是很简单的,检查这个简单的实现:http://jsbin.com/aqavo/edit虽然我会考虑更改它的名字,它可能会导致与事件/绑定方法混淆... – CMS

+2

我强烈建议坚持使用名称'Function.prototype.bind'。它现在是语言的标准化部分;它不会消失。 – bobince

1

由于您使用jQuery的,但值得注意的是this已经被jQuery的本身维护:

$("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

10

对于你最后MyClass例如,你可以这样做:

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

在传递给each功能,this指的是jQuery对象,因为你已经知道了。如果在该函数内部,则从myThis获得doSomething函数,然后使用参数数组(请参阅apply functionarguments variable)在该函数上调用apply方法,然后this将在doSomething中设置为myThis

+1

这是行不通的,到了this.doSomething的时候,'this'已经被jQuery取代了其中的一个元素。 –

+0

是的,我最初发布时遇到了两个问题。我编辑它,现在它应该工作。 (抱歉...) – icktoofay

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 
}); 
0

您可以创建到该对象的引用,或者您可以使用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 
     } 
    }); 

} 
+4

由于含糊不清和其他问题,避免使用'with'语句。 –

+0

当然,如果你可以避免使用它,那么通过一切方法使用一个更简单的方法,但是当所有其他方法都失败时它仍然有效且有用。 –

+0

-1:'with'不会改变'this'的值 – Bergi

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 

在功能上面我们定义一个局部变量(上下文)。然后我们添加了一个返回局部变量的原型函数(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函数没有被多次实例化。如果你知道你永远不需要同一个函数的第二个实例,那么有一个简写的方法来创建它们。被警告,但是:皮棉会抱怨说,这是一个奇怪的建筑,并质疑你的关键字“新”的使用:

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”的引用并无限期地维护该引用。

反对的: 有一些缺点,使用这种方法。我不假装是全面:)

  • 因为方法被定义为成员的对象,而不是原型 - 继承可以使用成员的定义,但不是典型的定义来实现。 这实际上是不正确的。通过采取行动f.constructor.prototype可以实现同样的原型继承。
+0

这是一个很好的方法,但在某些情况下可能会出现另一个更微妙的问题。当你用你的构造函数返回一个方法时,'new'运算符不再返回原型链。也就是说,这不是被隐藏或覆盖的问题 - 它不在那里。任何你在原型链上拥有的成员 - 比如来自超类的成员都不在了。 – dhimes

+0

@dhimes - 实际上你不能访问原型链的唯一原因是你不再有权访问构造函数。除非我们通过' .constructor'属性访问它。测试这个证明:'a = new function(){}; a.constructor.prototype.b = function(){console.log('in .b');}; a.b();' – Nemesarial

相关问题