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

Posted

技术标签:

【中文标题】在 JavaScript 原型函数中保留对“this”的引用 [重复]【英文标题】:Preserving a reference to "this" in JavaScript prototype functions [duplicate] 【发布时间】:2011-01-02 19:18:52 【问题描述】:

我刚刚开始使用原型 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; 创建对主对象的引用,因为即使doSomething 上下文中this 的原始值也是jQuery 对象而不是MyClass对象。

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

有什么建议吗?有没有一种干净的方法来做我所追求的?还是我的整个设计模式有缺陷?

【问题讨论】:

【参考方案1】:

为了保留上下文,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里面的关键字将引用全局对象,它会提醒“全局测试”。

现在,fx2bind 方法生成的新函数,它将调用我们的函数,保留上下文并正确传递参数,它会警告“obj test 1, 2, 3, 4, 5 " 因为我们在调用它时添加了两个额外的参数,所以它已经绑定了前三个。

【讨论】:

我真的很喜欢这个功能,但在 jQuery 环境中,鉴于现有的 jQuery.bind 方法,我倾向于将其命名为其他名称(即使没有实际的命名冲突)。 我强烈建议使用 Function.prototype.bind 这个名字。它现在是语言的标准化部分;它不会消失。 @bobnice:完全同意,原生实现很快就会在主要的 JavaScript 引擎中可用...bugs.webkit.org/show_bug.cgi?id=26382bugzilla.mozilla.org/show_bug.cgi?id=429507 很高兴了解浏览器错误。仅供参考,jQuery 1.4 现在包括 jQuery.proxy,其功能相似(尽管不相同)。像这样使用$.proxy(obj.fx, obj)$.proxy(obj, "fx") 这是 JS 的一大缺点。在大型项目中,它使代码看起来很乱。闭包的想法是带给 JS 的最糟糕的想法之一。似乎根本无法将对象原型的上下文绑定到实际对象。【参考方案2】:

对于您最后一个 MyClass 示例,您可以这样做:

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

如您所知,在传递给each 的函数中,this 指的是一个jQuery 对象。如果在该函数中您从myThis 获得doSomething 函数,然后使用参数数组调用该函数的apply 方法(参见apply function 和arguments variable),那么this 将被设置为myThisdoSomething.

【讨论】:

这行不通,当您访问 this.doSomething 时,this 已经被 jQuery 替换为其中一个元素。 是的,我最初发布的时候有两个问题。我编辑了它,现在它应该可以工作了。 (对不起……)【参考方案3】:

我意识到这是一个旧线程,但我有一个更优雅的解决方案,并且除了通常不这样做之外几乎没有缺点,正如我所注意到的。

考虑以下几点:

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

在上面的函数中,我们定义了一个局部变量(上下文)。然后我们添加了一个返回局部变量的原型函数(测试)。正如您可能已经预料到的那样,当我们创建此函数的实例然后执行测试方法时,它不会返回局部变量,因为当我们将原型函数定义为主函数的成员时,它超出了局部变量被定义。 这是创建函数然后向其中添加原型的一般问题 - 您无法访问在主函数范围内创建的任何内容。

要创建局部变量范围内的方法,我们需要直接将它们定义为函数的成员,并摆脱原型引用:

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

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

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

【讨论】:

这是一个很好的方法,但在某些情况下可能会出现更进一步、更微妙的问题。当您使用构造函数返回方法时,new 运算符甚至不再返回原型链。也就是说,这不是隐藏或覆盖的问题——它不存在。你在原型链上拥有的任何成员——比如超类中的成员——都消失了。 @dhimes - 实际上,您无法访问原型链的唯一原因是您不再有权访问构造函数。除了我们确实可以通过<function>.constructor 属性访问它。对此进行测试以证明:a=new function(); a.constructor.prototype.b=function()console.log('in .b');; a.b();【参考方案4】:

您可以使用call() and apply() functions设置范围

【讨论】:

【参考方案5】:

由于您使用的是 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 代表lio 代表子span

【讨论】:

【参考方案6】:

您可以创建对 this 对象的引用,也可以使用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
        
    );


【讨论】:

with 语句由于歧义等问题要避免。 当然,如果你可以避免使用它,那么一定要使用更简单的方法,但是当其他方法都失败时它仍然有效且有用。 -1: with 不会改变this 的值【参考方案7】:

另一个解决方案(也是我在 jQuery 中最喜欢的方式)是使用 jQuery 提供的 'e.data' 来传递 'this'。然后你可以这样做:

this.element.bind('click', this, function(e) 
    e.data.myValue; //e.data now references the 'this' that you want
);

【讨论】:

以上是关于在 JavaScript 原型函数中保留对“this”的引用 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

js继承

前端面试宝典 - Javascript 基础知识

javascript中的this与prototype,原型理解

[javascript]js原型链以及原型链继承

JavaScript中继承的实现

组合使用构造函数模式和原型模式