3

[vue源码02] computed 响应式 - 初始化,访问,更新过程

 2 years ago
source link: https://segmentfault.com/a/1190000040554046
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[vue源码02] computed 响应式 - 初始化,访问,更新过程

发布于 今天 14:11

[[深入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对象中的函数
          • 是一个空函数
        • 在 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 如果在浏览器环境就是 true
    
    if (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...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK