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

时间:2010-01-08 05:51:50

标签: javascript oop scope this prototype-programming

我刚刚开始使用原型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对象而不会相互干扰。

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

7 个答案:

答案 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对象,它将提醒“全局测试”。

现在,fx2bind方法生成的新函数,它将调用我们的函数保留上下文并正确传递参数,它将警告“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 functionarguments 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的: 使用此方法创建函数对象的好处很多。

  • 它使您的代码更易于阅读,因为它以一种使视觉上更容易理解的方式缩进函数对象的方法。
  • 它允许仅在最初以这种方式定义的方法中访问本地定义的变量 ,即使您稍后将原型函数甚至成员函数添加到函数对象,它无法访问本地变量,您在该级别存储的任何功能或数据仍然是安全的,并且无法从其他任何地方访问。
  • 它允许一种简单直接的方式来定义单身。
  • 它允许您存储对“this”的引用并无限期地维护该引用。

<强>反对的: 使用这种方法有一些缺点。我并不假装全面:)

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

答案 3 :(得分:4)

您可以使用call() and apply() functions

设置范围

答案 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代表lio代表孩子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
});