Vue的响应式是基于数据拦截+发布订阅模式,包含了四个模块:
Observer:通过Object.defineProperty或者Proxy拦截对象的getter和setter方法,从而令每个响应式数据都拥有一个Dep,当触发getter时收集依赖(使用该数据的Watcher),当触发setter时派发更新。Dep:依赖收集器,维护使用数据的各个依赖Watcher。Watcher:将视图所依赖的数据绑定到Observer的依赖收集器Dep中,当数据出现变化时触发setter,就通知调用Dep的notify方法,通知依赖该数据的各个Watcher进行update,进而触发指定对应的更新方法更新视图,最终把模型和视图关联起来。Complie:就是template模板指令解析器,对每个元素节点的指令进行扫描解析,根据指令模板替换数据,同时根据指令模板绑定Watcher的更新函数。以微博举个可能不太恰当的例子:微博上某大佬A技术非常牛,我想想随时知道大佬A的最新动态,于是我在微博关注了大佬A。某一天大佬A发了一条动态,微博立马就会通知我。
在这个例子中,我就是一个Watcher,微博就是一个Dep,而大佬A就是一个Observer。在Watcher关注Observer的时候,Dep收集了Watcher对Observer的依赖,于是Observer更新时,就直接通过Dep通知所有的Watcher。
要理解Vue的响应式原理,我们就只需要知道Watcher怎么关注Observer,Observer如何收集Watcher的依赖,Observer如何通知Watcher。so,往下看吧。
首先我们看Observer的实现:
// 源码目录 src/core/observer/index.js import Dep from './dep' import VNode from '../vdom/vnode' import { arrayMethods } from './array' import { def, warn, hasOwn, hasProto, isObject, isPlainObject, isPrimitive, isUndef, isValidArrayIndex, isServerRendering } from '../util/index' // 获取Array原型对象上的所有属性名,包括不可枚举的,但不包括Symbol,其中有7种方法已被拦截 const arrayKeys = Object.getOwnPropertyNames(arrayMethods) // 是否应该变成响应式数据,在组件内部更新的时候,我们可能不希望变成数据变成响应式 export let shouldObserve: boolean = true // 改变shouldObserve的状态 export function toggleObserving (value: boolean) { shouldObserve = value } // Observer实现核心,修改每个属性的getter和setter,实现其依赖收集和更新派发 export class Observer { value: any; dep: Dep; vmCount: number; // 把这个对象作为$data的实例个数 constructor (value: any) { // 对于可响应式数据对象或者数组本身 this.value = value // 每个Observer自己也维护一个Dep this.dep = new Dep() // 实例数量 this.vmCount = 0 // 修改属性描述符,增加__ob__属性,其值为当前实例 def(value, '__ob__', this) // 如果Observe的对象是数组 if (Array.isArray(value)) { // 判断当前宿主环境是否实现__proto__属性 if (hasProto) { // 使用__proto__替换原型对象,拦截数组原生方法。后面会讲到 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]) } } } // 使用__proto__替换实例的原型对象 function protoAugment (target, src: Object) { target.__proto__ = src } // 扩充实例对象的属性 function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } } // 创建Observer实例,如果存在就直接返回,如果不存在则新建一个返回。单例模式 export function observe (value: any, asRootData: ?boolean): Observer | void { // 非对象类型和虚拟节点对象直接退出 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 如果有__ob__属性,且其值是Observer的实例,则直接使用这个实例 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 生成新的Observe实例 ob = new Observer(value) } // 这个对象是Vue实例的根$data if (asRootData && ob) { ob.vmCount++ } return ob } // 将对象的属性变成响应式 export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // 每个属性维护一个依赖收集器 const dep = new Dep() // 获取属性描述符 const property = Object.getOwnPropertyDescriptor(obj, key) // 如果属性不可配置,即不能设置getter和setter,则跳过 if (property && property.configurable === false) { return } // 有可能开发者自己重新定义了getter和setter,需要缓存一份 const getter = property && property.get const setter = property && property.set // 没有getter或者有setter,且入参只有2个 if ((!getter || setter) && arguments.length === 2) { // 当前值就是obj的key属性对应的值 val = obj[key] } // 若属性值也是对象,深度遍历递归执行observe实例化 let childOb = !shallow && observe(val) // 核心!!!设置getter和setter Object.defineProperty(obj, key, { // 可枚举 enumerable: true, // 可配置 configurable: true, // getter,触发getter时,收集依赖。 get: function reactiveGetter () { // 获得当前值 const value = getter ? getter.call(obj) : val // 如果当前有目标依赖这个数据,则添加依赖Watcher if (Dep.target) { dep.depend() // 子对象也要增加依赖收集 if (childOb) { childOb.dep.depend() // 数组特殊处理 if (Array.isArray(value)) { dependArray(value) } } } return value }, // setter,触发setter时,派发更新。newVal待设置的值 set: function reactiveSetter (newVal) { // 获得当前值 const value = getter ? getter.call(obj) : val // 如果值没有变化,则不触发更新通知 if (newVal === value || (newVal !== newVal && value !== value)) { return } // 自定义setter方法 if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // 如果属性不支持setter,则直接跳过 if (getter && !setter) return if (setter) { // 有自己的setter就调用自身setter setter.call(obj, newVal) } else { // 更新赋值 val = newVal } // 子对象也要重新observe实例化 childOb = !shallow && observe(newVal) // 通知更新 dep.notify() } }) } // 也就是$set的实现,增加一个对象上没有的属性,同时将其转换为响应式 export function set (target: Array<any> | Object, key: any, val: any): any { // 异常检测,基础类型的值不能 if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 处理数组的set if (Array.isArray(target) && isValidArrayIndex(key)) { // 扩容 target.length = Math.max(target.length, key) // 调用已经hack的splice方法 target.splice(key, 1, val) // 返回值 return val } // 如果这个属性在对象中,而且这个属性不在原型链上,证明这个是属性已经是响应式的了 if (key in target && !(key in Object.prototype)) { // 直接更新属性值 target[key] = val return val } // 获得对象的Observer实例 const ob = (target: any).__ob__ // 不允许向实例中或者$data添加响应式数据 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 该实例没有响应式数据,则不需要将其变成响应式,直接赋值返回 if (!ob) { target[key] = val return val } // 将该属性变为响应式 defineReactive(ob.value, key, val) // 通知依赖这个对象的Watcher更新 ob.dep.notify() return val } /** * Delete a property and trigger change if necessary. */ // 删除对象上的属性 export function del (target: Array<any> | Object, key: any) { // 基础类型的值无法执行属性删除操作 if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 数组直接调用hack后的splice方法 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } // 获取对象的Observer实例 const ob = (target: any).__ob__ // 对象可能不支持删除操作 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } // 没有该属性值,直接返回 if (!hasOwn(target, key)) { return } // 删除属性 delete target[key] // 该对象不是响应式的,直接返回 if (!ob) { return } // 响应式的,通知更新 ob.dep.notify() } // 如果触发getter,则数组的所有元素都需要触发依赖收集。因为Vue针对数组做了额外处理,不像对象一样设置getter方法 function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }总结:Observer的核心是observe方法,其内部维护也维护一个Dep用于收集对该对象的依赖。而observe方法的核心是通过Object.defineProperty覆写Object上各个属性的getter和setter方法,对于Object每个属性都维护一个Dep,收依赖该属性的所有目标(即Watcher),当该属性触发setter且新值与旧值不相同时,派发更新的通知。
比较特别的是数组,虽然可以通过设置下角标的getter和setter方法实现响应式数据,但是尤大出于性能考虑,而是直接hack了数组的7种方法,在调用这7种方法时触发更新,同时将数组中的元素转换为响应式数据。
此外还实现了set和方法,用于解决直接增加对象属性无法触发响应式更新的问题,对象直接使用defineReactive方法,而数组使用hack后的splice方法。del同理,删除属性也会派发更新通知。
在observer的源码中,我们可以发现一个核心就是依赖收集器,也就是Dep对象,它具体是个啥呢?我们接着看其对应的源码。
// 源码目录:src/core/observer/dep.js import type Watcher from './watcher' import { remove } from '../util/index' import config from '../config' // 每个Dep都有唯一的编号 let uid = 0 // Dep就是一个依赖收集器,一个响应式数据的依赖收集器可以被许多指令订阅 export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { // Dep的唯一编号 this.id = uid++ // 所有的订阅者,也就是Watcher收集器 this.subs = [] } // 增加订阅者,即添加Watcher addSub (sub: Watcher) { this.subs.push(sub) } // 取消订阅 removeSub (sub: Watcher) { remove(this.subs, sub) } // 添加依赖 depend () { // Dep.target指向一个具体的Watcher,每个Watcher自己也维护了一个依赖收集器,表示依赖的多个数据 if (Dep.target) { Dep.target.addDep(this) } } // 派发更新 notify () { // 拷贝一份数组 const subs = this.subs.slice() // 非异步情况下,保证Dep中Watcher的有序执行 if (process.env.NODE_ENV !== 'production' && !config.async) { subs.sort((a, b) => a.id - b.id) } // 依次触发更新 for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // target指向依赖当前数据的Watcher。全局唯一,每一次只能有一个Wacther被添加 Dep.target = null // Watcher栈 const targetStack = [] export function pushTarget (target: ?Watcher) { // 推入Watcher targetStack.push(target) // target指向推入的Watcher Dep.target = target } export function popTarget () { // 弹出Watcher targetStack.pop() // 指向Watcher栈栈顶 Dep.target = targetStack[targetStack.length - 1] }总结:Dep的核心不复杂,说白就是两样,一个是用数组收集Watcher,一个是遍历数组通知每个Watcher进行update,而他的静态属性target是连接Wacther和Observer的桥梁。
那么Dep收集的Wacther到底是个什么东西呢?其实就是一个依赖于该数据的订阅者,当数据发生变化时,依赖收集器Dep就是调用notify方法,触发这些watcher的update方法。这个更新方法可以是用户自定的函数,也可以是Vue模板编译后的render函数。
// 源码目录:src/core/observer/watcher.js import { warn, remove, isObject, parsePath, _Set as Set, handleError, noop } from '../util/index' import { traverse } from './traverse' import { queueWatcher } from './scheduler' import Dep, { pushTarget, popTarget } from './dep' import type { SimpleSet } from '../util/index' // 全局编号,唯一性 let uid = 0 // 每个Watcher会解析一个表达式,收集其对应的数据依赖,当表达式所对应的值发生变化时就执行回调函数。 // 它也被用于$watch()和指令当中 export default class Watcher { vm: Component; // 组件实例 expression: string; // 表达式 cb: Function; // 回调函数(一般是更新函数) id: number; // watcher实例的唯一值 deep: boolean; // 是否深层次数据观测,也就是watch的deep属性 user: boolean; // user watch,$watcher就是一个user watcher,它监测到变化就执行回调函数并返回值 lazy: boolean; // 惰性监测,computed watcher,也就是计算属性对应的watcher,它具有懒加载、缓存计算值的特性 sync: boolean; // 是否同步更新,一旦我们设置了 sync,就可以在当前 Tick 中同步执行 watcher 的回调函数,默认是在nextTick中执行,用于优化 dirty: boolean; // 标记watcher是否需要重新求值,用于惰性求值 active: boolean; deps: Array<Dep>; // 用于收集该watcher的依赖的dep,方便一次性移除watcher。还可与lazy配合使用,惰性求值 newDeps: Array<Dep>; // 新的依赖收集器 depIds: SimpleSet; // 与deps配合使用,存储dep的id newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, // 组件实例 expOrFn: string | Function, // 表达式,取数逻辑 cb: Function, // 回调函数(更新函数) options?: ?Object, // 配置选项 isRenderWatcher?: boolean // 是否为渲染watcher。还有两类是Computed Watcher和User Watcher(watch:{}定义的),执行顺序computed->user->render,这样保证更新时数据最新。 ) { this.vm = vm // 保存组件实例 if (isRenderWatcher) { // 绑定实例的依赖renderWatcher vm._watcher = this } // 实例的维护的watcher队列 vm._watchers.push(this) // watcher配置项 if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } 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() : '' // 将表达式解析为getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop 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 ) } } // 非惰性监测的情况下,触发对象属性的getter方法,收集依赖 this.value = this.lazy ? undefined : this.get() } // 触发getter,重新收集依赖 get () { // Dep的target执行当前watcher,用于收集依赖 pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // 深度遍历对象的每个属性,触发每个属性的getter方法,从而收集所有依赖 if (this.deep) { traverse(value) } // 删除Dep上的target popTarget() // 移除当前watcher已经不再依赖的dep this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ // watcher本身 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) } } } // 移除当前watcher已经不再依赖的dep cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] // watcher已经不再依赖的dep,直接移除 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 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { // 同步更新 this.run() } else { // 异步更新,本质是调用nextTick方法 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. 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) } } } } // 针对computed watcher,当前watcher无需更新 evaluate () { this.value = this.get() this.dirty = false } // 该watcher依赖(订阅)的所有dep depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } // 从所有dep中删除其依赖(订阅者)watcher teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { // 移除实例上的watcher remove(this.vm._watchers, this) } let i = this.deps.length // 从维护的deps的中移除其订阅列表中的当前watcher while (i--) { this.deps[i].removeSub(this) } this.active = false } } }