英文原址:http://emberjs.com/guides/object-model/observers/
Ember支持监听任何属性,包括计算型属性。你可以通过对函数使用observes来针对一个对象创建observer。
Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function() {
var firstName = this.get('firstName');
var lastName = this.get('lastName');
return firstName + ' ' + lastName;
}.property('firstName', 'lastName'),
fullNameChanged: function() {
// deal with the change
}.observes('fullName').on('init')
});
var person = Person.create({
firstName: 'Yehuda',
lastName: 'Katz'
});
person.set('firstName', 'Brohuda'); // observer will fire
因为fullName计算型属性依赖firstName属性,更新firstName也会触发fullName的observer。
观察者和异步
目前Ember中Observer都是同步的,这意味着observer方法会在他们观察的属性被改变的当时就被触发,因为这个原因,当属性不是同步方式使用时,容易引入一些bug。
Person.reopen({
lastNameChanged: function() {
// The observer depends on lastName and so does fullName. Because observers
// are synchronous, when this function is called the value of fullName is
// not updated yet so this will log the old value of fullName
console.log(this.get('fullName'));
}.observes('lastName')
});
上面的observer依赖lastName,也需要调用fullName。因为observer是同步的,当这个函数被调用时,fullName的值还没有来得及更新,因此它会打印出fullName的旧值。
这种同步的机制也会导致同一个observer被触发多次,当这个observer同时监听多个属性时。
Person.reopen({
partOfNameChanged: function() {
// Because both firstName and lastName were set, this observer will fire twice.
}.observes('firstName', 'lastName')
});
person.set('firstName', 'John');
person.set('lastName', 'Smith');
为了避免这样的问题,你应该使用Ember.run.once。这会保证任何你需要做的事情只会被执行一次,当所有绑定都是同步方式时,只在下一次run loop中发生一次。 (这里还是有点不太理解,没有使用过,回头理解的更透彻了,再细说)
Person.reopen({
partOfNameChanged: function() {
Ember.run.once(this, 'processFullName');
}.observes('firstName', 'lastName'),
processFullName: function() {
// This will only fire once if you set two properties at the same time, and
// will also happen in the next run loop once all properties are synchronized
console.log(this.get('fullName'));
}
});
person.set('firstName', 'John');
person.set('lastName', 'Smith');
观察者和对象初始化
只有一个对象的初始化完成了后,observer才会被触发。如果你需要observer在初始化发生时也被触发,你不能依赖set方法的副作用,你需要使用.on('init')来说明这个observer在初始化时也需要被触发。
App.Person = Ember.Object.extend({
init: function() {
this.set('salutation', "Mr/Ms");
},
salutationDidChange: function() {
// some side effect of salutation changing
}.observes('salutation').on('init')
});
没有被调用的计算型属性不会触发观察者
如果你从来没有对一个计算型属性使用get方法,它的observer永远也不会被触发,即使是该计算型属性依赖的属性发生了变化。你可以想象成它的值从一个unknown值变成另外一个。
这通常都不会影响应用的代码,因为计算型属性通常基本上被观察的同时也会被访问。举个例子,你访问了一个计算型属性的值,将它插入了HTML DOM(或者使用D3画出来),然后你监听它,当它改变时,你可以更新DOM。
如果你需要监听一个计算型属性,但是却不马上读取该计算型属性,你可以在init方法中读取它。
不使用Prototype扩展
如果你没有使用Ember的Prototype扩展,那么你可以使用Ember.observer方法来定义observer:
Person.reopen({
fullNameChanged: Ember.observer('fullName', function() {
// deal with the change
})
});
在类定义外面使用
你也可以在一个类定义的外面通过使用addObserver方法来给某个对象添加observer:person.addObserver('fullName', function() {
// deal with the change
});