原文链接: mvvm vue 实现原理 介绍
上一篇: mvvm defineProperty 设置 对象属性
下一篇: mvvm oop 实现
实现原理
1. Vue作为一个MVVM框架的基本实现原理:数据代理、模板解析、数据绑定
2. Vue涉及的核心技术:虚拟DOM、Diff算法
3. 关键性方法:Object.defineProperty(obj, prop, descriptor)
DocumentFragment
1. DocumentFragment:文档碎片,虚拟DOM对象,表示一个没有父级文件的最小文档对象;
2. 被当作一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式的XML片段;
3. Document对应显示的页面,包含N个Element,更新Document内部的元素,界面也会更新;
4. DocumentFragment并不是真实DOM树的一部分,而是内存中保存N个Element的容器对象,
不与界面关联,它的变化不会引起DOM树的重新渲染,界面不变,且不会导致性能等问题;
数据代理
1. 数据代理:通过一个对象代理操作另一个对象中的属性;
2. Vue的数据代理:通过vm对象来代理data对象中的所有属性操作;
3. Vue数据代理的实现:vm中的_data属性代理了data对象
1. 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符,监视
vm的属性变化;
2. Object.defineProperty()会为vm中所有添加的属性提供getter/setter,它们内部
去操作data中对应的属性数据。
数据代理: 通过一个对象代理对另一个对象中的属性的操作(读/写)
vue数据代理: 通过vm对象来代理data对象中所有属性的操作
好处:更加方便的操作data中的数据
基本实现流程:
通过Object.defineProperty给vm添加与data对象的属性对应的属性值描述符
所有添加的属性都包含getter和setter
getter和setter内部去操作data中对应的属性数据
模板解析
1. 创建DocumentFragment对象,递归遍历DOM树,将DOM节点添加进DocumentFragment中,
也即,将DOM节点映射到虚拟DOM对象中;
2. 解析大括号表达式({{name}})、一般指令、事件指令。
模板更新
模板更新的基本流程
1,将el的所有子节点取出,添加到一个新的文档fragment对象中
2,对fragment中所有层次子节点递归进行编译解析处理
对大括号表达式文字结点进行解析
对元素节点的指令属性进行解析
事件指令解析
大括号表达式解析
根据正则表达式对象得到匹配出的表达式字符串
从data中取出表达式对应的属性值
将属性值设置为文本结点的textContent
事件指令解析
从指令名中取出事件名
根据指令的值(表达式)从methods中得到对应的时间处理函数对象
给当前元素结点绑定指定事件名和回调函数的dom时间监听
指令解析完后,移除此指令属性
一般指令解析
得到指令名和表达式 text/html/class
从data中根据表达式得到对应的值
根据指令名确定需要操作元素结点的什么属性
v-text ==> textContent
v-html ==> innerHtml
v-class ==> class
将得到的表达式的值设置到对应的属性上
移除元素的指令属性
数据绑定
1. 一旦更新了data对象中的某个属性数据,所有界面上直接/间接使用此属性的节点都会更新;
2. 数据劫持:Vue中用来实现数据绑定的一种技术;
3. 劫持的基本思想:也是通过Object.defineProperty()为data对象中的属性添加setter/
getter,来监视所有属性数据的变化,并随之更新界面;
4. vm --> data --> 更新界面
5. 双向数据绑定:建立在单向数据绑定的基础之上;
1. 在解析v-model指定时,给当前元素添加input监听;
2. 当input的value发生变化时,将最新的指赋给当前表达式对应的data对象中的属性。
数据绑定
一旦更新了data中的某个属性数据,所有界面上直接使用或者间接使用此属性的结点都会更新
数据劫持
数据劫持是vue中用来实现数据绑定的一种技术
基本思想:通过defineProperty来监视data中的所有属性(任意层次)
一旦变化就去更新界面
四个对象
Observer
用来对data所有属性数据进行劫持
给data中所有属性重新定义属性描述符(get/set)
为data中的每个属性创建对应的dep对象
Dep(Depend)
data中的每个属性(所有层次)都对应一个dep对象
创建的时机:
在初始化definedata中各个属性时创建对应的dep对象
在data中的某个属性值被设置为新的对象时
当watcher被创建时,内部将当前watcher对象添加到对应的dep对象的subs中
当此data的属性值发生改变时,subs中所有的watcher都会收到更新的通知,从而更新对应的界面
Compiler
用来解析模板页面的对象
利用compile对象解析模板页面
每解析一个表达式(非事件指令)都会创建一个对应的watcher对象,并建立watcher与dep的关系
compile与watcher关系:一对多的关系
Watcher
模板中每个非事件指令或者表达式都对应一个watcher对象
监视当前表达式数据的变化
创建的时机:在初始化编译模板时
Dep
实例什么时候创建
初始化给data属性进行数据劫持时创建的
个数
与data中的属性一一对应
Dep的结构
id:标示
subs:[] n个相关的watcher的容器(数组)
Watcher
实例什么时候创建
初始化的解析大括号表达式/一般指令时创建
个数
与模板中的表达式(不包含事件指令)一一对应
Watcher结构
cb ==> 回调函数,用于界面更新
vm ==> vm实例
exp ==> 对应的表达式
depIds ==> {} 相关的n个dep容器对象
value ==> 当前表达式的对应value
Dep 与Watcher的关系
什么关系:
多对多
data属性 ==> Dep ==> n个watcher(模板中有多个表达式使用了此属性)
表达式 ==> watcher ==> m个Dep (多层表达式a.b.c)
如何建立
data中属性的get()中建立
什么时候建立
初始化的解析模块中的表达式创建watcher对象时
在dep 创建完成后,创建watcher时再建立关系
双向数据绑定
双向数据绑定
双向数据绑定时建立在单向数据绑定(model==>view)的基础之上的
双向数据绑定的实现流程
在解析v-model指令时,给当前元素添加input事件监听
当input的value发生变化时,将最新的值赋值给当前表达式所对应的data属性