【vue源码】computed和watch源码解析

computed

提问

  1. computed一共有几种创建方式
  2. computed如何实现惰性执行
  3. computed缓存如何实现
  4. 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之前也先想几个

  1. watch有几种创建方式
  2. watch依赖何时收集,如何收集
  3. 回调立即执行如何实现(immediate)
  4. 深度监听如何实现(deep)
  5. 同步情况下多次修改同一个值watch是否会执行多次
  6. 新老值如何抛出

watch一共有几种创建方式

  1. 函数
  2. 对象
  3. 数组
  4. 字符串(函数名)

cn.vuejs.org/v2/api/#wat…

老规矩,分析一下思路
第一步依旧是遍历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回调

image.png

おすすめ

転載: juejin.im/post/7066414752829800456