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
首先我们看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同理,删除属性也会派发更新通知。
Dep
在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的桥梁。
Watcher
那么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
}
}
}
traverse
// 源码目录:src/core/observer/traverse.js
import {
_Set as Set, isObject } from '../util/index'
import type {
SimpleSet } from '../util/index'
import VNode from '../vdom/vnode'
const seenObjects = new Set()
/**
* Recursively traverse an object to evoke all converted
* getters, so that every nested property inside the object
* is collected as a "deep" dependency.
*/
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
// 深度遍历对象的每个属性,每次访问都会触发它的getter,从而收集到依赖,就是订阅它们变化的 watcher。
// 这里有一个小优化是使用集合Set记录依赖收集器的Id,避免重复访问
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
// 避免重复收集依赖,导致重复触发getter
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}