# vue源码 响应式系统分开讲
# data的响应式流程
/src/core/instance/state.js
initdata
阶段 先proxy
一层代理后,初始化observer
类
// initData
function initData(data) {
···
observe(data, true)
}
// observe
function observe(value, asRootData) {
···
ob = new Observer(value);
return ob
}
observer
类会给data
加上一个不可枚举的属性_ob_
,标识为响应式对象,并且外界无法通过遍历获得该属性值
// 观察者类,对象只要设置成拥有观察属性,
//则对象下的所有属性都会重写getter和setter方法,
//而getter,setter方法会进行依赖的收集和派发更新
var Observer = function Observer (value) {
···
// 将__ob__属性设置成不可枚举属性。外部无法通过遍历获取。
def(value, '__ob__', this);
// 数组处理
if (Array.isArray(value)) {
···
} else {
// 对象处理
this.walk(value);
}
};
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable, // 是否可枚举
writable: true,
configurable: true
});
}
- 如果是对象,遍历 walk遍历重写getter/setter后调用defineReactive,如果是数组是另外的处理
Observer.prototype.walk = function walk (obj) {
// 获取对象所有属性,遍历调用defineReactive###1进行改写
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive###1(obj, keys[i]);
}
};
defineReactive
里会实例化dep
类,这个类为每个数据创建一个依赖管理,实质也是Object.defineProperty
data
被访问时会被getter
拦截
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
}
- 实例挂载前会创建一个
watcher
,见mountComponent
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
render
的时候会访问data
,触发getter
进行依赖收集
dep.depend();
...
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
// newDepIds和newDeps记录watcher拥有的数据
this.newDepIds.add(id);
this.newDeps.push(dep);
// 避免重复添加同一个data收集器
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
- 数据发生改变后,触发
setter
, 判断逻辑后,dep.notify()
进行更新的派发
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
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() //
}
- 更新时会将每个
watcher
推入队列中
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this) //推入队列
}
}
run()
还是会调用this.get()
触发getter
并重新收集
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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
# computed的响应式流程
initComputed
/src/core/instance/state.js
function initComputed (vm: Component, computed: Object) {
...
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
...
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher( // 为每个computed属性实例化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)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
....
}
}
}
- 计算属性结果是会被缓存的,只有在响应式数据发生变化的时候才会被重新求值
...
? createComputedGetter(key)
...
Object.defineProperty(target, key, sharedPropertyDefinition)
createComputedGetter()
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) { // 标志是否执行过计算
watcher.evaluate() // -->this.get()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
computed watcher
不会立即执行依赖的更新派发,会通过dirty
进行标记
# 数组的响应式流程
- 数组的改变不会出发
setter
进行依赖更新,所以vue创建了一个新的数组类,重写后改变指向 - 访问时依旧触发
getter
进行依赖派发 - vue的做法是在原型链上对数组方法进行重写
- /core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch
- 当调用数组内部一些操作时,会执行
arrayMethods
进行响应式
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
})
})
def
也是Object.defineProperty
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
...
- 在
observe
类里也有对数组相关的处理constructor
里
...
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
protoAugment
将当前数组的原型指向arrayMethods
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
copyAugment
不支持_proto_
时,通过代理
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])
}
}
# 对象检测异常
test:{a:1}
,当test.b = 2
添加时vue无法检测(因为没有依赖收集的过程)- 用
vue.set
或vue.$set
解决
# 手动watch的响应式过程
initWatch
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
createWatcher
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
vm.$watch
Vue.prototype.$watch = function (c// yiki watcher
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) { // 立即执行
...
}
return function unwatchFn () {
watcher.teardown()
}
}