本文基于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 = nullconst 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.setVue.set(vm.items, indexOfItem, newValue)// Array.prototype.splicevm.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.jsconst arrayProto = Array.prototypeexport 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.jsexport 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.jsfunction 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.jsget() { // 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.jsdepend () { if (Dep.target) { Dep.target.addDep(this) }}// watcher.jsaddDep(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.jsdepend() { // this.deps是从cleanupDeps()中 // this.deps = this.newDeps来的 // this.newDeps是通过addDep()来的 let i = this.deps.length while (i--) { this.deps[i].depend() }}// Dep.jsdepend() { 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.jsexport 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.jsget() { 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 技术揭秘