vue源码阅读之响应式原理
Vue 中对数据的响应式,即对 props
、data
、computed
的变化进行响应式更改。
其入口是在 src/core/instance/init.js
里的 initState(vm)
里进行实现的。
initState(vm)
里 初始化了 props
, methods
, data
, computed
, watch
。
initData
从 initData
入手来看下:
function initData (vm: Component) {
let data = vm.$options.data
// 将_data作为中间变量,后续 proxy 里会用到
data = vm._data = typeof data === 'function'
? getData(data, vm) // getData 做了错误处理,并控制了 Dep.target,使得避免在获取 data 初始值的过程中意外地把依赖记录下来。
: data || {}
// 省略相关 warn 代码...
// 进行 data proxy methods 重复 key 的检验并抛出 warn msg
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
// 省略相关关于重复 key 检测 warn 代码
// 对 _data 对象上的每个 key 进行 getter setter 设置
// 使得访问 this.xxx 代理到 this._data.xxx
proxy(vm, `_data`, key)
}
// observe data
/**
* 响应式核心方法
* 即 new Observer(value)
* 具体看 new Observer(value) 逻辑
* */
observe(data, true /* asRootData */)
}
initData
里首先将原始 data 复制了份 _data,然后遍历了每个 key,
通过 proxy
方法对 _data 里的 key 进行了 getter setter 设置。
使得访问 this.xxx 代理到 this._data.xxx。
接下来执行 observe(data, true /* asRootData */)
。
该方法结果是 return new Observer(value)
,返回了一个 Observer
实例。
Observer
来看下 Observer
类的定义,这也是响应式实现的主要地方。
class Observer 源码
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 添加 __ob__ 属性,值为自身,即 this.data.__ob__ = this
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 对数组原生方法进行了劫持,使得push 等能监控到,来触发响应式,但无法监控到 通过下标更改的情况
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 对数组进行遍历执行 observe
this.observeArray(value)
} else {
// 会进入 walk 方法
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// 通过 defineReactive 方法,设置 getter setter, 真正的响应代码
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Observer
在 new 的时候,主要逻辑会先执行 this.dep = new Dep()
来创建依赖实例,然后处理数组/对象的情况。
这中间还对里面的每个元素进行了深度遍历,使得每个元素均是响应式的。
再主要来看 this.walk(value)
方法,该方法通过 defineReactive
方法,设置 getter setter,这是真正的响应代码。
defineReactive 源码
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 缓存依赖
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 这里代码类比 computed 里属性定义的 getter setter 那样
// 定义了,就用定义的 getter setter 方法
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// childObserve 对象,对 child 进行监听
let childOb = !shallow && 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) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// 这步触发依赖并设置值
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
// 自己定义了 setter 就调用自己的 setter, 没有就直接赋值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// newVal 是个 object,则继续递归执行监听 observe
childOb = !shallow && observe(newVal)
// 触发更新
dep.notify()
}
})
}
defineReactive
代码可以简化如下:
_defineReactive (obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get: function reactiveGetter() {
dep.addSub(Dep.target)
return val
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value) return
// 自己定义了 setter 就调用自己的 setter, 没有就直接赋值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
dep.notify()
}
})
}
在 defineReactive
函数内,通过闭包实例化了 Dep
这个订阅者。
Dep
class Dep 源码
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
// Dep.target 即 watcher:watcher.addDep(dep)
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
// 保存当前全局唯一存在的 watcher
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
这里可以简化为:
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Dep.target = null
Dep
这个依赖订阅者,有自己的 subs
用来缓存 Dep.target
,Dep.target
其实是一个 watcher 实例,这里先不管 watcher。
回到 defineReactive
,在 get 里将 watcher 实例添加到订阅者 Dep 里,
在 set 时遍历缓存在 dep.subs 数组里的 watcher 实例,执行 watcher.update()
方法,来触发更新。
Watcher
来看下 class Watcher
class Watcher 源码
export default 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: SimpleSet; // ES6 set 类型
newDepIds: SimpleSet; // ES6 set 类型
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function, // 表达式本身 [ getter | noop ]
cb: Function, // [ noop ]
options?: ?Object, // { lazy: true } // 如果设置为 true 则在第一次 get 的时候才计算值,初始化的时候并不计算。init 时为 true
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
/*
before作用是定义 beforeUpdate 钩子:
「
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
」
*/
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 = []
// 两个id 为 set 实例,在 add 时候,防止重复添加相同 id
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for 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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
// 执行 getter, 并重新收集依赖关系
get () {
/* pushTarget 代码:
「
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()
}
」
*/
// 这行代码作用:在进行 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 {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
// if (!this.newDepIds.has(id)) 判断,为了防止如 computed 里
// `return this.a + this.a` 这种相同值的计算情况
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
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
}
/**
* 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 {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
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)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
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) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
上面说到在 set 某个值的时候,实际上会执行 watcher.update()
方法。来看下这个 update 方法:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
update 方法,同步情况执行 this.run()
,一般情况是异步,会进入到最后个 else 里,执行 queueWatcher(this)
。
queueWatcher
里主要代码 nextTick(flushSchedulerQueue)
,它将 watch 队列放到 nextTick 里统一执行,这也使得连续 set 同个数据两次不同值,不会更新两次视图,提升了一定性能。
computed 的原理
注意 watcher 实例化 constructor 最后行有个 this.value
:
this.value = this.lazy
? undefined
: this.get()
这个 value,在 initComputed
的时候,会对 computed 里的每个 key new Watcher(...)
,所以可以说是 computed 的双向绑定实现即是通过 Watcher 来响应变化。
initComputed 源码函数:
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
// 遍历定义在 computed 上的 key
for (const key in computed) {
const userDef = computed[key]
// 处理自定义 getter 情况
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
// 非 ssr 情况,对 key 进行 监控,ssr 下就不需要监控了
if (!isSSR) {
// create internal watcher for the computed property.
// 对每个key 设置 Watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
// 将 computed 里定义的 key 在实例上添加进行代理,使得可直接通过 this.xx 来访问
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
从上面传入参数 { lazy: true }
可知,初始化时 value 为 undefined
,
defineComputed(vm, key, userDef)
即 Object.defineProperty(target, key, sharedPropertyDefinition)
.
在 sharedPropertyDefinition
里会拿之前 watcher 过的依赖的值:
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
所以 computed 中的值在读取的时候,拿到的结果即 watcher.value。
总结下 data 响应式流程:
this.xx 代理到 this._data.xx 然后 _data 在 Observer 里定义过 setter getter,在初始 render 时会收集依赖到闭包的 Dep 依赖实例的数组里,在 setter 时会通知所有依赖即每个 watcher 实例进行 update 更新。
总结下 computed 响应式流程:
this.xx 初始化时会先创建一个 watcher,然后通过 Object.defineProperty
进行 setter,getter 设置,其中 getter 会得到是 watcher.value 的值。
注意:
Observer 文件下定义了个
arrayMethods
,该方法重写了数组方法,使得改变 push 等操作也能触发更新。
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})