模板编译原理
把template 模板编译成 html 的ast => ast经过compilerToFunction(ast)处理 => render函数 => vm._update(vm._render()),其中render函数执行后得到的是虚拟dom,update函数根据新旧虚拟dom进行patch(diff算法)更新真实dom。
-
template:
template -
html的ast:
html的ast: -
render函数:
render函数
不同节点的虚拟dom生成函数
-
虚拟dom:
虚拟dom:
-
data数据返回的是个对象,在vue2.x时,通过Object.defineProperty劫持该对象,并且每个key对应1个dep实例,用于收集依赖和触发更新,在get使用depend收集依赖,在set时使用notify触发更新,更新对应着更新watcher实例的update方法,一个组件有一个对应的更新watcher实例,
劫持的对象 -
dep实例中有watcher的实例数组,watcher有所有收集的dep实例,当数据发生变化时,触发dep实例的notify函数,该函数循环dep收集的watcher的实例数组,将实例的update方法放入queueWatcher方法中延迟执行(涉及到nextTick, vue用promise => MutationObserver => setImmediate => setTimeout)[setImmediate 的设计更像 nodejs 中的 process.nextTick]
收集依赖
- 数据劫持的方法:
function defineReactive(obj, key, value) {
let childOb = observe(value) // 递归进行观测数据. 不管有多少层,我都进行defineProperty
//childOb 如果有值 那么就是数组或对象
// 数组的dep
// vue2 慢的原因 主要在这个方法中
let dep = new Dep() // 每个属性都增加一个dep
Object.defineProperty(obj, key, {
get() {
// debugger
if (Dep.target) {
dep.depend()
if (childOb) {
// 取属性的时候,会对对应的值(对象本身和数组)进行依赖收集
childOb.dep.depend() // 让数组和对象也记住当前的watcher
if (Array.isArray(value)) {
// 可能是数组套数组的可能
dependArray(value)
}
}
}
return value // 闭包, 此value 会像上层的value进行查找
},
// 一个属性可能对应多个watcher, 数组也有更新
set(newValue) {
// 如果设置的是一个对象,那么会再次进行劫持
if (newValue === value) return
observe(newValue)
// console.log('修改')
value = newValue
dep.notify()
}
})
}
- dep:
let id = 0
// dep.subs = [watcher]
// watcher.deps = [dep]
class Dep {
constructor() {
// 把watcher 放到dep
this.subs = []
this.id = id++
}
depend() {
// 要给watcher 也加一个标识,防止重复
// 让dep记住这个watcher watcher还要记住dep 相互关系
Dep.target.addDep(this) // 在watcher中在调用addSub方法
console.log('addDep', this)
console.log('Dep.target', Dep.target)
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach((watcher) => watcher.updata())
}
}
Dep.target = null // 这里是一个全局的变量 window.target 静态属性
export default Dep
- watcher:
import Dep from './dep'
import { queueWatcher } from './scheduler'
let id = 0
class Watcher {
constructor(vm, fn, cb, options, ) {
this.vm = vm
this.fn = fn
this.cb = cb
this.options = options
this.id = id++
this.depsId = new Set()
this.deps = []
this.getter = fn // fn就是页面渲染逻辑
this.get() // 表示上来后就做一次初始化
console.log( 'wwwwwwwww',vm.$options.render.toString(), this)
}
addDep(dep) {
let did = dep.id
if (!this.depsId.has(did)) {
this.depsId.add(did)
this.deps.push(dep)
dep.addSub(this)
}
}
get() {
// debugger
Dep.target = this // window.target = watcher
this.getter() // 页面渲染逻辑
Dep.target = null // 渲染完毕后,就将标识清空了,只有在渲染的时候才会进行依赖收集
}
updata() {
// 每次更新数据都会同步调用这个updata方法,可以将更新的逻辑缓存起来,等会同步更新数据的逻辑执行完毕后,依次调用(去重逻辑)
console.log('缓存更新')
queueWatcher(this)
// 可以做异步更新
// this.get() vue.nextTick
}
run() {
console.log('真正更新')
this.get() // render() 取最新的vm上的数据
}
}
let queue = [] // 这个存放要更新的watcher
let has = {}
function flushSchedulerQueue() {
queue.forEach((watcher) => watcher.run())
queue = []
has = {}
pending = false
}
let pending = false
export function queueWatcher(watcher) {
// 一般情况下,写去重,可以采用这种方式,如果你使用set的时候
let id = watcher.id
console.log('queueWatcher', watcher)
if (has[id] == null) {
has[id] = true
queue.push(watcher)
console.log('watch queue', queue);
if (!pending) {
// 防抖 多次执行, 只走1次
// setTimeout(() => {
// queue.forEach((watcher) => watcher.run())
// queue = []
// has = {}
// pending = false
// }, 0)
nextTick(flushSchedulerQueue)
pending = true
}
}
}
export default Watcher