摘要:因为项目刚开始用的vue框架,所以早期也研究了一下他的代码看过相关文章的解析,说说也能说个七七八八。不过今天再去看以前的demo的时候,发现忽然一知半解了,说明当时可能也没有理解透,所以写篇文章让自己理解的更深一些。
本篇文章大多数知识点实在学习了这篇Vue.js双向绑定的实现原理之后避免遗忘,所以写这个温故知新,加强理解。
一、访问器属性
如果稍微看过相关文章的人都知道vue的实现是依靠Object.defineproperty()来实现的。每个对象都有自己内置的set和get方法,当每次使用set时,去改变引用该属性的地方,从而实现数据的双向绑定。简单举例
|
|
二、极简双向绑定的实现
基于这个原理,如果想实现显示文字根据输入input变化,实现一个简单版的。
上面这个实例实现的效果是:随着文本框输入文字的变化,span会同步显示相同的文字内容。同时在控制台用js改变obj.hello,视图也会更新。这样就实现了view->model,model->view的双向绑定。
三、拆解任务,实现vue的双向数据绑定
我们最终实现下面vue的效果
1.输入框的文本与文本节点的data数据绑定
2.输入框的内容发生变化时,data中的数据也发生变化,实现view->model的变化
3.data中的数据发生变化时,文本节点的内容同步发生变化,实现model->view的变化
要实现1的要求,则又涉及到了dom的编译,其中有一个DocumentFragment的知识点。
四、DocumentFragment
众所周知,vue吸收了react虚拟DOM的优点,使用DocumentFragment处理节点,其性能和速度远胜于直接操作dom。vue进行编译时,就是将所有挂载在dom上的子节点进行劫持到使用DocumentFragment处理节点,等到所有操作都执行完毕,将DocumentFragment再一模一样返回到挂载的目标上。
先实现一段劫持函数,将要操作的dom全部劫持到DocumentFragment中,然后再append会原位置。
五、数据初始化绑定
当已经获取到所有的dom元素之后,则需要对数据进行初始化绑定,这里简单涉及到了模板的编译。
|
|
通过以上代码先实现了第一个要求,文本框和文本节点已经出现了hello woeld了
六、响应式的数据绑定
接下来我们要实现数据双向绑定的第一步,即view->model的绑定。根据之前那个简单的例子看到,我们实时获取input中的值,通过Object.defineProperty将data中的text设置为vm的访问器属性,通过set方法,当我们在设置vm.data的值时,实现数据层的绑定。在这一步,set中要做的操作是更新属性的值。
|
|
七、订阅/发布模式(subscribe&publish)
text 属性变化了,set方法触发了,可以通过view层的改变实时改变数据,可是并没有改变文本节点的数据。一个新的知识点:订阅发布模式。
订阅发布模式(又称为观察者模式)定义了一种一对多的关系,让多个观察者同时监听一个主题对象,这个主体对象的改变会通知所有观察者对象。
发布者发出通知=>主题对象收到通知并推送给订阅者=>订阅者执行操作
上图为一个简单实例,发布者执行发布命令,所有这个主题的订阅者执行更新操作。接下去我们要做的就是,当set方法触发后,input作为发布者,改变了text属性;而文本节点作为订阅者,在收到消息后执行更新操作。
八、双向绑定的实现
每次new一个新的vue对象时,主要是做了两件事,一件是监听数据:observer(监听数据),第二个是编译HTML,nodeToFragement(id)。
在监听数据的过程中,会为data中的每一个属性生成一个主题对象。
而在编译HTML的过程中,会为每个与数据绑定的相关节点生成一个订阅者watcher,订阅者watcher会将自己订阅到相应属性的dep中。
在前面的方法中已经实现了:修改输入框内容=>再时间回调中修改属性值=>触发属性的set方法。
接下来要做的是发出通知dep.notify=>发出订阅者的uodate方法=>更新视图。
那么如何将watcher添加到关联属性的dep中呢。
编译HTML过程中,为每一个与data关联的节点生成一个watcher,那么watcher中又发生了什么?
在编译HTML的过程中,生成watcher
首先将自己赋给了一个全局变量Dep.target;然后执行了uodate方法,进而执行了get方法,读取了vm的访问器属性,从而触发了访问器属性的get方法,get方法将相应的watcher添加到对应访问器属性的dep中。再次,获取属性的值,然后更新视图。最后将dep.target设置为空,是因为这是个全局变量也是watcher与dep之间唯一的桥梁,任何时间都只能保证只有一个值。(其实就是说全局一个主题,每个订阅者和发布者都是通过这个主题进行沟通。当执行代码时,这个主题接受到一个发布通知,通知完所有订阅者,然后注销掉,用于下一个通知发布。啰嗦了一段就是想讲为什么要设置Dep.target = null)。
|
|
至此,hello world 双向绑定就基本实现了。文本内容会随输入框内容同步变化,在控制器中修改 vm.text 的值,会同步反映到文本内容中。