代码之家  ›  专栏  ›  技术社区  ›  Jimmy Nitzan Tomer

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

  •  88
  • Jimmy Nitzan Tomer  · 技术社区  · 15 年前

    这个问题已经有了答案:

    我刚开始使用原型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 ?我必须将引用保存到 在每个开始的时候?似乎应该有一个更干净的方法。像这样的情况怎么样:

    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 是一个 jQuery 对象而不是 类名 对象。

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

    有什么建议吗?有没有一个干净的方法来做我想要的?或者我的整个设计模式有缺陷?

    7 回复  |  直到 10 年前
        1
  •  64
  •   Christian C. Salvadó    15 年前

    为了保存上下文, 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);
    

    在第二个例子中,我们可以更多地观察 绑定 .

    它基本上生成了一个新的函数,负责调用我们的函数,保留函数上下文( this 值),它被定义为 绑定 .

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

    注意在这个例子中,函数 fx1 ,调用时不带任何 对象上下文 ( obj.method() ,就像一个简单的函数调用一样,在这种类型的调用中, 关键字内部将引用全局对象,它将警告“全局测试”。

    现在, fx2 新功能是 绑定 方法生成后,它将调用我们的函数来保留上下文并正确传递参数,它将警告“obj test 1,2,3,4,5”,因为我们调用它时添加了另外两个参数,它已经 绑定的 前三个。

        2
  •  10
  •   icktoofay pcp    15 年前

    为了你的最后 MyClass 例如,您可以这样做:

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

    在传递给的函数中 each , this 引用jquery对象,如您所知。如果在这个函数中你得到 doSomething 函数来自 myThis ,然后使用arguments数组对该函数调用apply方法(请参见 apply function 以及 arguments variable ) 将设置为 我这 在里面 剂量测定法 .

        3
  •  7
  •   Nemesarial    10 年前

    我意识到这是一条旧线,但我有一个更优雅的解决方案,除了我注意到的一般不做之外,它几乎没有缺点。

    考虑以下事项:

    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函数不会被多次实例化。如果您知道永远不需要同一个函数的第二个实例,那么有一个创建它们的简写方法。不过,请注意: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”的引用并无限期地维护该引用。

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

    • 因为方法被定义为对象的成员而不是原型——继承可以通过成员定义而不是原型定义来实现。 这实际上是错误的。同样的原型继承可以通过作用于 f.constructor.prototype .
        4
  •  4
  •   Gabriel McAdams    15 年前
        5
  •  1
  •   Sampson    15 年前

    既然您使用的是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 代表 o 表示子级 跨度 .

        6
  •  1
  •   Martijn Pieters    12 年前

    可以创建对此对象的引用,也可以使用 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
            }
        });
    
    }
    
        7
  •  0
  •   SimplGy    14 年前

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

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