参考: VUE双向绑定的简单实现

完整代码详见 : 完整代码

流程图

流程图

流程解析

依据上面这张数据双向绑定的流程图,具体的从开始创建开始的流程是:

  1. new MVVM()
    img1
  2. Observer(),里面做了,劫持了vm.data所有属性的输入和输出(set和get),并为其每一个单独创建了一个data数据的通知者和data数据的订阅者列表
    img2
    getter: 主要是添加订阅者,维护一个订阅者数组,return val
    setter: view => model(输入而改变vm中的data数据), model => view(dep.notify()去遍历所有的订阅者,update数据,从而改变相关的文本节点数据)
    img3
  3. 遍历解析指定id的DOM树(documentFragment的使用, 虚拟DOM)
    如果其中有v-model的元素节点, 就把vm上相应的data数据赋值给它,并且去除v-model(如果还是input输入框,就监听其keyup,并去触发vm.data的getter)
    img4
    如果是{ { } }这样格式的文本节点,先把对应的值赋上去,然后为这节点创建一个订阅者,并去添加到Observer里面创的vm.data[name]的订阅者列表里面(添加,主要利用watcher.update=>watcher.get=>vm[name]的getter里面去添加).
    img5
  4. 如果上面的input输入了,触发了keyup事件
    会触发vm[name]的setter来让相应的数据双向绑定的也改变, setter上面已有,不做累述

订阅者 :
img6
发布者(通知者) :
img7

遇到的坑

其实刚接触的时候比较绕的是其中的 数据劫持 + 订阅者/发布者模式

while为什么可以循环:
demo :

1
2
3
while(child = node.firstChild){
...
}

参考 : Node.appendChild 的解释
如果被插入的节点已经存在于当前文档的文档树中,则那个节点会首先从原先的位置移除,然后再插入到新的位置.

如果你需要保留这个子节点在原先位置的显示,则你需要先用Node.cloneNode方法复制出一个节点的副本,然后在插入到新位置.

DocumentFragment
参考 : DocumentFragment Web API
DocumentFragment添加子节点来解决频繁操作DOM的问题,可以理解为中间的一层存在于内存中的虚拟DOM.提高页面的性能.

疑惑和收获

  1. 用 Dep.target 这个全局的变量保存当前的watcher, 并在添加到订阅者队列之后清空, 总感觉这个实现不太好.
  2. 数据双向绑定中最后更新视图层中,直接是去更新node.value的值来响应,感觉这样的实现和之前使用DocuemntFragment的思维不符啊!感觉也可以用DocumentFragment来实现!
  3. 因为之前接触过函数节流和函数去抖,感觉可以用到keyup事件上.

感觉创建监听者时候,为每个data中的元素创建一个通知者,这里的闭包实现挺棒的!

还有就是之前有稍微了解,react的虚拟DOM是把真实的DOM树,转化为用对象表示的数据结构,然后在数据变更的时候用一些diff算法比较两棵虚拟DOM树,从而算出最优的变化操作,最后更新真实DOM树.