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;
<span class="token function">constructor</span> <span class="token punctuation">(</span>
vm<span class="token operator">:</span> Component<span class="token punctuation">,</span>
expOrFn<span class="token operator">:</span> string <span class="token operator">|</span> Function<span class="token punctuation">,</span> <span class="token comment">// 表达式本身 [ getter | noop ]</span>
cb<span class="token operator">:</span> Function<span class="token punctuation">,</span> <span class="token comment">// [ noop ]</span>
options<span class="token operator">?</span><span class="token operator">:</span> <span class="token operator">?</span>Object<span class="token punctuation">,</span> <span class="token comment">// { lazy: true } // 如果设置为 true 则在第一次 get 的时候才计算值,初始化的时候并不计算。init 时为 true</span>
isRenderWatcher<span class="token operator">?</span><span class="token operator">:</span> boolean
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>vm <span class="token operator">=</span> vm
<span class="token keyword">if</span> <span class="token punctuation">(</span>isRenderWatcher<span class="token punctuation">)</span> <span class="token punctuation">{</span>
vm<span class="token punctuation">.</span>_watcher <span class="token operator">=</span> <span class="token keyword">this</span>
<span class="token punctuation">}</span>
vm<span class="token punctuation">.</span>_watchers<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token comment">// options</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>deep <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>deep
<span class="token keyword">this</span><span class="token punctuation">.</span>user <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>user
<span class="token keyword">this</span><span class="token punctuation">.</span>lazy <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>lazy
<span class="token keyword">this</span><span class="token punctuation">.</span>sync <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>sync
<span class="token comment">/*
before作用是定义 beforeUpdate 钩子:
「
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
」
*/</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>before <span class="token operator">=</span> options<span class="token punctuation">.</span>before
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>deep <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>user <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lazy <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sync <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>cb <span class="token operator">=</span> cb
<span class="token keyword">this</span><span class="token punctuation">.</span>id <span class="token operator">=</span> <span class="token operator">++</span>uid <span class="token comment">// uid for batching</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>active <span class="token operator">=</span> <span class="token boolean">true</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>dirty <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lazy <span class="token comment">// for lazy watchers</span>
<span class="token comment">// 两个数组,</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>deps <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>newDeps <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token comment">// 两个id 为 set 实例,在 add 时候,防止重复添加相同 id</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>depIds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>expression <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span>
<span class="token operator">?</span> expOrFn<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">:</span> <span class="token string">''</span>
<span class="token comment">// parse expression for getter</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> expOrFn <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>getter <span class="token operator">=</span> expOrFn
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>getter <span class="token operator">=</span> <span class="token function">parsePath</span><span class="token punctuation">(</span>expOrFn<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>getter<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>getter <span class="token operator">=</span> noop
process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span> <span class="token operator">&amp;&amp;</span> <span class="token function">warn</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Failed watching path: "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>expOrFn<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" </span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span>
<span class="token string">'Watcher only accepts simple dot-delimited paths. '</span> <span class="token operator">+</span>
<span class="token string">'For full control, use a function instead.'</span><span class="token punctuation">,</span>
vm
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lazy
<span class="token operator">?</span> <span class="token keyword">undefined</span>
<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Evaluate the getter, and re-collect dependencies.
*/</span>
<span class="token comment">// 执行 getter, 并重新收集依赖关系</span>
<span class="token function">get</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">/* 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()
}
」
*/</span>
<span class="token comment">// 这行代码作用:在进行 get 取值时,使得 Dep.target 为 watcher 实例自身</span>
<span class="token function">pushTarget</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token keyword">let</span> value
<span class="token keyword">const</span> vm <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>vm
<span class="token keyword">try</span> <span class="token punctuation">{</span>
value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getter</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> vm<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">handleError</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> vm<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">getter for watcher "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expression<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">throw</span> e
<span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
<span class="token comment">// "touch" every property so they are all tracked as</span>
<span class="token comment">// dependencies for deep watching</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>deep<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">traverse</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token function">popTarget</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cleanupDeps</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> value
<span class="token punctuation">}</span>
<span class="token comment">/**
* Add a dependency to this directive.
*/</span>
<span class="token function">addDep</span> <span class="token punctuation">(</span><span class="token parameter">dep<span class="token operator">:</span> Dep</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> id <span class="token operator">=</span> dep<span class="token punctuation">.</span>id
<span class="token comment">// if (!this.newDepIds.has(id)) 判断,为了防止如 computed 里</span>
<span class="token comment">// `return this.a + this.a` 这种相同值的计算情况</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>newDeps<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>dep<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>depIds<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
dep<span class="token punctuation">.</span><span class="token function">addSub</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Clean up for dependency collection.
*/</span>
<span class="token function">cleanupDeps</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">.</span>length
<span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> dep <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">[</span>i<span class="token punctuation">]</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>dep<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
dep<span class="token punctuation">.</span><span class="token function">removeSub</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">let</span> tmp <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>depIds
<span class="token keyword">this</span><span class="token punctuation">.</span>depIds <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds
<span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds <span class="token operator">=</span> tmp
<span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
tmp <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps
<span class="token keyword">this</span><span class="token punctuation">.</span>deps <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>newDeps
<span class="token keyword">this</span><span class="token punctuation">.</span>newDeps <span class="token operator">=</span> tmp
<span class="token keyword">this</span><span class="token punctuation">.</span>newDeps<span class="token punctuation">.</span>length <span class="token operator">=</span> <span class="token number">0</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Subscriber interface.
* Will be called when a dependency changes.
*/</span>
<span class="token function">update</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">/* istanbul ignore else */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>lazy<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>dirty <span class="token operator">=</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sync<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">queueWatcher</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Scheduler job interface.
* Will be called by the scheduler.
*/</span>
<span class="token function">run</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>active<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>
value <span class="token operator">!==</span> <span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">||</span>
<span class="token comment">// Deep watchers and watchers on Object/Arrays should fire even</span>
<span class="token comment">// when the value is the same, because the value may</span>
<span class="token comment">// have mutated.</span>
<span class="token function">isObject</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">||</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>deep
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// set new value</span>
<span class="token keyword">const</span> oldValue <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>value
<span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> value
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cb</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">,</span> value<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">handleError</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">callback for watcher "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expression<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cb</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">,</span> value<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/</span>
<span class="token function">evaluate</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>dirty <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Depend on all deps collected by this watcher.
*/</span>
<span class="token function">depend</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">.</span>length
<span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">depend</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Remove self from all dependencies' subscriber list.
*/</span>
<span class="token function">teardown</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>active<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// remove self from vm's watcher list</span>
<span class="token comment">// this is a somewhat expensive operation so we skip it</span>
<span class="token comment">// if the vm is being destroyed.</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">.</span>_isBeingDestroyed<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">.</span>_watchers<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">.</span>length
<span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">removeSub</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>active <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
}
上面说到在 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(
</span><span class="token string">Getter is missing for computed property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">".</span><span class="token template-punctuation string">
,
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(</span><span class="token string">The computed property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" is already defined in data.</span><span class="token template-punctuation string">
, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(</span><span class="token string">The computed property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" is already defined as a prop.</span><span class="token template-punctuation string">
, 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
})
})