1. 前言
Vue的双向绑定一直是其核心,在这里我们将通过Vue源码来了解双向绑定的原理。
vue版本:2.6.11
vue仓库:https://github.com/vuejs/vue
vue文档:https://cn.vuejs.org/
2. Observer 观察者模式
简单来讲,观察者模式(Observer Pattern)适用于对象间存在一对多关系。比如,当一个对象被修改时,会自动通知依赖它的对象(事件)。
Vue的双向绑定就是采用此模式进行的。当某个对象的值被更改时,依赖(引用)此值的对象或事件都会收到通知。
观察者模式的更多信息可参考:https://www.runoob.com/design-pattern/observer-pattern.html
Vue代码中创建了 Observer类、Dep类、Watcher类来构建观察者模式。
2.1 Observer类
说明:深度遍历data内的成员,添加观察者模式,具体为以下操作:
①当成员类型为Object或Array时,在根节点创建一个 __ob__ 成员,指向一个初始化的Observer类的实例。
②当成员不是Object和Array时,封装其set和get属性,在2个属性的内部增加对Dep支持,用来支持观察者模式。当进行get操作时触发depend(),进行set操作时触发notify()。
2.2 Dep类
说明:在每一个需要进行绑定(单项或双向)的js对象(非Object和Array),Vue都在其对象内创建了一个Dep类的实例,此Dep实例用于管理当前js对象与订阅者的关系。
当进行get操作时触发Dep的depend(),其内部逻辑为此
进行set操作时触发Dep的notify(),在内部遍历订阅者集合,调用每个订阅者的update()方法。
主要成员:
subs[]:管理一个订阅者集合。
addSub():添加订阅者;
removeSub():移除订阅者;
notify():遍历订阅者数组,调用每个订阅者的update()方法。
depend():添加dep与sub(订阅者)的互相依赖。sub(订阅者)的dep集合添加此dep,dep实例的sub集合添加相关订阅者。
2.3 Watcher类
说明:Watcher类,即订阅者,在Vue中扮演的角色是当监听到data的值变更时,进行相关工作,比如修改HTML代码、getter、watch api等等。
3. 什么是data
在Vue官方教程对data解释如下:data为Vue 实例的数据对象。Vue 将会递归将 data 的 property 转换为 getter/setter,从而让 data 的 property 能够响应数据变化。
对象必须是纯粹的对象 (含有零个或多个的 key/value 对);浏览器 API 创建的原生对象和原型链上的 property 会被忽略。大概来说,data 应该只能是数据。
首先看个简单例子:
<body> <div id="app"> <p>姓名:<input v-model="userName" /></p> <p>年龄:<input v-model="age" /></p> <span>{{userName}}</span> </div> <script> var VM = new Vue({ data: { userName: '', age: '', likes: ['语文', '数学'] }, el: '#app' }) </script> </body>
4. data是如何初始化的
通过Vue源码,data在Vue初始化时经过了以下几个步骤:
4.1 initData(vm)
说明:这一步中,Vue会遍历data的成员,分别绑定到Vue实例的_data成员和根节点上。
其中根节点data成员的set和get属性都是与_data相关。
示例:
4.2 observe(data)
同 #2.1 Observer类 的解释:深度遍历data内的成员,添加观察者模式,具体为以下操作:
①当成员类型为Object或Array时,在根节点创建一个 __ob__ 成员,指向一个初始化的Observer类的实例。
②当成员不是Object和Array时,封装其set和get属性,在2个属性的内部增加对Dep支持,用来支持观察者模式。当进行get操作时触发depend(),进行set操作时触发notify()。
5. HTML是如何跟data联动的
Vue源码对HTML处理的步骤如下:
5.1 $mount(el)
说明:获取初始化Vue对象的el成员指定的HTML。
5.2 parse(html)
说明:将HTML代码转换为AST对象。AST即抽象语法树,这里不做深入讲解,感兴趣的可自行搜索。
在这一步中,会得到HTML元素内的属性,包括Vue中的指令。
5.3 generate(ast)
说明:深度遍历AST对象,生成render方法。
注意:此时render为string类型。(这里方便展示进行了代码格式化)
5.4 callHook(vm, 'beforeMount')
说明:触发beforeMount事件的回调。
5.5 new Watcher
说明:创建一个Watcher类的实例,并赋值给Vue实例的_watcher成员。
Watcher类,即监听者类,在Vue中扮演的角色是当监听到data的值变更时,进行相关工作,比如修改HTML代码、getter、watch api等等。
5.6 vm._render()
说明:调用实例的render()转换为VNode。
这一块比较复杂,其最终结果是把AST tree → 转换为VNode tree。
5.7 vm._update()
说明:新生成的VNode替换旧的VNode,替换算法就是diff算法。而在初始化时,原始html代码会转换为一个空VNode
关于diff算法的了解可看此篇文章:
Vue的diff算法解析:https://www.infoq.cn/article/uDLCPKH4iQb0cR5wGY7f
5.8 callHook(vm, 'mounted')
说明:触发mounted事件的回调。