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

2021-09-25  本文已影响0人  woow_wu7
image

导航

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

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick

前置知识

学习目标

一些单词

internal:内部的

Scheduler:调度器
queue:队列
( flushSchedulerQueue : 刷新调度器队列 )

computed源码

(1) computed的初始化

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

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
---

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
---

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
---

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>

(3) computed的更新过程

资料

实际案例-避坑 https://juejin.im/post/6844903705540558855
详细 https://juejin.im/post/6844903606676799501
源码版 https://juejin.im/post/6844903881583886349
computed watcher https://juejin.im/post/6844903648984596494

上一篇下一篇

猜你喜欢

热点阅读