Open adodo0829 opened 4 years ago
数据响应式: 与data绑定的视图, 互相状态的更改是双向的 这里以 data 的初始化来梳理 vue 是如何将数据变为响应式的... 原理图
function initData (vm: Component) { let data = vm.$options.data // 省略...主要 data 的校验等操作 // 调用observe, 对数据进行双向绑定 observe(data, true /* asRootData */) }
Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定上方法。 结果: 添加ob属性; 调用defineReactive方法实现响应式
结果
function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value)) { return } let ob: Observer | void // __ob__: 用这个标记属性来判断是否已经有Observer实例, 防止重复绑定 // 如果没有Observer实例, 则会新建一个Observer实例并赋值给__ob__这个属性 // 如果已有Observer实例则直接返回该Observer实例 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( /*这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。*/ observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { /*如果是根数据则计数,后面Observer中的observe的asRootData非true*/ ob.vmCount++ } return ob } // Observer class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 通过Object.defineProperty将data 绑定到__ob__属性上 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } // 如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法, // 达到监听数组数据变化响应的效果 this.observeArray(value) } else { // 如果是对象, 遍历进行绑定 this.walk(value) } } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
function defineReactive (obj: Object,key: string,val: any,customSetter?: Function) { // 每个对象值的 getter 都持有一个 dep 实例, 触发 getter 的时候会调用 dep.depend() 方法 const dep = new Dep() // 获取属性描述符, 如果不可配置, 则不会添加添加响应式 // 这也是为什么 Object.freeze(data)能优化的原因 const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,// 保证不会覆盖之前已经定义的getter/setter const getter = property && property.get const setter = property && property.set // 对象的子对象递归进行observe并返回子节点的Observer对象 let childOb = observe(val) // 核心方法 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // 如果原本对象拥有getter方法则执行 const value = getter ? getter.call(obj) : val if (Dep.target) { // 进行依赖收集 dep.depend() if (childOb) { // 如果有子对象, 对子对象进行依赖收集 // 其实就是将同一个watcher观察者实例放进了两个depend中, // 一个是正在本身闭包中的depend,另一个是子元素的depend childOb.dep.depend() } if (Array.isArray(value)) { // 如果是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归 dependArray(value) } } return value }, set: function reactiveSetter (newVal) { // 通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作 const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { // 如果原本对象拥有setter方法则执行setter setter.call(obj, newVal) } else { val = newVal } // 新的值需要重新进行observe,保证数据响应式 childOb = observe(newVal) // dep对象通知所有的观察者watcher dep.notify() } }) }
我们看到添加响应式的时候引入了一个dep对象, 并在get(属性访问时)调用 dep.depend(), 在set(属性设置时)调用 dep.notify();
在根节点root下我们会最终调用 render 函数; 让 data 添加响应式; 所有被视图所依赖的data中的数据就会被getter收集到Dep的subs中去;
修改模板中的 data 时, 我们便会触发notify方法, 即Dep的subs中的更新(update)函数
class Dep { static target: ?Watcher; // 静态属性, 一个 watcher 实例 id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] // watcher 数组 } /*添加一个观察者对象*/ addSub (sub: Watcher) { this.subs.push(sub) } /*移除一个观察者对象*/ removeSub (sub: Watcher) { remove(this.subs, sub) } /*依赖收集,当存在Dep.target的时候添加观察者对象*/ depend () { if (Dep.target) { Dep.target.addDep(this) } } /*通知所有订阅者, 派发更新*/ notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { // 派发更新 subs[i].update() } } } /*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/ Dep.target = null const targetStack = [] export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
全局的 Dep.target 它就是一个 watcher 对象, 添加完后需要释放其指向; 同一时间只能有一个全局的 Watcher 被计算... 下一次添加时会进行判断, 不释放就会重复指向, 岂不是乱套了...
我的理解是dep负责收集 watcher, 对其进行管理, 每个响应式数据都有对应的watcher 还有就是响应式对象的属性描述符: 对 dep 有引用, 访问 getter 和 setter 时会触发相应 dep 逻辑 这就是传说中的依赖收集, 不就是一个闭包引用吗?
## Watcher构造函数(核心): 实例化 watcher 对象, 接受 dep 通知, 触发回调逻辑 ```js class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: ISet; newDepIds: ISet; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm /*_watchers存放订阅者实例*/ vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter /*把表达式expOrFn解析成getter*/ if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /*获得getter的值并且重新进行依赖收集*/ get () { /*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/ pushTarget(this) let value const vm = this.vm /* 执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。 在将Dep.target设置为自身观察者实例以后,执行getter操作。 譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c, 那么在执行getter的时候就会触发a跟c两个数据的getter函数, 在getter函数中即可判断Dep.target是否存在然后完成依赖收集, 将该观察者对象放入闭包中的Dep的subs中去。 */ if (this.user) { try { value = this.getter.call(vm, vm) } catch (e) { handleError(e, vm, `getter for watcher "${this.expression}"`) } } else { value = this.getter.call(vm, vm) } /*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/ if (this.deep) { /*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/ traverse(value) } /*将观察者实例从target栈中取出并设置给Dep.target*/ popTarget() this.cleanupDeps() return value } /*添加一个依赖关系到Deps集合中*/ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } /*清理依赖收集*/ // 每次数据变化都会重新 render, 那么 vm._render() 方法又会再次执行,并再次触发数据的 getters, // 所以 Wathcer 在构造函数中会初始化 2 个 Dep 实例数组,newDeps 表示新添加的 Dep 实例数组,而 // deps 表示上一次添加的 Dep 实例数组。 // cleanupDeps 函数会首先遍历 deps,移除对 dep.subs 数组中 Wathcer 的订阅, // 然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换,并把 newDepIds 和 newDeps 清空。 cleanupDeps () { /*移除所有观察者对象*/ let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /* 当依赖发生改变的时候进行回调。 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { /*同步则执行run直接渲染视图*/ this.run() } else { /*异步推送到观察者队列中,由调度者调用。*/ queueWatcher(this) } } /* 调度者工作接口,将被调度者回调。 */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. /* 即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。 */ isObject(value) || this.deep ) { // set new value const oldValue = this.value /*设置新的值*/ this.value = value /*触发回调渲染视图*/ if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ /*获取观察者的值*/ evaluate () { this.value = this.get() this.dirty = false } /*收集该watcher的所有deps依赖*/ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /*将自身从所有依赖收集订阅列表删除*/ teardown () { if (this.active) { /*从vm实例的观察者列表中将自身移除,由于该操作比较耗费资源,所以如果vm实例正在被销毁则跳过该步骤。*/ if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
// 首先我们要实例化嘛, new Watcher()会调用自身的 getter, 结果是添加一个Dep target export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target }
pushTarget(this); // 其实是添加了依赖 // 还有就是等待 dep 通知我, 然后出发相应的回调逻辑
add: 依赖图
数据响应式原理梳理
数据响应式: 与data绑定的视图, 互相状态的更改是双向的 这里以 data 的初始化来梳理 vue 是如何将数据变为响应式的... 原理图
初始化 data
进行数据观测 Observer: 返回一个 ob 实例对象
Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定上方法。
结果
: 添加ob属性; 调用defineReactive方法实现响应式defineReactive核心方法: 添加响应式
我们看到添加响应式的时候引入了一个dep对象, 并在get(属性访问时)调用 dep.depend(), 在set(属性设置时)调用 dep.notify();
Dep 构造函数: 实例化一个 dep 对象, 管理 watcher
我的理解是dep负责收集 watcher, 对其进行管理, 每个响应式数据都有对应的watcher 还有就是响应式对象的属性描述符: 对 dep 有引用, 访问 getter 和 setter 时会触发相应 dep 逻辑 这就是传说中的依赖收集, 不就是一个闭包引用吗?
pushTarget(this); // 其实是添加了依赖 // 还有就是等待 dep 通知我, 然后出发相应的回调逻辑