Object.observe(..)

前端web开发的圣杯之一就是数据绑定 —— 监听一个数据对象的更新并同步这个数据的DOM表现形式。大多数JS框架都为这些类型的操作提供某种机制。

在ES6后期,我们似乎很有可能看到这门语言通过一个称为Object.observe(..)的工具,对此提供直接的支持。实质上,它的思想是你可以建立监听器来监听一个对象的变化,并在一个变化发生的任何时候调用一个回调。例如,你可相应地更新DOM。

你可以监听六种类型的变化:

  • add
  • update
  • delete
  • reconfigure
  • setPrototype
  • preventExtensions

默认情况下,你将会收到所有这些类型的变化的通知,但是你可以将它们过滤为你关心的那一些。

考虑如下代码:

  1. var obj = { a: 1, b: 2 };
  2. Object.observe(
  3. obj,
  4. function(changes){
  5. for (var change of changes) {
  6. console.log( change );
  7. }
  8. },
  9. [ "add", "update", "delete" ]
  10. );
  11. obj.c = 3;
  12. // { name: "c", object: obj, type: "add" }
  13. obj.a = 42;
  14. // { name: "a", object: obj, type: "update", oldValue: 1 }
  15. delete obj.b;
  16. // { 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(..)设置它。

注意,这些变化事件在会在变化发生后立即触发。不要将它们与代理(见第七章)搞混,代理是可以在动作发生之前拦截它们的。对象监听让你在变化(或一组变化)发生之后进行应答。

自定义变化事件

除了六种内建的变化事件类型,你还可以监听并触发自定义变化事件。

考虑如下代码:

  1. function observer(changes){
  2. for (var change of changes) {
  3. if (change.type == "recalc") {
  4. change.object.c =
  5. change.object.oldValue +
  6. change.object.a +
  7. change.object.b;
  8. }
  9. }
  10. }
  11. function changeObj(a,b) {
  12. var notifier = Object.getNotifier( obj );
  13. obj.a = a * 2;
  14. obj.b = b * 3;
  15. // queue up change events into a set
  16. notifier.notify( {
  17. type: "recalc",
  18. name: "c",
  19. oldValue: obj.c
  20. } );
  21. }
  22. var obj = { a: 1, b: 2, c: 3 };
  23. Object.observe(
  24. obj,
  25. observer,
  26. ["recalc"]
  27. );
  28. changeObj( 3, 11 );
  29. obj.a; // 12
  30. obj.b; // 30
  31. obj.c; // 3

变化的集合("recalc"自定义事件)为了投递给监听器而被排队,但还没被投递,这就是为什么obj.c依然是3

默认情况下,这些变化将在当前事件轮询(参见本系列的 异步与性能)的末尾被投递。如果你想要立即投递它们,使用Object.deliverChangeRecords(observer)。一旦这些变化投递完成,你就可以观察到obj.c如预期地更新为:

  1. obj.c; // 42

在前面的例子中,我们使用变化完成事件的记录调用了notifier.notify(..)。将变化事件的记录进行排队的一种替代形式是使用performChange(..),它把事件的类型与事件记录的属性(通过一个函数回调)分割开来。考虑如下代码:

  1. notifier.performChange( "recalc", function(){
  2. return {
  3. name: "c",
  4. // `this` 是被监听的对象
  5. oldValue: this.c
  6. };
  7. } );

在特定的环境下,这种关注点分离可能与你的使用模式匹配的更干净。

中止监听

正如普通的事件监听器一样,你可能希望停止监听一个对象的变化事件。为此,你可以使用Object.unobserve(..)

举例来说:

  1. var obj = { a: 1, b: 2 };
  2. Object.observe( obj, function observer(changes) {
  3. for (var change of changes) {
  4. if (change.type == "setPrototype") {
  5. Object.unobserve(
  6. change.object, observer
  7. );
  8. break;
  9. }
  10. }
  11. } );

在这个小例子中,我们监听变化事件直到我们看到"setPrototype"事件到来,那时我们就不再监听任何变化事件了。