最近看了一下Vue相关的东西 ,整理一下流程:
1. 数据代理 -> Object.defineProperty()
let vm = new Vue({
data:{
a:0
}
})
// 为什么能通过 vm.a 访问到 data 中的 a ?
// 因为会通过 Object.defineProperty 设置了数据代理
Object.defineProperty(vm, 'a', {
get(){
return data.a
},
set(val){
data.a = val
}
})
2. 模板解析 -> NodeType -> Reg -> {{}} -> v-on -> bind/html/class/text
Vue 2 是虚拟节点VNode
let vm = new Vue({
el: '#my',
data:{
a:0
}
})
// 拿到 el 对应的模板以后, createDocumentFragment() 产生一个Dom片段
// 再将el所有子节点插入到该Fragment
// 遍历Fragment中每个节点
// 文本 => 匹配{{}}
// 元素 => 编译元素的指令
// 普通指令/事件指令 => 编译完后删除该指令
// 包含子节点 => 继续迭代
3. 数据绑定和响应式 -> 使用数据劫持的技术实现
一旦更新data中某个属性数据, 所有界面直接使用或间接使用该属性的节点会更新
基本思路: 通过defineProperty监视data中所有层级数据的变化, 变化则更新界面
vm.a = 3
=> vm.a.set // 更新data中的a
=> vm.data.a = 3
=> vm.data.a.set //更新界面
增加observe => 递归对每个属性 Object.defineProperty 增加 get set, 并且设为configurable:false
除了事件指令, 每个指令/表达式 => 增加watcher
Dep -> {
实例创建时间 => 给data中每个属性增加observe数据劫持时(增加get,set)创建的, 在模板编译之前, new watcher()以前
数量 => 与data中属性一一对应(所有层级)
结构 => {
id: 标识 uid
subs: [] subscribe订阅者数组, 内容是 watchers // 即需要更新的所有相关表达式
}
}
watcher -> {
实例创建时间 => 初始化解析大括号表达式/一般指令时创建
数量 => 与大括号表达式/一般指令一一对应
结构 => {
cb: cb 界面更新回调
vm: vm
exp: 对应的表达式
depIds: {} 相关的n个dep // 主要用于判断关系是否建立, 避免再次建立dep watcher关系
value: get() 当前表达式value
}
}
Dep与Watcher关系
关系 ->
data属性 -> 一个dep -> n个watcher(属性在模板多次使用)
表达式 -> watcher -> n个dep(多层表达式, 如a.b, 对应了两个dep)
建立 ->
初始化data数据 -> 每个数据增加dep, 并增加 get, set -> 编译模板 -> 拿到表达式 -> 每个表达式增加一个watcher -> watcher初始化会有个value属性 -> 该value属性调用了dep的get方法 -> dep的get方法depend() -> 通过watcher的depIds判断关系是否建立 -> dep 保存watcher到subs -> watcher 的depIds保存dep
响应式 ->
vm.name = 'abc' -> data中name属性值变化 -> name的 set() -> dep.notify() -> dep中subs数组中每个watcher进行update() -> watcher.cb 回调 -> updater更新界面
总结
整体实现分为已下步骤
- 实现一个Observer,对数据进行劫持,通知数据的变化(将使用的要点为:Object.defineProperty()方法)
- 实现一个Compile,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数
- 实现一个Watcher,将其作为以上两者的一个中介点,在接收数据变更的同时,让Dep添加当前Watcher,并及时通知视图进行update
- 实现一些VUE的其他功能(Computed、menthods)
- 实现MVVM,整合以上几点,作为一个入口函数