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

Jim*_*dra 89 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();
Run Code Online (Sandbox Code Playgroud)

我知道我可以通过以下方式执行此操作来保留对主对象的引用myfunc:

var myThis = this;
Run Code Online (Sandbox Code Playgroud)

然后使用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();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我无法创建对主对象的引用,var myThis = this;因为即使this上下文中的原始值doSomethingjQuery对象而不是MyClass对象.

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

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

CMS*_*CMS 64

为了保留上下文,该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))); 
    }; 
  };
}
Run Code Online (Sandbox Code Playgroud)

你可以使用它,在你的例子中这样:

MyClass.prototype.myfunc = function() {

  this.element.click((function() {
    // ...
  }).bind(this));
};
Run Code Online (Sandbox Code Playgroud)

另一个例子:

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);
Run Code Online (Sandbox Code Playgroud)

在第二个例子中,我们可以更多地观察到它的行为bind.

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

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

注意在这个例子中,函数fx1被调用而没有任何对象context(obj.method()),就像一个简单的函数调用一样,在这种类型的invokation中,this关键字inside会引用Global对象,它会提醒"global test".

现在,该方法生成fx2的新函数,bind它将调用我们的函数保留上下文并正确传递参数,它将警告"obj test 1,2,3,4,5",因为我们调用它另外添加两个参数,它已经有了绑定前三.

  • 很高兴知道浏览器错误.仅供参考,jQuery 1.4现在包含[jQuery.proxy](http://api.jquery.com/jQuery.proxy/),其功能类似(尽管不完全相同).使用这样的`$ .proxy(obj.fx,obj)`或`$ .proxy(obj,"fx")` (3认同)
  • 我强烈建议坚持使用名称`Function.prototype.bind`.它现在是该语言的标准化部分; 它不会消失. (2认同)

ick*_*fay 10

对于您的上一个MyClass示例,您可以这样做:

var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });
Run Code Online (Sandbox Code Playgroud)

在传递给的函数中each,this引用jQuery对象,如您所知.如果在函数内部从函数中获取doSomething函数myThis,然后使用arguments数组调用该函数的apply方法(参见apply函数arguments变量),this则将其设置为myThisin doSomething.


Nem*_*ial 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
Run Code Online (Sandbox Code Playgroud)

在上面的函数中,我们定义了一个局部变量(context).然后我们添加了一个返回局部变量的原型函数(test).正如您可能已经预测的那样,当我们创建此函数的实例然后执行测试方法时,它不会返回局部变量,因为当我们将原型函数定义为我们的main函数的成员时,它超出了局部变量已定义.这是创建函数然后向其添加原型的一般问题 - 您无法访问在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;
Run Code Online (Sandbox Code Playgroud)

您可能会担心因为这些方法不是原型创建的,所以不同的实例可能实际上并不是数据分离的.为了证明它们是,请考虑这个:

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
Run Code Online (Sandbox Code Playgroud)

使用此方法的另一种方法是创建单例.通常情况下,我们的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();
Run Code Online (Sandbox Code Playgroud)

Pro: 使用此方法创建函数对象的好处很多.

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

Con: 使用这种方法有一些缺点.我不假装全面:)

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