vue2.7.16源码 - Watcher

源码Watcher类型

Render Watcher

src/platforms/web/runtime/index.ts - 通过$mount绑定,后续还会在complier增强

  • 进行$mount的挂载,调用mountComponent函数,进行元素获取
typescript 复制代码
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

src/core/instance/lifecycle.ts - mountComponent方法

  • 触发生命周期 beforeMount
  • 绑定更新函数,在更新函数执行render方法,这个render方法使用的是compiler绑定的
  • 处理存储的Watcher
  • 触发mounted钩子
javascript 复制代码
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  vm.$el = el
 
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }
 // 创建Watcher  传入当前组件实例 和 更新方法以及配置项
  new Watcher(
    vm,   // 实例
    updateComponent, // 更新回调
    noop,   //  render Watcher用不到,是回调函数
    watcherOptions,  // 配置项
    true /* isRenderWatcher */
  )
  hydrating = false  // 重置

  // 获取预先存储的 Watcher 列表
  // 处理父组件传递的动态 props 和插槽内容
  const preWatchers = vm._preWatchers
  if (preWatchers) {
    for (let i = 0; i < preWatchers.length; i++) {
      preWatchers[i].run()
    }
  }

  // 触发mounted
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

src/platforms/web/runtime-with-compiler.ts - compiler重写$mount

  • 在compiler层处理render函数和目标节点,然后将这些信息传递给原始的mounted

  • hydrating 表示是否要复用dom节点,在patch函数中使用

    typescript 复制代码
    const idToTemplate = cached(id => {
      const el = query(id)
      return el && el.innerHTML
    })
    
    function getOuterHTML(el: Element): string {
      if (el.outerHTML) {
        return el.outerHTML
      } else {
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
      }
    }
    
    const mount = Vue.prototype.$mount
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && query(el)
    
      // dom目标不能是body 和 html
      if (el === document.body || el === document.documentElement) {
        __DEV__ &&
          warn(
            `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
          )
        return this
      }
    
      const options = this.$options
      
      // 第一次构建render
      if (!options.render) {
      // 获获取模板信息
        let template = options.template
        if (template) {
          if (typeof template === 'string') {
            if (template.charAt(0) === '#') {
            // 获取目标的innerHTML
              template = idToTemplate(template)
              /* istanbul ignore if */
              if (__DEV__ && !template) {
                warn(
                  `Template element not found or is empty: ${options.template}`,
                  this
                )
              }
            }
          } else if (template.nodeType) {
          // 如果是html节点 获取innerHTML
            template = template.innerHTML
          } else {
            if (__DEV__) {
              warn('invalid template option:' + template, this)
            }
            return this
          }
        } else if (el) {
          // 创建一个目标节点返回其html内容
          template = getOuterHTML(el)
        }
        if (template) {
       
         // 将html模板转为render函数
          const { render, staticRenderFns } = compileToFunctions(
            template,
            {
              outputSourceRange: __DEV__,
              shouldDecodeNewlines,
              shouldDecodeNewlinesForHref,
              delimiters: options.delimiters,
              comments: options.comments
            },
            this
          )
          options.render = render
          options.staticRenderFns = staticRenderFns
        }
      }
      return mount.call(this, el, hydrating)
    }

Watch Watcher

src/core/instance/state.ts - initState中,触发initWatch

  • 传入的watch数组如果是数组需要遍历创建Watcher

    scss 复制代码
    function initWatch(vm: Component, watch: Object) {
      for (const key in watch) {
        const handler = watch[key]
        if (isArray(handler)) {
          for (let i = 0; i < handler.length; i++) {
            createWatcher(vm, key, handler[i])
          }
        } else {
          createWatcher(vm, key, handler)
        }
      }
    }
  • 如果传入handle是一个平面对象,需要拆出handler,重新调用$watch

    typescript 复制代码
    function createWatcher(
      vm: Component,
      expOrFn: string | (() => any),
      handler: any,
      options?: Object
    ) {
      if (isPlainObject(handler)) {
        options = handler
        handler = handler.handler
      }
      if (typeof handler === 'string') {
        handler = vm[handler]
      }
      return vm.$watch(expOrFn, handler, options)
    }

src/core/instance/state.ts - 绑定$watch

  • 拆解cb中的handler
  • 创建Watcher
  • 如果是immediate需要先执行一边Watcher
  • 返回Watcher清理函数
typescript 复制代码
  Vue.prototype.$watch = function (
    expOrFn: string | (() => any),
    cb: any,
    options?: Record<string, any>
  ): Function {
    // 拆解cb 会重新执行$watch
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn() {
      watcher.teardown()
    }
  }
}

Computed Watcher

src/core/instance/state.ts - initState中,触发initComputed

  • 读取computed配置,生成Watcher
  • 如果不存在,就调用defineComputed函数。目的是避免覆盖实例上已经存在的属性
javascript 复制代码
const computedWatcherOptions = { lazy: true }

function initComputed(vm: Component, computed: Object) {
  // 保存 _computedWatchers
  const watchers = (vm._computedWatchers = Object.create(null))
  
  const isSSR = isServerRendering()

// 遍历computed配置,按顺序生成computed Watcher
  for (const key in computed) {
    const userDef = computed[key]
    const getter = isFunction(userDef) ? userDef : userDef.get
 
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,   // computed副作用函数
        noop,
        computedWatcherOptions  // 专属配置
      )
    }

    // 如果是一个新的值,要变成响应式的值
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } 
    }
  }
}

Watcher源码

源码地址 - src/core/observer/watcher.ts

  • src/v3/debug.ts - DepTarget的抽象类

    typescript 复制代码
    export interface DebuggerOptions {
      onTrack?: (event: DebuggerEvent) => void
      onTrigger?: (event: DebuggerEvent) => void
    }
  • src/core/observer/dep.ts Watcher的抽象类 DepTarget

    java 复制代码
    export interface DepTarget extends DebuggerOptions {
      id: number
      addDep(dep: Dep): void
      update(): void
    }
  • Watcher实现

    • recordEffectScope 的作用是 将副作用(Effect,如 Watcher)与当前的作用域(如组件实例)关联,实现依赖的自动清理和资源管理
    • 根据配置判断等待执行还是立即执行getter
    • 使用traverse深度访问响应式数据,触发深度的getter
    • dirty 用于实现计算属性的缓存。当依赖的响应式数据未变化时,直接返回缓存值,避免重复计算
    • Watcher类实现 addDep update, 提供给dep类调用
    kotlin 复制代码
    import {
      warn,
      remove,
      isObject,
      parsePath,
      _Set as Set,
      handleError,
      invokeWithErrorHandling,
      noop,
      isFunction
    } from '../util/index'
    
    import { traverse } from './traverse'
    import { queueWatcher } from './scheduler'
    import Dep, { pushTarget, popTarget, DepTarget } from './dep'
    import { DebuggerEvent, DebuggerOptions } from 'v3/debug'
    
    import type { SimpleSet } from '../util/index'
    import type { Component } from 'types/component'
    import { activeEffectScope, recordEffectScope } from 'v3/reactivity/effectScope'
    
    let uid = 0
    
    /**
     * @internal
     */
    export interface WatcherOptions extends DebuggerOptions {
      deep?: boolean
      user?: boolean
      lazy?: boolean
      sync?: boolean
      before?: Function
    }
    
    /**
      *观察者解析表达式,收集依赖项,
      *并在表达式值改变时触发回调。
      *这用于$watch() api和指令。
     * @internal
     */
    export default class Watcher implements DepTarget {
      vm?: Component | null
      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   // 更新前的回调
      onStop?: Function
      noRecurse?: boolean
      getter: Function
      value: any
      post: boolean
    
      // vue开发者使用
      onTrack?: ((event: DebuggerEvent) => void) | undefined
      onTrigger?: ((event: DebuggerEvent) => void) | undefined
    
      constructor(
        vm: Component | null,   //  组件实例
        expOrFn: string | (() => any),  // render Watcher的render函数
        cb: Function,                   // 回调函数
        options?: WatcherOptions | null,   // 配置
        isRenderWatcher?: boolean   //  标记是render Watcher
      ) {
        recordEffectScope(
          this,
          // if the active effect scope is manually created (not a component scope),
          // prioritize it
          activeEffectScope && !activeEffectScope._vm
            ? activeEffectScope
            : vm
            ? vm._scope
            : undefined
        )
        // 标记render目标
        if ((this.vm = vm) && isRenderWatcher) {
          vm._watcher = this
        }
        // 挂载配置项到实例
        if (options) {
          this.deep = !!options.deep
          this.user = !!options.user
          this.lazy = !!options.lazy
          this.sync = !!options.sync
          this.before = options.before
          if (__DEV__) {
            this.onTrack = options.onTrack
            this.onTrigger = options.onTrigger
          }
        } else {
            // 设置默认值
          this.deep = this.user = this.lazy = this.sync = false
        }
        this.cb = cb
        this.id = ++uid // 创建id
        this.active = true
        this.post = false
        this.dirty = this.lazy // for lazy watchers
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.expression = __DEV__ ? expOrFn.toString() : ''
        // 获取getter的内容
        if (isFunction(expOrFn)) {
          this.getter = expOrFn
        } else {
          this.getter = parsePath(expOrFn)
          if (!this.getter) {
            this.getter = noop
            __DEV__ &&
              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()
      }
    
      /**
       * 执行getter 并且重新收集依赖
       */
      get() {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)
        } catch (e: any) {
          if (this.user) {
            handleError(e, vm, `getter for watcher "${this.expression}"`)
          } else {
            throw e
          }
        } finally {
          // 如果是深度监听 需要深度遍历 ,因为在前面变为响应了,需要访问一下触发一下get
          if (this.deep) {
            traverse(value)
          }
          popTarget()
          // 清空依赖 更新deps数组
          this.cleanupDeps()
        }
        return value
      }
    
      /**
       * 操作dep的增加
       */
      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: any = 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) {
              const info = `callback for watcher "${this.expression}"`
              invokeWithErrorHandling(
                this.cb,
                this.vm,
                [value, oldValue],
                this.vm,
                info
              )
            } 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.vm && !this.vm._isBeingDestroyed) {
          remove(this.vm._scope.effects, this)
        }
        if (this.active) {
          let i = this.deps.length
          while (i--) {
            this.deps[i].removeSub(this)
          }
          this.active = false
          if (this.onStop) {
            this.onStop()
          }
        }
      }
    }
相关推荐
傻球6 分钟前
Jotai 使用详解:React 轻量级状态管理库
前端·react.js
Json_8 分钟前
Vue 构造器 Vue.extend
前端·vue.js·深度学习
伶俜monster8 分钟前
UV 法向量实验室:Threejs 纹理与光照炼金术
前端·webgl·three.js
小old弟8 分钟前
老生常谈: 谈谈对vue的理解?vue3有而vue2没有的东西或者说特性?
前端·vue.js
蔓越莓11 分钟前
[Electron] win.setSize 在RTL布局下的表现,是否需要适配?
前端·electron
Json_16 分钟前
Vue 实例方法
前端·vue.js·深度学习
数据攻城小狮子17 分钟前
Java Spring Boot 与前端结合打造图书管理系统:技术剖析与实现
java·前端·spring boot·后端·maven·intellij-idea
阿鲁冶夫30 分钟前
最佳实践初始化项目公用cli
前端
Json_44 分钟前
实例入门 实例属性
前端·深度学习
Json_44 分钟前
JS中的apply和arguments小练习
前端·javascript·深度学习