本文基于 Vue 2.6.14 进行源码剖析
为了减少可读性,会对源码进行删减、调整程序、扭转的操作,文中所有源码均可视作为伪代码
文章内容
- 响应式原理相干 function 和 class 的解说
- Object 数据类型的响应式初始化和非凡更新模式
- Array 数据类型的响应式初始化和非凡更新模式
- 渲染 Watcher 的依赖收集和派发更新剖析:流程图
- computed 类型的依赖收集和派发更新剖析:流程图和源码剖析
- watch 类型的的依赖收集和派发更新剖析:源码剖析
响应式原理初始化
响应式数据设置代理
- 拜访 props 的 item 对应的 key 时,应用
this.[key]
会主动代理到vm._props.[key]
- 拜访 data 的 item 对应的 key1 时,应用
this.[key1]
会主动代理到vm._data.[key1]
function initProps(vm: Component, propsOptions: Object) {for (const key in propsOptions) {if (!(key in vm)) {proxy(vm, `_props`, key)
}
}
}
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
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]
// 监测 props 是否曾经有这个 key 了,有的话弹出正告
proxy(vm, `_data`, key)
}
}
export function proxy(target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter(val) {this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Vue.props 响应式数据设置
在合并配置 mergeOptions()中,会调用 normalizeProps()对 props 的数据进行整顿,最终确保 initPros 调用时
props
曾经是一个对象,因而不须要 Observer 判断是否是数组,间接对 key 进行 defineReactive 即可
function initProps(vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
for (const key in propsOptions) {keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
defineReactive(props, key, value)
}
}
Vue.data 响应式数据设置
- 为
data
建设一个Observer
,次要性能是依据 value 类型判断,是数组则递归调用observe
,为每一个 item 都创立一个Observer
对象,如果是对象,则遍历key
,为每一个key
都创立响应式监听
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// observe data
observe(data, true /* asRootData */)
}
export function observe(value: any, asRootData: ?boolean): Observer | void {if (!isObject(value) || value instanceof VNode) {return}
// ... 判断数据 value 是否曾经设置响应式过
let ob = new Observer(value)
return ob
}
export 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()
if (Array.isArray(value)) {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])
}
}
}
Object.defineProperty 响应式根底办法
get
:返回对应 key 的数据 + 依赖收集set
:设置对应 key 的数据 + 派发更新
export function defineReactive(obj: Object, key: string, val: any, ...args) {const dep = new Dep()
let childOb = !shallow && observe(val) // 如果 val 也是 object
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
if (Dep.target) {dep.depend()
if (childOb) {
// key 对应的 val 是 Object, 当 val 外面的 key 产生扭转时
// 即 obj[key][key1]=xxx
// 也会告诉目前 obj[key]收集的 Watcher 的更新
childOb.dep.depend()
if (Array.isArray(value)) {dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {return}
if (setter) {setter.call(obj, newVal)
} else {val = newVal}
childOb = !shallow && observe(newVal)
dep.notify()}
})
}
Dep 响应式依赖治理类
- 每一个 key 都有一个
Dep
治理类 Dep
具备addSub
,即关联Watcher
(渲染 Watcher 或者其它) 的能力Dep
具备depend()
,被Watcher
显式关联,能够被Watcher
触发dep.notify()
告诉它关联Watcher
更新的能力
Dep.target = null
const targetStack = []
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.addDep(this)
}
}
notify () {const subs = this.subs.slice()
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()}
}
}
Watcher 响应式依赖收集和派发更新执行类
get()
办法进行pushTarget(this)
,触发对应的getter
回调,开始收集,而后popTarget(this)
,进行收集,最初触发cleanupDeps()
进行依赖的更新update()
将更新内容压入队列中,而后依据顺序调用Watcher.run()
,也就是回调constructor()
传进来的this.cb
办法
export default class Watcher {constructor(...args) {
this.vm = vm
if (isRenderWatcher) {vm._watcher = this}
vm._watchers.push(this)
this.cb = cb; // 触发更新时调用的办法
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.value = this.lazy
? undefined
: this.get()}
get() {pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
if (this.deep) {traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
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)
}
}
}
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() {if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
} else {queueWatcher(this)
}
}
run() {if (this.active) {const value = this.get()
if (value !== this.value || isObject(value) || this.deep) {
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)
}
}
}
}
depend() {
let i = this.deps.length
while (i--) {this.deps[i].depend()}
}
}
Object 数据类型响应式
最外一层 key 的响应式设置
应用 observe()
对每一个 Object
的key
都进行 Object.defineProperty()
劫持
function observe(value, asRootData) {ob = new Observer(value);
return ob
}
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
this.walk(value);
};
walk (obj: Object) {const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])
}
}
export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {if ((!getter || setter) && arguments.length === 2) {val = obj[key]
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
if (Dep.target) {dep.depend()
}
return value
},
set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal)
} else {val = newVal}
dep.notify()}
})
}
深度 key 的响应式设置
export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {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) {if (setter) {setter.call(obj, newVal)
} else {val = newVal}
childOb = !shallow && observe(newVal)
dep.notify()}
})
}
- 由上面对
observe()
办法的剖析,它会遍历Object
的每一个key
,进行Object.defineProperty
申明 - 对于
Object
每一个key
对应的value
,如果childOb = !shallow && observe(val)
不为空,那么它会遍历value
对应的每一个key
,如果value[key]
也是一个Object
,那么会再次走到childOb = !shallow && observe(val)
,直到所有Object
都为响应式数据为止 - 对于
obj[key]
来说,会调用dep.depend()
,如果obj[key]
自身也是一个对象,即childOb
不为空,那么它就会调用childOb.dep.depend()
,因而当obj[key][key1]=xx
时,也会触发dep.depend()
收集的Watcher
产生更新,例如
data: {
parent: {children: {test: "111"}
}
}
<div>{{parent.children}}</div>
由下面的剖析能够晓得,当 this.parent.children.test
发生变化时,会触发 this.parent.children
收集的 渲染 Watcher
发生变化,从而触发界面从新渲染
额定增加 key
因为 Object.defineProperty()
的限度,无奈实现对 Object
新增 key
的响应式监听,因而当咱们想要为 Object
设置新的 key
的时候,须要调用 Vue.set
办法
export function set(target: Array<any> | Object, key: any, val: any): any {if (key in target && !(key in Object.prototype)) {target[key] = val;
return val;
}
const ob = (target: any).__ob__;
if (!ob) {target[key] = val;
return val;
}
defineReactive(ob.value, key, val);
ob.dep.notify();
return val;
}
Vue.set()
的流程能够总结为:
- 为
Object
减少对应的key
和value
数据 - 将新增的
key
退出响应式监听中,如果key
对应的value
也是Object
,依照下面深度 key 的监听设置剖析,会递归调用observe
进行深度 key 的响应式设置 -
手动触发
Object
收集的Watcher
的刷新操作实质上,下面的三步流程除了第二步有稍微差异之外,其它局部跟 defineReactive 中的 set()办法流程统一
删除 key
删除 key
也无奈触发响应式的变动,须要手动调用 Vue.del()
办法:
- 删除
Object
指定的key
- 手动触发
Object
收集的Watcher
的刷新操作
function del(target: Array<any> | Object, key: any) {if (Array.isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (!hasOwn(target, key)) {return}
delete target[key]
if (!ob) {return}
ob.dep.notify()}
Array 数据类型响应式
前置阐明
依据官网文档阐明,Vue 不能检测以下数组的变动
- 当你利用索引间接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
- 当你批改数组的长度时,例如:vm.items.length = newLength
举个例子:
var vm = new Vue({
data: {items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
为了解决第一类问题,以下两种形式都能够实现和 vm.items[indexOfItem] = newValue 雷同的成果,同时也将在响应式零碎内触发状态更新
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
为了解决第二类问题,你能够应用 splice:
vm.items.splice(newLength)
对 Array[index]数据的响应式监听
如果 item=Array[index]
是Object
数据,应用 observe()
对Array
的每一个 item
都进行响应式的申明
function observe(value, asRootData) {ob = new Observer(value);
return ob
}
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)
} else {copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
}
};
observeArray(items: Array < any >) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])
}
}
Vue.set 更新 Array-item
从上面代码能够看出,Vue.set()
更新数组的 item
实质上也是调用 Array.splice()
办法
export function set(target: Array<any> | Object, key: any, val: any): any {if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
}
Array.splice 更新 Array-item
从下面的剖析能够晓得,一开始会触发 new Observer(value)
的初始化
从上面代码能够晓得,大部分浏览器会触发 protoAugment()
办法,也就是扭转Array.__proto__
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)
} else {copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
}
};
function protoAugment (target, src: Object) {target.__proto__ = src}
// node_modules/vue/src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
而扭转了 Array.__proto__
多少办法呢?
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (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
})
})
// node_modules/vue/src/core/util/lang.js
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
从下面代码剖析能够晓得,Vue
劫持了 Array
的'push'
,'pop'
,'shift'
, 'unshift'
, 'splice'
, 'sort'
,'reverse'
办法,一旦运行了这些办法,会被动触发:
- 调用
Array
原来的办法进行调用,而后返回Array
原来的办法的返回值,如Array.push
调用后的返回值 - 进行
observeArray
的响应式设置,更新新设置的item
(可能为Object
,须要设置响应式) - 手动触发
ob.dep.notify()
,触发对应的Watcher
更新,达到响应式自动更新的目标
渲染 Watcher 依赖收集流程剖析
仅仅剖析最简略的渲染 Watcher 依赖收集的流程,实际上并不是只有渲染 Watcher 一种
渲染 Watcher 派发更新流程剖析
computed 依赖收集和派发更新剖析
测试代码
<div>{{myName}}</div>
// {[key: string]: Function | {get: Function, set: Function} }
computed: {myName: function() {// 没有 set()办法,只有 get()办法
return this.firstName + this.lastName;
}
}
依赖收集流程图剖析
依赖收集代码剖析
computedWatcher 初始化
Vue.prototype._init
初始化时,会调用 initState()->initComputed()
,从而进行computed
数据的初始化
// node_modules/vue/src/core/instance/state.js
function initComputed(vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {const userDef = computed[key];
const getter = typeof userDef === 'function' ? userDef : userDef.get;
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions //{lazy: true}
)
defineComputed(vm, key, userDef);
}
}
从下面代码能够晓得,最终为每一个 computed
监听的数据建设一个 Watcher
,一个数据对应一个computed Watcher,传入{lazy: true}
,而后调用defineComputed()
办法
export function defineComputed(target: any, key: string, userDef: Object | Function) {
// 为了缩小分支判断,不便了解,对立假如 userDef 传入 Function
sharedPropertyDefinition.get = createComputedGetter(key);
sharedPropertyDefinition.set = noop;
Object.defineProperty(target, key, sharedPropertyDefinition)
}
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
}
}
}
从下面代码能够晓得,最终 defineComputed
是进行了 Object.defineProperty
的数据劫持,个别在 computed
中都只写 get()
办法,即
computed: {myName: function() {// 没有 set()办法,只有 get()办法
return this.firstName + this.lastName;
}
}
而回到下面代码的剖析,defineComputed
劫持了 computed
的get()
办法,最终返回watcher.value
渲染 Watcher 触发 ComputedWatcher 的 get()办法执行
当界面上 <template>{myName}</template>
渲染 myName
的时候,会触发 myName
的get()
办法,因为 Object.defineProperty
的数据劫持,会先调用
watcher.evaluate()
->watcher.get()
(从上面的代码能够得出这样的推导关系)-
watcher.depend()
const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) {if (watcher.dirty) {// evaluate () {// this.value = this.get() // this.dirty = false // } watcher.evaluate()} if (Dep.target) {// depend() { // let i = this.deps.length // while (i--) {// this.deps[i].depend() // } // } watcher.depend()} return watcher.value }
// watcher.js get() {// function pushTarget (target: ?Watcher) {// targetStack.push(target) // Dep.target = target // } pushTarget(this); let value; const vm = this.vm; try { // this.getter = return this.firstName + this.lastName; value = this.getter.call(vm, vm); } catch (e) {} finally {if (this.deep) { // watch 类型的 watcher 能力配置这个参数 traverse(value); } popTarget(); this.cleanupDeps();} return value; }
从下面的代码能够晓得,当调用
watcher.evaluate()
->watcher.get()
的时候,会调用: - pushTarget(this):将目前的
Dep.target
切换到Computed Watcher
- this.getter.call(vm, vm):触发
this.firstName
对应的get()
办法和this.lastName
对应的get()
办法。由上面的依赖收集代码能够晓得,此时this.firstName
和this.lastName
持有的Dep
会进行dep.addSub(this)
,收集该Computed Watcher
- popTarget():将目前的
Dep.target
复原到上一个状态 cleanupDeps()
:更新Computed Watcher
的所有依赖关系,将有效的依赖关系删除 (比方v-if
造成的依赖关系不必再依赖)-
最终返回
myName
=return this.firstName + this.lastName;
watcher.evaluate():求值 + 更新依赖 + 将波及到的响应式对象 firstName 和 lastName 关联到 Computed Watcher
export function defineReactive(obj: Object, key: string, val: any, ...args) {const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {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
}
})
}
// Dep.js
depend () {if (Dep.target) {Dep.target.addDep(this)
}
}
// watcher.js
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)
}
}
}
回到 myName
的get()
办法,即上面的代码,咱们刚刚剖析了 watcher.evaluate()
,那么咱们接下来还调用了myName
中watcher.depend()
咱们从下面的代码晓得,这个办法次要是用来收集依赖的,此时的 Dep.target
是渲染 Watcher
,computed Watcher
会进行本身的 depend()
,实质是拿出本人所有记录的Dep(为了不便了解,咱们了解 Dep 就是一个响应式对象的代理)
,computed Watcher
拿出本人记录的所有的 deps[i]
,而后调用它们的depend()
办法,从而实现这些响应式对象 (firstName
和lastName
)与 渲染 Watcher
的关联,最初返回watcher.value
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {if (watcher.dirty) {// 下面剖析触发了 watcher.get()办法
// 失去对应的 watcher.value
// 收集了 firstName+lastName 和 computerWatcher 的绑定
watcher.evaluate();
// 将目前的 Dep.target 切换到渲染 Watcher
}
if (Dep.target) {// depend() {
// let i = this.deps.length
// while (i--) {// this.deps[i].depend()
// }
// }
watcher.depend()}
return watcher.value
}
// watcher.js
depend() {// this.deps 是从 cleanupDeps()中
// this.deps = this.newDeps 来的
// this.newDeps 是通过 addDep()来的
let i = this.deps.length
while (i--) {this.deps[i].depend()}
}
// Dep.js
depend() {if (Dep.target) {Dep.target.addDep(this)
}
}
派发更新流程图剖析
派发更新代码剖析
computed: {myName: function() {// 没有 set()办法,只有 get()办法
return this.firstName + this.lastName;
}
}
当 this.firstName
产生扭转时,会触发 this.firstName.dep.subs.notify()
性能,也就是触发刚刚注册的两个 Watcher
: 渲染 Watcher
和 Computed Watcher
,首先触发的是Computed Watcher
的notify()
办法,由上面的代码能够晓得,只执行this.dirty=true
update () {
// Computed Watcher 的 this.lazy 都为 true
if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
} else {queueWatcher(this)
}
}
而后触发 渲染 Watcher
,触发整个界面进行渲染,从而触发该 computed[key]
的get()
办法执行,也就是 myName
的get()
办法执行,由依赖收集的代码能够晓得,最终执行为
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {if (watcher.dirty) {// 下面剖析触发了 watcher.get()办法
// 失去对应的 watcher.value
watcher.evaluate();}
if (Dep.target) {// depend() {
// let i = this.deps.length
// while (i--) {// this.deps[i].depend()
// }
// }
watcher.depend()}
return watcher.value
}
从下面的剖析能够晓得,computed[key]
的 get()
先收集了一波依赖:
- watcher.evaluate():求值 watcher.value + 更新依赖 + 将波及到的响应式对象关联到
Computed Watcher
- watcher.depend():将波及到的响应式对象关联到以后的
Dep.target
,即渲染 Watcher
而后返回了对应的值watcher.value
computedWatcher 个别无 set 办法,因而触发派发更新就是触发渲染 Watcher/ 其它 Watcher 持有 computed 进行从新渲染,从而触发 computed 的 get 办法,收集最新依赖以及获取最新值
watch 依赖收集和派发更新剖析
watch 流程图跟 computed 流程大同小异,因而 watch 只做源码剖析
测试代码
watch
反对多种模式的监听形式,比方传入一个回调函数,比方传入一个办法名称,比方传入一个Object
,配置参数
// {[key: string]: string | Function | Object | Array }
watch: {a: function (val, oldVal) {},
b: 'someMethod', // 办法名
c: {handler: function (val, oldVal) {}, // 值扭转时的回调办法
deep: true, // 深度遍历
immediate: true // 马上回调一次
},
// 你能够传入回调数组,它们会被逐个调用
e: [
'handle1', // 形式 1
function handle2 (val, oldVal) {}, // 形式 2
{ // 形式 3
handler: function (val, oldVal) {},
deep: true,
immediate: true
},
],
// watch vm.e.f's value: {g: 5}'e.f': function (val, oldVal) {}}
初始化 watch
export function initState(vm: Component) {if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch);
}
}
function initWatch(vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key];
// 解决 watch:{b: [三种模式都容许]}的模式
if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i]);
}
} else {createWatcher(vm, key, handler);
}
}
}
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {if (isPlainObject(handler)) {// 解决 watch:{b: {handler: 处理函数, deep: true, immediate: true}}的模式
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {// 解决 watch: {b: 'someMethod'}的模式
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
从下面的代码能够看出,初始化时,会进行 watch
中各种参数的解决,将 3 种不同类型的 watch
回调模式整顿成为标准的模式,最终调用 Vue.prototype.$watch
进行 new Watcher
的构建
Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {
const vm: Component = this
// cb 是回调办法,如果还是对象,则应用 createWatcher 拆出来外面的对象
if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)
}
options.user = true
// 建设一个 watch 类型的 Watcher
// expOrFn: getter
// cb: 注册的回调
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {// options={immediate:true}的分支逻辑
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()}
return function unwatchFn() {watcher.teardown()
}
}
依赖收集代码剖析
新建 Watcher
的时候,在 constructor()
中会触发
class watcher {constructor() {
// watch 的 key
this.getter = parsePath(expOrFn);
this.value = this.lazy?undefined:this.get();}
const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`)
export function parsePath (path: string): any {if (bailRE.test(path)) {return}
const segments = path.split('.')
return function (obj) {for (let i = 0; i < segments.length; i++) {if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
从下面的代码能够晓得,最终 this.getter
调用的还是传入的 obj[key]
,从上面的get()
办法能够晓得,赋值 this.getter
后,会触发 get()
办法,从而触发 this.getter.call(vm, vm)
,因而最终this.getter
失去的就是vm[key]
get() {pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
if (this.deep) {traverse(value); // 深度遍历数组 / 对象,实现
}
popTarget()
this.cleanupDeps()
return value
}
// traverse.js
export function traverse (val: any) {_traverse(val, seenObjects)
seenObjects.clear()}
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__) {
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)
}
}
下面代码的步骤能够概括为
- pushTarget:修复以后的
Dep.target
为以后的watch 类型的 Watcher
- this.getter:返回以后的
vm[key]
,同时触发vm[key]
的响应式劫持get()
办法,从而触发vm[key]
持有的Dep
对象启动dep.depend()
进行依赖收集 (如上面代码所示),vm[key]
持有的Dep
对象将以后的watch 类型的 Watcher
收集到vm[key]
中,下次vm[key]
发生变化时,会触发watch 类型的 Watcher
进行callback 的回调
- traverse(value):深度遍历,会拜访每一个 Object 的 key,因为每一个 Object 的 key 之前在 initState()的时候曾经应用
Object.defineProperty()
进行 get 办法的劫持,因而触发它们对应的 getter 办法,进行dep.depend()
收集以后的watch 类型的 Watcher
,从而实现扭转 Object 外部深层的某一个 key 的时候会回调watch 类型的 Watcher
。没有加deep=true
的时候,watch 类型的 Watcher
只能监听 Object 的扭转,比方 watch:{curData: function(){}},只有 this.curData=xxx,才会触发 watch,this.curData.children=xxx 是不会触发的 - popTarget:复原
Dep.target
为上一个状态 - cleanupDeps:更新依赖关系
- 返回值 value,依赖收集完结,
watch 类型的 Watcher
初始化完结
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {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
的值产生扭转时,会触发 dep.subs.notify()
办法,从下面的剖析能够晓得,最终会调用 watcher.run()
办法
run() {if (this.active) {const value = this.get()
if (
value !== this.value ||
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)
}
}
}
}
因为 watch 类型的 Watcher
传入了this.user=true
,因而会触发invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
,将新值和旧值一起回调,比方
watch: {myObject: function(value, oldValue) {// 新值和旧值}
}
watchOptions 几种模式分析
deep=true
// watcher.js
get() {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
}
在 get()
办法中进行对象的深度 key 的遍历,触发它们的 getter()
办法,进行依赖的收集,能够实现
watch: {
myObject: {
deep: true,
handler: function(value, oldValue) {// 新值和旧值}
}
}
this.myObject.a = 2;
尽管下面的例子只是监听了 myObject
,然而因为退出deep=true
,因而this.myObject.a
也会触发watcher.run()
,如上面代码所示,因为this.deep=true
,因而会回调cb(value, oldValue)
run() {if (this.active) {const value = this.get()
if (
value !== this.value ||
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)
}
}
}
}
immediate=true
从上面代码能够晓得,当申明 immediate=true
的时候,初始化 Watcher
,会马上调用invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
,即cb
的回调
Vue.prototype.$watch = function (
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) {const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()}
return function unwatchFn() {watcher.teardown()
}
}
watch: {
myObject:
{
immediate: true,
handler: function() {... 初始化马上触发一次}
}
}
sync=true
如果申明了 sync=true
,在dep.sub.notify()
中,会马上执行,如果没有申明 sync=true
,会推入队列中,等到下一个nextTick
周期才会执行
update() {
/* istanbul ignore else */
if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
} else {queueWatcher(this)
}
}
export function queueWatcher(watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {has[id] = true
if (!flushing) {queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {i--}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
参考文章
- Vue.js 技术揭秘