使用var self =这是在类和事件之间同步的好方法吗?

时间:2015-04-19 08:09:42

标签: javascript angularjs

让我们看看这个简单的代码示例(为了简单起见,它是用angularjs编写的,但这种情况在JavaScript中一直发生):

angular.module('app',[]).
directive('myDir', function(){
    this.state = {a:1, b:2};

    return {
        link: function(scope, elem, attrs){
            elem.on('click', function(){
                // "this" is not the class but the element
                this.state.a++;       
                this.state.b++;
                console.log(this.state);
            });
        }
    }
});

当要调用onclick回调时," this"不是指令功能,而是元素本身。

所以我们都知道这里的技巧,我们创建一个闭包并使用var self = this来完成工作。

angular.module('app',[]).
directive('myDir', function(){
    // create a closure for the rescue
    var self = this;
    this.state = {a:1, b:2};
    return {
        link: function(scope, elem, attrs){
            elem.on('click', function(){
                self.state.a++;       
                self.state.b++;
                console.log(self.state);
            });
        }
    }
});

好的 - 这很有效,而且我已经这么做了很多次,但是我问自己这是不是最好的办法呢? 这对我来说总是看起来像一个糟糕的设计方法 是否有更好的方法在类和用户事件之间进行同步?

4 个答案:

答案 0 :(得分:1)

很难客观地说,这是一个糟糕的设计。但我认为有更优雅的方式来处理解决方案。

  • 它是否会在您的范围内创建不必要的变量? 即可。
  • 是否可能让其他开发者感到困惑? 即可。

在我们同意应该有更好的方法之前,你不必再提出更多问题。

其他两个答案提出了替代方案。第一个是使用Function.prototype.bind,它允许我们在另一个函数中设置它的上下文。

从概念上讲,它很优雅,但在语法和心理方面它可能很重。我发现这往往会导致巨大的矫枉过正,最终你不得不在事件处理程序上写一个很多.bind次调用。

第二个是别名self = this,然后根据你所在的范围适当地使用它。

我认为这本身就是一种不好的做法,因为维护代码的开发人员必须考虑应该使用哪个版本,并且必须记住在每个新版本的顶部创建self = this别名上下文块。

我认为核心问题是Javascript有this个关键字。它是Javascript程序员普遍混淆的一个巨大来源,主要是因为它被添加以使Javascript对于面向对象的程序员来说更加舒适。

一旦你开始以纯粹的原型方式使用Javascript(forget new and this),所有这些问题就会消失。

让我们来做一个班级'用普通的老物件代替。

function BankAccount(name, balance) {
  var account = {};

  account.name = name || 'Anonymous Benefactor';
  account.balance = balance || 1000;

  account.withdraw = function(amount) {
    return (account.balance -= amount);
  };

  account.deposit = function(amount) {
    return (account.balance += amount);
  };

  return account;
}

// we don't need a new keyword!
var account = BankAccount('Prototype', 310);

// this means we can use bind, call and apply!
account = BankAccount.apply(null, ['Prototype', 310]);

无论我们想在哪个函数中引用上下文对象(account),我们都可以这样做,因为Javascript有引用和闭包。上下文对象不会更改为windowevent

这种风格的灵感来自a talk by Doug Crockford

您可以将代码修改为:

angular.module('app',[]).
directive('myDir', function(){
  var myDir = {};
  myDir.state = {a:1, b:2};
  return {
    link: function(scope, elem, attrs){
      elem.on('click', function(){
        myDir.state.a++;       
        myDir.state.b++;
        console.log(myDir.state);
      });
    }
  }
});
  • 您不必担心.bind
  • 您不必担心上下文的变化。

但是,请将此视为指南。不是法律。有些情况this绝对可以接受(Angular服务让人想起)。有时,您无法做出选择,某些库和函数会强制您修改this值。


最后一件事。如果您可以使用ES6(本机(Firefox,IO.js等))或通过转换器(traceur,babel),那么您可以使用胖箭头语法。

function foo() {
  this.bar = 'baz';
  element.on('click', event => {
    console.log(this.bar); // 'baz'
  });
}

它将当前值绑定到功能块的上下文。它的工作方式与.bind(this)非常相似,但在语法上更轻。

答案 1 :(得分:0)

你的特殊例子我认为它很糟糕,但也许不是你想的原因。如果您在http://plnkr.co/edit/K66o8tmRtnnfk8NZ8YPf?p=preview

看到修改后的版本
app.directive('myDir', function(){
    // create a closure for the rescue
    var self = this;
    this.state = {a:1, b:2};
    return {
        link: function(scope, elem, attrs){
            elem.on('click', function(){
                self.state.a++;       
                self.state.b++;
                // Using global scope (seen as a bad thing)
                console.log(self === window);
            });
        }
    }
});

然后您定义的self实际上等于window,因此您实际上将状态保存到全局范围。这是因为定义指令的函数不是new语句的目标,因此this具有默认值window

如果该函数是new语句的目标,例如controllerservice,那么设置self = this就可以了。但是,在directivefactory中,this不会更改其默认值window。在这些情况下,我只想定义一个局部变量

var state = {a:1, b:2};

并从闭包中访问它。

另一种方法是在每个事件处理程序上使用bind之类的内容来更改this中引用的内容。但是,有时您可能希望在事件处理程序中使用默认绑定,因此有时需要使用bind而有时不需要,我怀疑可能会导致花费更多的调试时间,因为您没有一致的方法写这些。

答案 2 :(得分:0)

在服务和控制器中是可以接受的,但是在指令的嵌套函数范围内,它看起来很麻烦 - 而且也是错误的。 this仅在compile中定义(并且它指的是DDO),但在指令工厂函数和前/后链接中都没有定义。

更好更清晰的方法是为回调函数提供上下文,因此它知道(以及它所操作的)上下文。

       elem.on('click', angular.bind(state, function(){
            this.a++;       
            this.b++;
            console.log(this);
        }));

替代方案(ES5原生)方式是

        elem.on('click', function(){
            this.a++;       
            this.b++;
            console.log(this);
        }.bind(state));

如果IE8缺乏支持不是问题,可以认为是推荐的。

答案 3 :(得分:-1)

虽然您使用的方法也很好,但是,还有另一种最好的方法可以使用bind()方法在任何JavaScript代码中编写。

angular.module('app',[]).
directive('myDir', function(){
    // create a closure for the rescue
    var self = this;
    this.state = {a:1, b:2};
    return {
        link: function(scope, elem, attrs){
            elem.on('click', function(){
                self.state.a++;
                console.log(this.state.a == self.state.a);
            }.bind(self));
        }
    }
});

bind更改了代码的上下文。您可以为上下文传递任何内容。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind