一些注意事项

这里介绍的数据绑定方法只是一个可运行的版本,但是为了让其易懂我做了一个简化。你可以在下面找到一些因为简化而忽略的主题的说明。

内存清理

内存泄漏是恼人的。这里的代码在某种意义上避免了这一问题,因为它使用 WeakMap 来保存监听函数。这意味着可监听对象相关联的监听函数会和被监听对象一起被垃圾回收。

然而,一个可能的用例是一个中心化,持久化的存储,伴随着频繁的 DOM 变动。在这个情况下, DOM 节点在内存垃圾回收前必须释放所有的注册的监听函数。示例中没有写上这个功能,但你可以在 nx-observe code 中检查 unobserve() 是如何实现的。

使用代理进行双重封装

代理是透明的,这意味着没有原生的方法来确定对象是代理还是简单对象。还有,它们可以无限嵌套,若不进行必要的预防,最终可能导致不停地对 observable 对象进行包装。

有很多种聪明的方法来把代理对象和普通对象区分开来,但是我没有在例子中写出。一个方法即是把代理添加入 WeakSet 并命名为 proxies ,然后在之后检查是否包含。如果对 nx-observe 是如何实现 isObservable ,有兴趣的可以查看这里

继承

nx-observe 也支持原型链继承。如下例子演示了继承是如何运作的。

  1. const parent = observable({greeting: 'Hello'})
  2. const child = observable({subject: 'World!'})
  3. Object.setPrototypeOf(child, parent)
  4. function print () {
  5. console.log(`${child.greeting} ${child.subject}`)
  6. }
  7. // outputs 'Hello World!' to the console
  8. observe(print)
  9. // outputs 'Hello There!' to the console
  10. setTimeout(() => child.subject = 'There!')
  11. // outputs 'Hey There!' to the console
  12. setTimeout(() => parent.greeting = 'Hey', 100)
  13. // outputs 'Look There!' to the console
  14. setTimeout(() => child.greeting = 'Look', 200)

对原型链的每个成员调用get 操作,直到找到属性为止,因此在需要的地方都会注册监听器。

有些极端情况是由一些极少见的情况引起的,既 set 操作也会遍历原型链(偷偷摸摸地),但这里将不会阐述。

内部属性

代理也可以拦截内部属性访问。你的代码可能使用了许多通常不考虑的内部属性。比如 well-known Symbols 即是这样的属性。这样的属性通常会被代理正确地拦截,但是也有一些错误的情况。

异步特性

当拦截set 操作时,可以同步运行监听器。这将会带来几个优点,如减少复杂性,精确定时和更好的堆栈追踪,但是它在一些情况下会引起巨大的麻烦。

想象一下,在一个循环中往一个被监听的数组插入 1000 个对象。数组长度会改变 1000 次并,且与之相关的监听器会也会紧接着连续执行 1000 次。这意味着运行完全相同的函数集 1000 次,这是毫无用处的。

  1. const observable1 = observable({prop: 'value1'})
  2. const observable2 = observable({prop: 'value2'})
  3. observe(() => observable1.prop = observable2.prop)
  4. observe(() => observable2.prop = observable1.prop)

另一个有问题的场景,即一个双向监听。如果监听函数同步执行,以下代码将会是一个无限循环。

  1. const observable1 = observable({prop: 'value1'})
  2. const observable2 = observable({prop: 'value2'})
  3. observe(() => observable1.prop = observable2.prop)
  4. observe(() => observable2.prop = observable1.prop)

基于这些原因, nx-observe 不重复地把监听函数插入队列,然后把它们作为微任务批量运行以防止 FOUC。如果你不熟悉微任务的概念,可以查阅之前关于浏览器中的定时的文章。