导航

[[深刻01] 执行上下文](https://juejin.im/post/684490...)
[[深刻02] 原型链](https://juejin.im/post/684490...)
[[深刻03] 继承](https://juejin.im/post/684490...)
[[深刻04] 事件循环](https://juejin.im/post/684490...)
[[深刻05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...)
[[深刻06] 隐式转换 和 运算符](https://juejin.im/post/684490...)
[[深刻07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...)
[[深刻08] 前端平安](https://juejin.im/post/684490...)
[[深刻09] 深浅拷贝](https://juejin.im/post/684490...)
[[深刻10] Debounce Throttle](https://juejin.im/post/684490...)
[[深刻11] 前端路由](https://juejin.im/post/684490...)
[[深刻12] 前端模块化](https://juejin.im/post/684490...)
[[深刻13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490...)
[[深刻14] canvas](https://juejin.im/post/684490...)
[[深刻15] webSocket](https://juejin.im/post/684490...)
[[深刻16] webpack](https://juejin.im/post/684490...)
[[深刻17] http 和 https](https://juejin.im/post/684490...)
[[深刻18] CSS-interview](https://juejin.im/post/684490...)
[[深刻19] 手写Promise](https://juejin.im/post/684490...)
[[深刻20] 手写函数](https://juejin.im/post/684490...)

[[react] Hooks](https://juejin.im/post/684490...)

[[部署01] Nginx](https://juejin.im/post/684490...)
[[部署02] Docker 部署vue我的项目](https://juejin.im/post/684490...)
[[部署03] gitlab-CI](https://juejin.im/post/684490...)

[[源码-webpack01-前置常识] AST形象语法树](https://juejin.im/post/684490...)
[[源码-webpack02-前置常识] Tapable](https://juejin.im/post/684490...)
[[源码-webpack03] 手写webpack - compiler简略编译流程](https://juejin.im/post/684490...)
[[源码] Redux React-Redux01](https://juejin.im/post/684490...)
[[源码] axios ](https://juejin.im/post/684490...)
[[源码] vuex ](https://juejin.im/post/684490...)
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...)
[[源码-vue02] computed 响应式 - 初始化,拜访,更新过程 ](https://juejin.im/post/684490...)
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...)
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...)
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...)

[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...)

前置常识

学习指标

  • computed计算属性只有在computed被拜访时,才会去计算

    • 因为在new Watcher是computed watcher时,即lazy=true时,在构造函数中没有立刻执行get()办法,而是在计算属性被拜访时触发computed的响应式get后,执行的get办法中回去调用computed getter函数
  • computed计算属性具备缓存性能

    • 通过dirty=true时,才会去执行watcher.evaluate()办法,才会执行computed wawtcher中的get()即computd 定义的函数,从新计算,计算完后,将this.dirty=false
    • 下次再拜访时,会先判断dirty,是false就间接返回缓存的值
  • computed的依赖必须是响应式数据,不然即便依赖变动不会触发computed从新计算
  • 即便computed的依赖变动了,然而computed计算的值没有变动的话,不会从新渲染
  • computed watcher 和 render watcher 和 dep 和 dep.subs 和 watcher.deps 之间简单的关系

    • computed拜访过程

      • 拜访computed,触发computed响应式的get函数,get函数中判断如果dirty=true,那么执行computed watchet的evalute办法,即watcher中的get()办法,而后把 Dep.target = computed watcher,执行computed getter函数就是用户指定的computed函数
      • 执行computed getter函数的时,因为有依赖的响应式的data,所以又会触发data的get函数,执行dep.depend(),就是把computed watcher中的 newDeps中增加computed依赖项的 dep,同时向computed依赖项的dep的subs中增加computed watcher,而后又把Dep.target = targetStack数组中的前一个watcher,而后返回计算的后果,并且把 dirty=false
      • 而后判断 Dep.taret 存在,就执行 computed watcher的depend办法,循环遍历computed watcher中的deps,取出dep,执行dep.depend
      • 因为此时的 Dep.target = render watcher,所以dep.depend会向render watcher的 newDeps中增加data的dep,向data的dep中的subs中增加render watcher,那么此时的computed计算属性的依赖项的dep中的subs就是[computed watcher, render watcher]这样就保障了渲染的时候,computed先于render先执行,保障computed有值
    • comuted更新过程

      • 下面的例子中,change的过程,是扭转了computed的依赖,之后的一系列流程
      • 触发computed的响应式依赖的dep.notify()办法,循环遍历该依赖的dep.subs数组中的每一个watcher.update()办法,在下面的例子中subs=[computed watcher, render watcher]所以先执行computed watcher
      • compued watcher

        • 在update办法中,会判断lazy=true?,因为computed watcher的lazy=true,所以执行 dirty=true
      • render watcher

        • 在update办法中, lazy不为true, 会执行 queueWatcher(this),就是调用渲染watcher从新求值和渲染

一些单词

internal:外部的Scheduler:调度器queue:队列( flushSchedulerQueue : 刷新调度器队列 )

computed源码

(1) computed的初始化

  • Vue.prototype._init => initState(vm) => initComputed(vm, opts.computed)

(1-1) initComputed(vm, opts.computed)

  • 次要做了以下事件

    • <font color=blue size=5>(1-1-1) new Watcher(vm, getter, noop, computedWatcherOptions) </font>

      • new了一个 computed watcher
      • 参数

        • computedWatcherOptions

          • 如何晓得是一个computed watcher => 次要通过第4个参数computedWatcherOptions => { lazy: true },即comoputed watcher的 lazy属性是true
        • getter

          • 就是用户本人定义的computed对象中的函数
        • noop

          • 是一个空函数
      • 留神点:

        • 在 new Watcher 计算属性watcher的构造函数中

          • this.value=this.lazy ? undefined : this.get()
          • 因为计算属性watcher的lazy=true,所以不会立刻去执行 get() 办法
          • <font color=blue>那什么时候去执行呢get()呢?</font>

            • <font color=blue>执行机会就是在template中拜访了computed</font>,因为computed又定义了响应式,拜访了computed属性就会执行computed的get办法,在get办法中会执行watcher.evaluate()办法,在外面就是去执行 get(),从而去计算computed的后果
    • <font color=blue size=5>(1-1-2) 把computd定义成响应式</font>

      • defineComputed(vm, key, userDef) => Object.defineProperty(target, key, sharedPropertyDefinition) => sharedPropertyDefinition.get => createComputedGetter(key) => computedGetter
      • 也就是说拜访computed中的 this.xxxx 就会去执行 computedGetter 函数
    • <font color=blue size=5>(1-1-3) computedGetter - 这个办法很重要 !!!!!!!!!!!!!!!!!!!!!</font>

      • watcher.evaluate() 执行计算属性watcher中的evaluate办法

        • 当dirty=true,并且watcher存在时,就会执行 computed watcher 的 evalute 办法
        • <font color=blue>evalute</font> 办法会执行 <font color=blue>get()</font> 办法,并将 <font color=blue>this.dirty</font> 改为 <font color=blue>false</font>

          • <font color=blue>get()</font>

            • <font color=blue>pushTarget(this)</font>

              • <font color=blue>向 targetStack 数组中 push 一个computed watcher</font>
              • <font color=blue>将 Dep.target 指定为 computed watcher</font>
            • 执行用户在computed对象中定义的办法,即getter办法newName

              • 比方 computed: {newName() {return this.name + 'new' }}
              • 留神:

                • 在这过程中又会触发 data对象的影响式,即this.name触发响应式data中的get函数,因为拜访了data的name属性
                • data,computed都有本人的响应式
                • 这里data的响应式又会收集计算属性的watcher,这个放在前面的计算属性的拜访流程中去梳理

                  • 次要就是

                    • 向 computed watcher 的newDeps中增加render watcher的dep
                    • 向 render watcher的依赖的属性的dep的 subs 中增加 computed watcher
                    • 详见下文
      • watcher.depend() 执行计算属性watcher的depend办法

        • 放在上面拜访流程一起剖析
  • 源码
  • Vue.prototype._init => initState(vm) => initComputed(vm, opts.computed)
  • initComputed - scr/core/instance/state.js

    initComputed - scr/core/instance/state.js---function initComputed (vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)// 申明 watchers 和 _computedWatchers 为一个空对象const isSSR = isServerRendering()// 是否是ssr环境for (const key in computed) {  const userDef = computed[key]  // userDef 是 computed 的 getter 函数  const getter = typeof userDef === 'function' ? userDef : userDef.get  // getter    // computed getter 能够是函数 或者 具备get办法的对象    // 这里个别都是函数,并且该函数须要return一个值  if (process.env.NODE_ENV !== 'production' && getter == null) {    warn(      `Getter is missing for computed property "${key}".`,      vm    )  }  // 如果不是函数或者对象就报正告  if (!isSSR) {    // create internal watcher for the computed property.    // 非ssr环境,即浏览器环境,就新建 computed watcher    // computed watcher      // computedWatcherOptions = { lazy: true }      // getter = 用户自定义的computed对象中的函数    watchers[key] = new Watcher(      vm,      getter || noop, // 用户自定义的computed对象中的函数,即 computed getter      noop,      computedWatcherOptions, //  { lazy: true }    )  }  // component-defined computed properties are already defined on the  // component prototype. We only need to define computed properties defined  // at instantiation here.  // 在 vue.extends 和 new Vue() 过程中都定义了响应式的computed  if (!(key in vm)) {    defineComputed(vm, key, userDef)    // defineComputed 将 computed 变成响应式  } else if (process.env.NODE_ENV !== 'production') {    // 解决重名的状况,在props,data,computed不能用重名的key    if (key in vm.$data) {      warn(`The computed property "${key}" is already defined in data.`, vm)    } else if (vm.$options.props && key in vm.$options.props) {      warn(`The computed property "${key}" is already defined as a prop.`, vm)    }  }}}
  • defineComputed - scr/core/instance/state.js

    defineComputed - scr/core/instance/state.js---export function defineComputed (target: any,key: string,userDef: Object | Function) {const shouldCache = !isServerRendering()// shouldCache 如果在浏览器环境就是 trueif (typeof userDef === 'function') {  sharedPropertyDefinition.get = shouldCache    ? createComputedGetter(key) // 定义computed被拜访时,触发的get    : createGetterInvoker(userDef)  sharedPropertyDefinition.set = noop} else {  // userDef 不是 function,咱们间接疏忽  sharedPropertyDefinition.get = userDef.get    ? shouldCache && userDef.cache !== false      ? createComputedGetter(key)      : createGetterInvoker(userDef.get)    : noop  sharedPropertyDefinition.set = userDef.set || noop}if (process.env.NODE_ENV !== 'production' &&    sharedPropertyDefinition.set === noop) {  sharedPropertyDefinition.set = function () {    warn(      `Computed property "${key}" was assigned to but it has no setter.`,      this    )  }}Object.defineProperty(target, key, sharedPropertyDefinition)// 定义响应式 computed  // 1. 当通过 this.xxxx 拜访computed,就会触发sharedPropertyDefinition对象中的get  // 2. get 其实就是上面createComputedGetter返回的 computedGetter函数}
  • createComputedGetter - scr/core/instance/state.js

    createComputedGetter - scr/core/instance/state.js---function createComputedGetter (key) {return function computedGetter () {  const watcher = this._computedWatchers && this._computedWatchers[key]  // 取出每一个 computed watcher  if (watcher) {    if (watcher.dirty) {      // watcher.dirty        // 1. 默认初始化时,comoputed watcher 的 dirty=true        // 2. 当 dirty=true 就会执行 watcher.evaluate()        // 3. watcher.evaluate() 执行完后, dirty=false        // 总结:  dirty=true => watcher.evaluate() => dirty=false      watcher.evaluate()      // watcher.evaluate()        // 1. 会去执行 computed watcher 中的 get()          // pushTarget(this)            // 1. 将 computed watcher 增加到  targetStack 数组中            // 2. 将 Dep.target = computed watcher          // 执行 this.getter.call(vm, vm) 即用户自定义的 computed对象中的办法            // 1. 列如: computed: {newName() {return this.name + 'new' }}            // 2. 因为:computed的newName办法中,依赖了data中的this.name,即拜访到了this.name就会触发data响应式的get办法            // 3. 所以:ata响应式的get办法执行过程如下              // 获取到了this.name的值              // 此时,Dep.target 是computed watcher              // 而后执行this.name对象的dep类的depend办法进行依赖收集                // 向 computed watcher 的newDeps中增加render watcher的dep                // 向 render watcher 的 subs 中增加 computed watcher          //  popTarget()            // 1. targetStack.pop()将 computed watcher从targetStack数组中删除            // 2. 并且将 Dep.target 指定为数组中的前一个 watcher,没有了就是undefined        // 2. 将 dirty=false      // evaluate () {      //   this.value = this.get()      //   this.dirty = false      // }      // get () {      //   pushTarget(this)      //   let value      //   const vm = this.vm      //   try {      //     value = this.getter.call(vm, vm)      //   } catch (e) {      //     if (this.user) {      //       handleError(e, vm, `getter for watcher "${this.expression}"`)      //     } else {      //       throw e      //     }      //   } finally {      //     // "touch" every property so they are all tracked as      //     // dependencies for deep watching      //     if (this.deep) {      //       traverse(value)      //     }      //     popTarget()      //     this.cleanupDeps()      //   }      //   return value      // }    }    if (Dep.target) {      watcher.depend()      // depend () {      //   let i = this.deps.length      //   while (i--) {      //     this.deps[i].depend()      //   }      // }          }    return watcher.value  }}}
  • Watcher - scr/core/observer/watcher.js

    Watcher - scr/core/observer/watcher.js---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  if (isRenderWatcher) {    vm._watcher = this  }  vm._watchers.push(this)  // options  if (options) {    this.deep = !!options.deep    this.user = !!options.user    this.lazy = !!options.lazy    this.sync = !!options.sync    this.before = options.before  } else {    this.deep = this.user = this.lazy = this.sync = false  }  this.cb = cb  this.id = ++uid // uid for batching  this.active = true  this.dirty = this.lazy // for lazy watchers  this.deps = []  this.newDeps = []  this.depIds = new Set()  this.newDepIds = new Set()  this.expression = process.env.NODE_ENV !== 'production'    ? expOrFn.toString()    : ''  // parse expression for getter  if (typeof expOrFn === 'function') {    this.getter = expOrFn  } else {    this.getter = parsePath(expOrFn)    if (!this.getter) {      this.getter = noop      process.env.NODE_ENV !== 'production' && warn(        `Failed watching path: "${expOrFn}" ` +        'Watcher only accepts simple dot-delimited paths. ' +        'For full control, use a function instead.',        vm      )    }  }  this.value = this.lazy    ? undefined    : this.get()}/** * Evaluate the getter, and re-collect dependencies. */get () {  pushTarget(this)  let value  const vm = this.vm  try {    value = this.getter.call(vm, vm)  } catch (e) {    if (this.user) {      handleError(e, vm, `getter for watcher "${this.expression}"`)    } else {      throw e    }  } finally {    // "touch" every property so they are all tracked as    // dependencies for deep watching    if (this.deep) {      traverse(value)    }    popTarget()    this.cleanupDeps()  }  return value}/** * Add a dependency to this directive. */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)    }  }}/** * Clean up for dependency collection. */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}/** * Subscriber interface. * Will be called when a dependency changes. */update () {  /* istanbul ignore else */  if (this.lazy) {    this.dirty = true  } else if (this.sync) {    this.run()  } else {    queueWatcher(this)  }}/** * Scheduler job interface. * Will be called by the scheduler. */run () {  if (this.active) {    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    ) {      // set new value      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)      }    }  }}/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */evaluate () {  this.value = this.get()  this.dirty = false}/** * Depend on all deps collected by this watcher. */depend () {  let i = this.deps.length  while (i--) {    this.deps[i].depend()  }}/** * Remove self from all dependencies' subscriber list. */teardown () {  if (this.active) {    // remove self from vm's watcher list    // this is a somewhat expensive operation so we skip it    // if the vm is being destroyed.    if (!this.vm._isBeingDestroyed) {      remove(this.vm._watchers, this)    }    let i = this.deps.length    while (i--) {      this.deps[i].removeSub(this)    }    this.active = false  }}}

(2) computed的拜访过程

  • 案例

    <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./vue.js"></script></head><body><div id="root">  <div>{{newName}}</div>  <button @click="change">change</button></div><script>  new Vue({    el: '#root',    data: {      name: 'ssssssssssssss'    },    computed: {      newName() {        return this.name + 'new'      }    },    methods: {      change() {        this.name = '222222222222222'      }    }  })</script></body></html>
  • vm._update(vm._render(), hydrating) 过程中, 当在template模板中应用了 computed 对象中的key的时候,就会触发computed响应式对象的 get 办法
  • computed的响应式get办法就是computedGetter办法,在该办法中,会判断wathcer和watcher.dirty是否存在,存在证实就是computed watcher,就会执行 computed wathcer 的 watcher.evaluate() 办法
  • watcher.evaluate()办法就会执行computed watcher中的get()办法,并将 this.dirty 改为 false
  • watcher.evaluate()

    • get()办法

      • 调用 <font color=red>pushTarget(this)</font>

        • <font color=red>向 targetStack 数组中 push 一个computed watcher</font>
        • <font color=red>将 Dep.target 指定为 computed watcher</font>
      • 而后调用 computed getter 办法,即用户本人定义的computed对象中的办法

        • 比方: computed: {newName() {return this.name + 'new' }}
        • 因为:computed的newName办法中,依赖了data中的this.name,即拜访到了this.name就会触发data响应式的get办法
        • 所以:<font color=DarkOrChid>data响应式的get办法执行过程如下:</font>

          • 获取到了this.name的值
          • 此时,Dep.target 是 computed watcher

            • 而后执行this.name对应的dep类的depend办法进行依赖收集

              • <font color=DarkOrChid>向 computed watcher 的newDeps中增加render watcher的对应的data属性的 dep</font>
              • <font color=DarkOrChid>向 render watcher对应的data属性的dep实例的 subs 中增加 computed watcher</font>
              • <font color=DarkOrChid>等于说data的 this.name 和 computed Watcher 具备同一个 dep 实例</font>
            • 执行完下面的步骤后 dep.subs 和 computed watcher.newDeps 的状态是

              • this.name 对应的 dep 实例中的 subs = [computedWatcher]
              • computed watcher 中的 newDeps = [下面的this.name对应的dep]
          • 返回name的值
      • 调用 <font color=red>popTarget()</font>

        • <font color=red>targetStack.pop()将 computed watcher从targetStack数组中删除</font>
        • <font color=red>并且将 Dep.target 指定为数组中的前一个 watcher</font>

          • <font color=red>Dep.target = render watcher</font>
          • 在下面的例子中 targetStack 数组中在执行computed 的getter办法时一共有两个成员
          • 第一个:render watcher
          • 第二个:computed watcher
          • pop后还剩一个render watcher
      • 最初返回computed计算失去的后果值
  • watcher.depend()

    • 当 Dep.target 存在时,才会执行 watcher.depend()
    • 下面执行完,<font color=DarkOrChid>Dep.target = render watcher</font>
    • watcher.depend()

      • 而后执行 <font color=DarkOrChid>compute watcher 中的 watcher.depend() 办法</font>
      • 而后,<font color=DarkOrChid>从后往前,顺次取出 computed watcher 中 deps 数组中的 dep,执行 dep.depend()</font>

        • 留神:下面computed watcher 中的 deps 中的 dep,就是this.name对象的dep,外面的subs数组中只有一个computedWatcher
        • 比照:在data对象的属性被拜访的时候,也会执行data对应的属性的 dep.depend()
      • <font color=DarkOrChid>Dep.target.addDep(this)</font>

        • <font color=DarkOrChid>此时的 Dep.targt 是 render watcher</font>,因为 popTarget() 操作pop出computed watcher后就只剩render watcher了
        • addDep 前

          • render watcher中的 deps 是空数组
          • render watcher中的 newDeps 是空数组
        • addDep 次要做两件事件

          • <font color=DarkOrChid>向 render watcher 的 newDeps 中增加 该render watcher 对应的datad属性的 dep</font>
          • <font color=DarkOrChid>向 render watcher 对应的data属性对应的dep类的 subs 中增加 render watcher</font>
          • 增加后,data的dep.subs = [computed watcher, render watcher]

            • <font color=red>这样当this.name属性批改后触发对应的set函数,就会触发dep.notify,而后循环sub中的watcher,执行watcher.update()办法</font>
            • <font color=red>[computed watcher, render watcher]这样的程序保障了在render的时候,computed肯定有值</font>

(3) computed的更新过程

  • 下面的例子中,change的过程,是扭转了computed的依赖,之后的一系列流程
  • 触发computed的响应式依赖的dep.notify()办法,循环遍历该依赖的dep.subs数组中的每一个watcher.update()办法,在下面的例子中subs=[computed watcher, render watcher]所以先执行computed watcher
  • compued watcher

    • 在update办法中,会判断lazy=true?,因为computed watcher的lazy=true,所以执行 dirty=true
  • render watcher

    • 在update办法中, lazy不为true, 会执行 queueWatcher(this),就是调用渲染watcher从新求值和渲染

材料

理论案例-避坑 https://juejin.im/post/684490...
具体 https://juejin.im/post/684490...
源码版 https://juejin.im/post/684490...
computed watcher https://juejin.im/post/684490...