computed
提问
- computed一共有几种创建方式
- computed如何实现惰性执行
- computed缓存如何实现
- computed依赖如何收集(嵌套依赖又是如何收集)
computed一共有几种创建方式
一共2种创建computed的方式
computed: {
// 函数
fn() {},
// 对象
obj: {
get() {},
set() {}
}
}
复制代码
在分析computed实现原理之前先整理一下思路
第一步肯定是先遍历computed,处理创建computed的差异,获取到函数
获取到函数之后创建Watcher,用来收集依赖。但是computed是懒执行的,注册的时候不会执行回调,而且vue内有好几种Watcher,所以我们需要传递一个参数用来标识
那什么时候执行computed函数呢?
获取的时候(this.computedKey)
那么又是怎么知道代码里调用了这个computedKey?
Object.definedProperty设置get呀
整体大纲大概是这样,那么我们一步步来看如何具体实现
具体实现
// src/core/instance/state.js
function initComputed (vm: Component, computed: Object) {
// 存储创建好的Watcher,拦截内会用到
const watchers = vm._computedWatchers = Object.create(null)
// 遍历computed属性
for (const key in computed) {
const userDef = computed[key]
// 处理差异
const getter = typeof userDef === 'function' ? userDef : userDef.get
// 创建computed Watcher, lazy: true是为了标识该Watcher是由computed创建的
// 赋值给watchers也等于赋值给了vm._computedWatchers,存储创建Watcher
watchers[key] = new Watcher(
vm, // 实例
getter || noop, // 回调
noop,
{ lazy: true }
)
// 判断key是否和methods和data里的的属性重名,创建拦截
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else {
// 警告
}
}
}
复制代码
// src/core/observer/watcher.js
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;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
// computed初始化只执行constructor
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// 初始化lazy
if (options) {
this.lazy = !!options.lazy
}
this.id = ++uid
// 赋值给dirty,判断是不是脏值,用来判断是否需要重新执行函数求值
this.dirty = this.lazy
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.getter = expOrFn
// computed lazy,就不执行函数
this.value = this.lazy
? undefined
: this.get()
}
// computed的时候由下面的evaluate触发
get () {
// 修改Dep.target
pushTarget(this)
let value
const vm = this.vm
// 修改this指向并指向函数求值
value = this.getter.call(vm, vm)
// 出栈
popTarget()
return value
}
// 执行data get时,给watcher添加dep,也给data添加watcher
addDep (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)
}
}
}
// 依赖发生改变时,遍历执行Watcher的update,是computed的时候就把dirty变为true,下次获取computed就需要重新求值
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else {
queueWatcher(this)
}
}
// this.computedKey,劫持的get函数时dirty:true执行(初始化为true)
evaluate () {
this.value = this.get()
// 执行过一次求值后dirty为false
this.dirty = false
}
// 上一级Watcher执行当前computed收集到的依赖(dep),给上一级Watcher dep添加当前依赖
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
复制代码
// src/core/instance/state.js
export function defineComputed (
target,
key,
userDef
) {
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
// 处理差异
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get ? createComputedGetter(key) : noop
sharedPropertyDefinition.set = userDef.set || noop
}
// get和set劫持
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 处理get函数
function createComputedGetter (key) {
return function computedGetter () {
// 获取之前创建的Watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 脏值就去重新求值
if (watcher.dirty) {
watcher.evaluate()
}
// 上一级Watcher收集依赖
if (Dep.target) {
watcher.depend()
}
// 返回值
return watcher.value
}
}
}
复制代码
watch
提问
在开始watch之前也先想几个
- watch有几种创建方式
- watch依赖何时收集,如何收集
- 回调立即执行如何实现(immediate)
- 深度监听如何实现(deep)
- 同步情况下多次修改同一个值watch是否会执行多次
- 新老值如何抛出
watch一共有几种创建方式
- 函数
- 对象
- 数组
- 字符串(函数名)
老规矩,分析一下思路
第一步依旧是遍历watch,然后处理差异
然后要做的就是为每个属性创建Watcher
首先我们要想明白,我们监听的是属性值的变化,也就是监听的值发生set的时候,我们需要执行回调,所以初始化的时候我们需要先读取一个监听的值,也就是走一次get流程,那么怎么读值呢?
直接根据监听的属性求值即可,把属性转成读取值的函数,赋值给Watcher内的getter,执行get时内部会维护Dep.target,为监听的属性添加Watcher
具体实现
// src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
// 遍历watch对象
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)
}
}
}
// 处理差异
function createWatcher (
vm: Component,
expOrFn: string | Function, //$watch创建可能是函数 https://cn.vuejs.org/v2/api/#vm-watch
handler: any,
options?: Object
) {
// 对象
if (isPlainObject(handler)) {
// 参数直接取对象,可能存在deep等
options = handler
// 获取回调
handler = handler.handler
}
// 字符串取methods方法,此时methods已经初始化完成,可以用vm.获取
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
// 在index.js的时候就注册完成
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 兼容vm.$watch中对象的创建方式
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// watch唯一标识
options.user = true
// 创建user Watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果存在immediate,就立即执行一次回调
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
复制代码
// src/core/observer/watcher.js
function parsePath (path: string): any {
// 字符串转数组
const segments = path.split('.')
return function (obj) {
// 遍历数组取值
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
//处理user Watcher 的deep参数,不断的去取data的值(data get)
const seenObjects = new Set()
function traverse (val: any) {
_traverse(val, seenObjects)
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
// 不是数组或者对象直接结束
if ((!isA && !isObject(val))) {
return
}
// 是否是响应式
if (val.__ob__) {
// 获取式id,已经添加过这个id就不重复添加
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)
}
}
let uid = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
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;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
}
this.cb = cb
this.id = ++uid // uid for batching
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
// 将key转为获取值的函数
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.lazy
? undefined
: this.get()
}
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 {
// 处理deep
if (this.deep) {
traverse(value)
}
popTarget()
}
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)
}
}
}
// 依赖性值变化后Watcher进入异步队列,所以同步情况下多次修改watch回调只会执行一次
update () {
if (this.lazy) {
this.dirty = true
} else {
// 更新队列
queueWatcher(this)
}
}
// 异步更新完成后执行run
run () {
// 新的值
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
) {
// 老的值
const oldValue = this.value
// 更新新值
this.value = value
if (this.user) {
// 回调
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
复制代码
更新队列
// src/core/observer/scheduler.js
import type Watcher from './watcher'
import config from '../config'
import { callHook, activateChildComponent } from '../instance/lifecycle'
import {
warn,
nextTick,
devtools,
inBrowser,
isIE
} from '../util/index'
// 队列,保存Watcher
const queue: Array<Watcher> = []
// 哈希,判断当前Watcher.id,已经添加过就过滤
let has: { [key: number]: ?true } = {}
// 是否开启异步更新
let waiting = false
// 队列是否执行中
let flushing = false
// 队列执行到哪个位置
let index = 0
/**
* Reset the scheduler's state.
*/
function resetSchedulerState () {
index = queue.length = activatedChildren.length = 0
has = {}
waiting = flushing = false
}
let getNow: () => number = Date.now
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// 1. 组件从父组件更新到子组件。(因为父母总是在child之前创建)
// 2. 组件的用户监视程序在呈现监视程序之前运行(因为用户观察者是在渲染观察者之前创建的)
// 3. 如果一个组件在父组件的监视程序运行期间被销毁,它的观察者可以跳过。(这个理由完成没理解)
queue.sort((a, b) => a.id - b.id)
// 遍历Watcher,执行run,index++维护当前执行到哪个回调(queueWatcher会用到)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
id = watcher.id
has[id] = null
watcher.run()
}
// 重置状态
resetSchedulerState()
}
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 过来已经push过的Watcher
if (has[id] == null) {
has[id] = true
// 未在执行中直接push到队列里
if (!flushing) {
queue.push(watcher)
} else {
let i = queue.length - 1
// i > index表示队列还未执行完,找到对应的位置
// i <= index表示错过了执行时机,直接插入下一个位置,然后立即执行,
// 看最下方例子和图解
while (i > index && queue[i].id > watcher.id) {
i--
}
// 将任务插到对应的位置
queue.splice(i + 1, 0, watcher)
}
// 是否开始异步更新
if (!waiting) {
waiting = true
// 更新队列
nextTick(flushSchedulerQueue)
}
}
}
复制代码
// src/core/util/next-tick.js
// 回调函数数组
const callbacks = []
// 是否执行中
let pending = false
// 微任务或者宏任务回调时执行
function flushCallbacks () {
// 开始执行的时候将pendding重置,使新的任务进入下一个循环
pending = false
// 拷贝和重置callbacks
const copies = callbacks.slice(0)
callbacks.length = 0
// 执行
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// 优雅降级
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 存储回调
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
// 创建当前微任务
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
复制代码
<template>
<div @click="changeA">change</div>
</template>
<script>
{
data(){
return {a: '', b: '', c: ''}
},
watch: {
c() {console.log('c')},
b() {console.log('b');this.c = 222;},
a() {console.log('a')}
},
methods: {
changeA() {
this.a = 111
this.b = 222
},
}
}
</script>
复制代码
- watch中注册顺序为c,b,a,所以假定c.id = 1,b.id = 2,a.id = 3(因为watcher id是自增的)
- 执行changeA,a和b进入queueWatcher,queue = [a watch,b watch]
- 进入nextTick执行flushSchedulerQueue
- queue.sort,queue = [b watch, a watch]
- b执行回调,c进入queueWatcher,此时flushing = true,寻找插入位置
- index = 0,i = 1,queue[i].id = 3,watcher.id = 1(也就是c的id),此时函数还未执行结束
- 插入结果为queue = [b watch, c watch, a watch],b执行结束
- 执行c回调,然后执行a回调