Object.observe(..)
前端web开发的圣杯之一就是数据绑定 —— 监听一个数据对象的更新并同步这个数据的DOM表现形式。大多数JS框架都为这些类型的操作提供某种机制。
在ES6后期,我们似乎很有可能看到这门语言通过一个称为Object.observe(..)
的工具,对此提供直接的支持。实质上,它的思想是你可以建立监听器来监听一个对象的变化,并在一个变化发生的任何时候调用一个回调。例如,你可相应地更新DOM。
你可以监听六种类型的变化:
- add
- update
- delete
- reconfigure
- setPrototype
- preventExtensions
默认情况下,你将会收到所有这些类型的变化的通知,但是你可以将它们过滤为你关心的那一些。
考虑如下代码:
var obj = { a: 1, b: 2 };
Object.observe(
obj,
function(changes){
for (var change of changes) {
console.log( change );
}
},
[ "add", "update", "delete" ]
);
obj.c = 3;
// { name: "c", object: obj, type: "add" }
obj.a = 42;
// { name: "a", object: obj, type: "update", oldValue: 1 }
delete obj.b;
// { name: "b", object: obj, type: "delete", oldValue: 2 }
除了主要的"add"
、"update"
、和"delete"
变化类型:
"reconfigure"
变化事件在对象的一个属性通过Object.defineProperty(..)
而重新配置时触发,比如改变它的writable
属性。更多信息参见本系列的 this与对象原型。"preventExtensions"
变化事件在对象通过Object.preventExtensions(..)
被设置为不可扩展时触发。因为
Object.seal(..)
和Object.freeze(..)
两者都暗示着Object.preventExtensions(..)
,所以它们也将触发相应的变化事件。另外,"reconfigure"
变化事件也会为对象上的每个属性被触发。"setPrototype"
变化事件在一个对象的[[Prototype]]
被改变时触发,不论是使用__proto__
setter,还是使用Object.setPrototypeOf(..)
设置它。
注意,这些变化事件在会在变化发生后立即触发。不要将它们与代理(见第七章)搞混,代理是可以在动作发生之前拦截它们的。对象监听让你在变化(或一组变化)发生之后进行应答。
自定义变化事件
除了六种内建的变化事件类型,你还可以监听并触发自定义变化事件。
考虑如下代码:
function observer(changes){
for (var change of changes) {
if (change.type == "recalc") {
change.object.c =
change.object.oldValue +
change.object.a +
change.object.b;
}
}
}
function changeObj(a,b) {
var notifier = Object.getNotifier( obj );
obj.a = a * 2;
obj.b = b * 3;
// queue up change events into a set
notifier.notify( {
type: "recalc",
name: "c",
oldValue: obj.c
} );
}
var obj = { a: 1, b: 2, c: 3 };
Object.observe(
obj,
observer,
["recalc"]
);
changeObj( 3, 11 );
obj.a; // 12
obj.b; // 30
obj.c; // 3
变化的集合("recalc"
自定义事件)为了投递给监听器而被排队,但还没被投递,这就是为什么obj.c
依然是3
。
默认情况下,这些变化将在当前事件轮询(参见本系列的 异步与性能)的末尾被投递。如果你想要立即投递它们,使用Object.deliverChangeRecords(observer)
。一旦这些变化投递完成,你就可以观察到obj.c
如预期地更新为:
obj.c; // 42
在前面的例子中,我们使用变化完成事件的记录调用了notifier.notify(..)
。将变化事件的记录进行排队的一种替代形式是使用performChange(..)
,它把事件的类型与事件记录的属性(通过一个函数回调)分割开来。考虑如下代码:
notifier.performChange( "recalc", function(){
return {
name: "c",
// `this` 是被监听的对象
oldValue: this.c
};
} );
在特定的环境下,这种关注点分离可能与你的使用模式匹配的更干净。
中止监听
正如普通的事件监听器一样,你可能希望停止监听一个对象的变化事件。为此,你可以使用Object.unobserve(..)
。
举例来说:
var obj = { a: 1, b: 2 };
Object.observe( obj, function observer(changes) {
for (var change of changes) {
if (change.type == "setPrototype") {
Object.unobserve(
change.object, observer
);
break;
}
}
} );
在这个小例子中,我们监听变化事件直到我们看到"setPrototype"
事件到来,那时我们就不再监听任何变化事件了。