让我们看看这个简单的代码示例(为了简单起见,它是用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);
});
}
}
});
好的 - 这很有效,而且我已经这么做了很多次,但是我问自己这是不是最好的办法呢?
这对我来说总是看起来像一个糟糕的设计方法
是否有更好的方法在类和用户事件之间进行同步?
答案 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有引用和闭包。上下文对象不会更改为window
或event
。
这种风格的灵感来自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
语句的目标,例如controller
或service
,那么设置self = this
就可以了。但是,在directive
或factory
中,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