Skip to content
/ vue Public
forked from vuejs/vue

vue2 源码解析,处理流程

License

Notifications You must be signed in to change notification settings

px6707/vue

This branch is 11 commits ahead of vuejs/vue:main.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Mar 17, 2025
a5094f8 · Mar 17, 2025
Dec 31, 2023
Jul 4, 2022
Jun 1, 2022
Oct 11, 2022
Dec 27, 2022
Jun 14, 2024
Oct 11, 2022
Feb 1, 2025
Dec 6, 2023
Dec 14, 2023
Mar 31, 2020
Jun 6, 2022
May 31, 2022
May 23, 2022
Feb 7, 2022
Oct 10, 2024
Jan 10, 2017
Mar 17, 2025
Jun 1, 2022
Jun 10, 2022
Dec 24, 2023
Dec 14, 2023
May 31, 2022
Oct 11, 2022
Jun 20, 2022

Repository files navigation

Vue 2 源码解析

vue2 源码解析,vue2源码流程图

目录

构建版本说明

- UMD CommonJS ESModule ESModule (Browser)
完整版 vue.js vue.common.js vue.esm.js vue.esm.browser.js
只包含运行时版 vue.runtime.js vue.runtime.common.js vue.runtime.esm.js -
完整版 (生产环境) vue.min.js - - vue.esm.browser.min.js
只包含运行时版 (生产环境) vue.runtime.min.js - - -

版本特点

  1. 完整版 (Full Build)
    • 同时包含编译器和运行时
    • 可以直接在浏览器中编译模板
    • 体积较大
  2. 运行时版本 (Runtime Only)
    • 只包含运行时,不包含编译器
    • 体积小,比完整版轻约 30%
    • 不能在浏览器中编译模板
  3. ESM 构建版本
    • 基于 ES modules
    • 支持现代打包工具

注意:如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就将需要加上编译器,即完整版。


源码入口分析

通过观察package.json可以看到

  "scripts": {
    "build": "node scripts/build.js",
  },

vue2使用 scripts/build.js 脚本进行打包。构建过程通过build(builds)对builds数组进行打包,builds数组包含了所有的打包配置信息。在scripts/config.js中可以查看详细的打包配置信息。我们源码分析主要关注带编译器的版本,入口文件是entry-runtime-with-compiler.ts
在这个文件中

import Vue from './runtime-with-compiler'

说明了vue引自runtime-with-compiler.ts 同样的,我们查看这个文件就能发现

import Vue from './runtime/index'

其引入的路径在instance/index.ts

function Vue(options) {
  if (__DEV__ && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

这样我们终于找到了vue2的本体,也就是这个构造函数。

入口文件加载流程

  1. entry-runtime-with-compiler.ts

    • 入口文件,添加编译器
  2. runtime-with-compiler.ts

    • 运行时编译器实现
  3. runtime/index.ts

    • 运行时核心功能
  4. instance/index.ts

    • Vue 构造函数定义

vue-构造函数的扩展

1. 扩展挂载,添加编译能力

Vue的本体就是一个构造函数,而且极其简单,只有一行代码就是调用了_init方法,这个方法甚至在Vue构造函数中找不到。Vue为了跨平台的特性,通过不同的platform入口,给Vue扩展了不同的能力。 文件:entry-runtime-with-compiler.ts

const mount = Vue.prototype.$mount
Vue.prototype.$mount= function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  ...
  // 如果render函数不存在
  if (!options.render) {
    let template = options.template
    if (template) {
      // template是字符串
      if (typeof template === 'string') {
        // template开头是# 说明传入的是节点ID
        if (template.charAt(0) === '#') {
          // 通过idToTemplate函数将节点ID转换为模板
          template = idToTemplate(template)
        }
        // 如果传入的是dom节点,有nodeType说明是dom节点
      } else if (template.nodeType) {
        // 获取DOM节点上的模版
        template = template.innerHTML
      } else {
        return this
      }
      // 没有template的情况下如果有el参数则使用el getOuterHTML作为模板
    } else if (el) {
      // @ts-expect-error
      template = getOuterHTML(el)
    }
    // 如果template模版存在
    if (template) {
      // 模版编译为render函数
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: __DEV__,
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        },
        this
      )
      // render函数挂载到options上
      options.render = render
      // 静态渲染函数
      // vue会在编译时把静态部分抽离出来生成一个静态渲染函数数组,并对执行后的渲染结果进行缓存
      options.staticRenderFns = staticRenderFns
    }
  }
  ...
}

在这个带编译器版本的vue中,它通过重写原型链上的$mount函数,给$mount添加了编译模版的能力。最后又调用了原始的$mout 让这个函数具有挂载的能力。

模板编译过程

  1. 模板字符串

    • <div>{{ message }}</div>
  2. 解析器(Parser)

    • 将模板解析为 AST
    • 标记静态节点
  3. 优化器(Optimizer)

    • 对 AST 进行静态分析
    • 标记静态子树
  4. 代码生成器(Code Generator)

    • 生成 render 函数
    • 生成 staticRenderFns
  5. 虚拟 DOM

    • render 函数执行
    • 生成 VNode 树

2. 扩展全局API

文件:core/index.ts

import { initGlobalAPI } from './global-api/index'
// 通过initGloablAPI函数扩展全局API
initGlobalAPI(Vue)

在initGlobalAPI中添加了mergeOptions、defineReactive、use、mixin、extend、component、filter、directive等工具函数。

3. 扩展Vue原型能力

在Vue本体的文件中,通过5个函数对Vue本体进行了扩展,分别是 文件:core/index.ts

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
// 给Vue类 添加能力
//@ts-expect-error Vue has function type
// 添加初始化函数_init
initMixin(Vue)
//@ts-expect-error Vue has function type
// Vue类上添加$props $data 的代理添加$watch方法
stateMixin(Vue)
//@ts-expect-error Vue has function type
// 给Vue类上添加$on $off $once $emit方法
eventsMixin(Vue)
//@ts-expect-error Vue has function type
// Vue类上挂载_update、$forceUpdate、$destroy方法
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
// 挂载生成虚拟节点的方法_render和$nextTick,以及执行render函数需要的各种工具函数
renderMixin(Vue)  

他们分别为Vue添加了_init函数,添加了$props、$data代理,给Vue类添加$on、$off $once $emit方法,给Vue类添加_update、$forceUpdate、$destroy方法,给Vue类添加_render和$nextTick,以及执行render函数需要的各种工具函数。

Vue实例化的过程

Vue 的实例化是从执行 new Vue() 开始的,这个过程主要通过 _init 方法完成初始化。让我们详细了解这个过程。

1. 初始化前的准备工作

Vue.prototype._init = function (options?: Record<string, any>) {
  const vm: Component = this
  // 设置实例的唯一标识
  vm._uid = uid++
  
  // 标记为 Vue 实例
  vm._isVue = true
  // 避免被响应式系统观察
  vm.__v_skip = true
  // 创建效果作用域
  vm._scope = new EffectScope(true /* detached */)
}

这个阶段主要完成:

  • 设置实例唯一标识 _uid
  • 标记 Vue 实例,防止被响应式系统观察
  • 创建独立的效果作用域

2. 合并选项

// 合并选项
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor as any),
  options || {},
  vm
)

其中mergeOptions的实现包含了合并策略:

  1. 生命周期钩子函数合并
    export function mergeLifecycleHook(
      parentVal: Array<Function> | null,
      childVal: Function | Array<Function> | null
    ): Array<Function> | null {
      const res = childVal
        ? parentVal
          ? parentVal.concat(childVal)
          : isArray(childVal)
          ? childVal
          : [childVal]
        : parentVal
      return res ? dedupeHooks(res) : res
    }
    • 子组件和父组件的同名钩子函数将合并为一个数组
    • 父组件的钩子函数在子组件之前调用
    • 去重
  2. data 选项
    strats.data = function (
      parentVal: any, 
      childVal: any, 
      vm?: Component
    ): Function | null {
      if (!vm) {
        return mergeDataOrFn(parentVal, childVal)
      }
    
      return mergeDataOrFn(parentVal, childVal, vm)
    }
    
    
    function mergeDataOrFn(
      parentVal: any,
      childVal: any,
      vm?: Component
    ): Function | null {
      if (!vm) {
        if (!childVal) {
          return parentVal
        }
        // 复选项没有,直接返回子选项
        if (!parentVal) {
          return childVal
        }
        return function mergedDataFn() {
          // 如果选项是函数,则先执行这个函数,返回值作为需要merge的data
          return mergeData(
            isFunction(childVal) ? childVal.call(this, this) : childVal,
            isFunction(parentVal) ? parentVal.call(this, this) : parentVal
          )
        }
      } else {
        return function mergedInstanceDataFn() {
          // instance merge
          const instanceData = isFunction(childVal)
            ? childVal.call(vm, vm)
            : childVal
          const defaultData = isFunction(parentVal)
            ? parentVal.call(vm, vm)
            : parentVal
          if (instanceData) {
            return mergeData(instanceData, defaultData)
          } else {
            return defaultData
          }
        }
      }
    }
    • 组件定义时 data 必须是函数
    • 通过 mergeDataOrFn 递归合并对象的属性
  3. 资源选项合并
function mergeAssets(
  parentVal: Object | null,
  childVal: Object | null,
  vm: Component | null,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    __DEV__ && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

处理了 components directives filters - 创建一个原型指向父选项的空对象 - 将子选项复制到这个对象上 - 这样可以通过原型链访问父选项中的资源

  1. watch 选项
    strats.watch = function (
    parentVal: Record<string, any> | null,
    childVal: Record<string, any> | null,
    vm: Component | null,
    key: string
  ): Object | null {
    // work around Firefox's Object.prototype.watch...
    //@ts-expect-error work around
    if (parentVal === nativeWatch) parentVal = undefined
    //@ts-expect-error work around
    if (childVal === nativeWatch) childVal = undefined
    /* istanbul ignore if */
    if (!childVal) return Object.create(parentVal || null)
    if (__DEV__) {
      assertObjectType(key, childVal, vm)
    }
    if (!parentVal) return childVal
    const ret: Record<string, any> = {}
    extend(ret, parentVal)
    for (const key in childVal) {
      let parent = ret[key]
      const child = childVal[key]
      if (parent && !isArray(parent)) {
        parent = [parent]
      }
      ret[key] = parent ? parent.concat(child) : isArray(child) ? child : [child]
    }
    return ret
  }
  • watch 选项中的函数会被合并为数组
  • 支持多个监听器监听同一个属性
  1. props、methods、inject、computed
    strats.props =
      strats.methods =
      strats.inject =
      strats.computed =
        function (
          parentVal: Object | null,
          childVal: Object | null,
          vm: Component | null,
          key: string
        ): Object | null {
          if (childVal && __DEV__) {
            assertObjectType(key, childVal, vm)
          }
          if (!parentVal) return childVal
          const ret = Object.create(null)
          extend(ret, parentVal)
          if (childVal) extend(ret, childVal)
          return ret
        }
  • 子选项优先级高于父选项
  • 同名的子选项会覆盖父选项
  1. provide 选项:
      strats.provide = function (
        parentVal: Object | null, 
        childVal: Object | null) {
        if (!parentVal) return childVal
        return function () {
          const ret = Object.create(null)
          mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal)
          if (childVal) {
            mergeData(
              ret,
              isFunction(childVal) ? childVal.call(this) : childVal,
              false // non-recursive
            )
          }
          return ret
        }
      }
    • 与 data 选项使用相同的合并策略
  2. 默认策略
    const defaultStrat = function (parentVal: any, childVal: any): any {
      return childVal === undefined ? parentVal : childVal
    }
    • 子选项存在则使用子选项
    • 子选项不存在则使用父选项
  3. 对于mixin
      if (child.mixins) {
      // 如果具有mixin,对每一个mixin进行合并选项
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
    • mixin 循环向父选项上合并

总结:

  1. 生命周期都会执行,父组件(全局配置、mixin)的钩子函数在子组件(本组件)之前调用。多个mixin,后注册的声明周期后执行。
  2. 对于数据、方法、filter,本组件覆盖父组件(mixin),多个mixin,后注册的mixin覆盖先注册的mixin。

3. 核心初始化流程

_init 方法中,Vue 按照特定的顺序执行了一系列初始化方法:

// 初始化生命周期
initLifecycle(vm)
// 初始化事件
initEvents(vm)
// 初始化渲染
initRender(vm)
// 调用beforeCreate钩子
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
// 初始化注入
initInjections(vm)
// 初始化状态
initState(vm)
// 初始化provide
initProvide(vm)
// 调用created钩子
callHook(vm, 'created')

这些初始化方法各自完成特定的任务:

  1. initLifecycle: 建立父子组件关系,初始化生命周期相关属性
    export function initLifecycle(vm: Component) {
      const options = vm.$options
      // 找到第一个非抽象的父组件
      let parent = options.parent
      if (parent && !options.abstract) {
        while (parent.$options.abstract && parent.$parent) {
          parent = parent.$parent
        }
        parent.$children.push(vm)
      }
      vm.$parent = parent
      vm.$root = parent ? parent.$root : vm
      vm.$children = []
      vm.$refs = {}
    }
  2. initEvents: 初始化事件
    • 处理父组件传递的事件监听器
    • 将事件添加到实例的 _events 属性上
  3. initRender: 初始化渲染相关功能
    • 处理插槽,将其放入 $slots
    • 处理作用域插槽,将其放入 $scopedSlots
    • 定义 $createElement 方法用于生成虚拟 DOM
    • 对 $attrs 和 $listeners 做响应式处理
  4. initInjections: 初始化注入
    • 处理 inject 配置
    • 将注入的属性代理到实例上
  5. initState: 按顺序初始化各种状态,并且会将props、data进行响应式处理
    export function initState(vm: Component) {
      const opts = vm.$options
      // 初始化props
      if (opts.props) initProps(vm, opts.props)
      // Composition API
      initSetup(vm)
      // 初始化methods
      if (opts.methods) initMethods(vm, opts.methods)
      // 初始化data
      if (opts.data) {
        initData(vm)
      } else {
        const ob = observe((vm._data = {}))
        ob && ob.vmCount++
      }
      // 初始化computed
      if (opts.computed) initComputed(vm, opts.computed)
      // 初始化watch
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    function initProps(vm: Component, propsOptions: Object) {
      defineReactive(props, key, value, undefined, true /* shallow */)
    }
    function initMethods(vm: Component, methods: Object) {
      // 函数绑定到vue实例上,this指向当前实例
      vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    }
    function initComputed(vm: Component, computed: Object) {
      ...
      const watchers = (vm._computedWatchers = Object.create(null))
      ...
       watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
      ...
    }
    function initWatch(vm: Component, watch: Object) {
      ...
      createWatcher(vm, key, handler)
      ...
    }
    function initData(vm: Component) {
      ...
      const ob = observe(data)
      ...
    }
  6. initProvide: 初始化 provide
    • 将 provide 选项挂载到实例的 _provided 属性上

4. 响应式处理

initState中对数据进行了响应式处理。Vue 的响应式系统主要由三个核心部分组成:Observer(数据劫持)、Dep(依赖收集)和 Watcher(订阅者)。

  1. observe 函数
    observe(data)export function observe(value: any): Observer | void {
      // 已经是响应式对象,直接返回
      if (value && hasOwn(value, '__ob__')) {
        return value.__ob__
      }
      // 满足以下条件才会创建 Observer:
      // 1. 是数组或普通对象
      // 2. 对象可扩展
      // 3. 不是被跳过的对象
      // 4. 不是 ref(因为已经是响应式)
      // 5. 不是 VNode
      if (shouldObserve && 
          (isArray(value) || isPlainObject(value)) && 
          Object.isExtensible(value) &&
          !value.__v_skip &&
          !isRef(value) &&
          !(value instanceof VNode)
      ) {
        return new Observer(value)
      }
    }
  2. Observer 类:负责将对象转换为响应式
    export class Observer {
      constructor(value: any) {
        this.dep = new Dep()
        // 在值上定义 __ob__ 属性,指向 Observer 实例
        def(value, '__ob__', this)
        
        if (isArray(value)) {
          // 数组的特殊处理
          if (hasProto) {
            value.__proto__ = arrayMethods  // 覆盖数组原型方法
          } else {
            // 降级处理:直接在对象上定义方法
            for (let i = 0; i < arrayKeys.length; i++) {
              def(value, arrayKeys[i], arrayMethods[arrayKeys[i]])
            }
          }
          this.observeArray(value)  // 递归处理数组元素
        } else {
          // 对象的处理:遍历属性转换为 getter/setter
          const keys = Object.keys(value)
          for (let i = 0; i < keys.length; i++) {
            defineReactive(value, keys[i])
          }
        }
      }
    }
  3. defineReactive 函数:核心响应式处理
      export function defineReactive(obj: object, key: string, val?: any) {
        // 每个属性都有自己的 dep 实例
        const dep = new Dep()
        
        // 获取属性描述符
        const property = Object.getOwnPropertyDescriptor(obj, key)
        const getter = property && property.get
        const setter = property && property.set
        
        // 递归子属性,对子属性进行响应式处理
        let childOb = observe(val)
        // 响应式处理的核心方法
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            // 依赖收集
            if (Dep.target) {
              dep.depend()
              // 子对象依赖收集
              if (childOb) {
                childOb.dep.depend()
              }
            }
            return value
          },
          set: function reactiveSetter(newVal) {
            const value = getter ? getter.call(obj) : val
            if (value === newVal) return
            
            val = newVal
            // 新值可能是对象,继续做响应式处理
            childOb = observe(newVal)
            // 通知更新
            dep.notify()
          }
        })
      }
    • 每一个key都会创建一个Dep实例
    • 当数据被读取时,也就是get操作时,会依次调用每个Dep实例的depend方法
    • 当数据被修改时,也就是set操作时,会依次调用每个Dep实例的notify方法
  4. 数组的特殊处理
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    methodsToPatch.forEach(function(method) {
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator(...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        // 对新增的元素进行响应式处理
        let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // 通知更新
        ob.dep.notify()
        return result
      })
    })
    • 由于 Object.defineProperty 无法监听数组的变化,Vue 通过重写数组方法来实现响应式
  5. Dep 类:用于收集依赖
    export class Dep {
      constructor() {
        this.subs = []
      }
      depend() {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
      notify() {
        for (let i = 0, l = subs.length; i < l; i++) {
          const sub = subs[i]
          sub.update()
        }
      }
    }

注意:现在只是进行了数据的响应式,但数据既没有被读取,也没有被修改,所以没有触发依赖更新,所以没有触发视图更新。在后面每一个vue实例都会new 一个渲染Watcher,它在第一个创建的时候会执行render函数,读取数据,触发响应式的依赖收集。

5. 挂载过程

如果在实例化时提供了 el 选项,Vue 会自动调用 $mount 方法进行挂载

  1. 编译模版,如果不存在render函数则使用template生成render函数 runtime-with-compiler.ts
      if (!options.render) {
        ...
        if (template) {
          ...
          const { render, staticRenderFns } = compileToFunctions(
            template,
            {
              outputSourceRange: __DEV__,
              shouldDecodeNewlines,
              shouldDecodeNewlinesForHref,
              delimiters: options.delimiters,
              comments: options.comments
            },
            this
          )
          // render函数挂载到options上
          options.render = render
          ...
        }
        ...
      }
    • 获取模板:优先使用 template 选项,其次是 el 的 outerHTML
    • 将模板编译为 render 函数
    • 将编译后的 render 和 staticRenderFns 挂载到 options 上
  2. 调用mountComponent进行挂载runtime/idnex.ts
      Vue.prototype.$mount = function (
        el?: string | Element,
        hydrating?: boolean
      ): Component {
        // 查找到对应的挂载节点
        el = el && inBrowser ? query(el) : undefined
        return mountComponent(this, el, hydrating)
      }
    挂载的实现在instance/lifecycle.ts
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode
    }
    callHook(vm, 'beforeMount')
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
    
    new Watcher(
        // 当前vue实例
        vm,
        // 更新函数
        updateComponent,
        // 空函数作为回调函数
        noop,
        // watcher选项
        watcherOptions,
        true /* isRenderWatcher */
      )
    if (vm.$vnode == null) {
      vm._isMounted = true
      callHook(vm, 'mounted')
    }
    • 检查 render 函数,不存在则使用空的 VNode
    • 调用 beforeMount 钩子
    • 创建渲染 Watcher,负责组件的更新,使用 updateComponent作为更新函数,更新函数会通过执行render函数获取虚拟节点,然后传递给_update方法渲染成真是DOM节点
    • Watcher 会立即执行一次更新函数,进行首次渲染
    • 设置 _isMounted 标志并调用 mounted 钩子

6.组件更新

在 Vue 中,组件的更新是由 Watcher 来控制的。每个组件实例都会创建一个渲染 Watcher,它负责在数据变化时重新渲染组件。

  1. Watcher 的初始化过程

    // 初始化watcher
    constructor(vm, expOrFn, cb, options, isRenderWatcher) {
      this.vm = vm
      if (isRenderWatcher) {
        vm._watcher = this    // 保存到组件实例上
      }
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
      this.getter = expOrFn   // 更新函数
      // 首次执行 getter 进行依赖收集
      this.value = this.lazy ? undefined : this.get()
    }
    get() {
      // 将当前 Watcher 设置为全局的活动 Watcher
        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 {
          // "touch" every property so they are all tracked as
          // dependencies for deep watching
          if (this.deep) {
            traverse(value)
          }
          popTarget()
          this.cleanupDeps()
        }
        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)
        }
      }
    }
    update() {
      /* istanbul ignore else */
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }
    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
        }
      }
    }
    • 创建 Watcher 时,会调用this.get()进行首次渲染,并触发依赖收集
    • 将当前 Watcher 设置为全局的活动 Watcher
    • 执行 getter 函数(即 updateComponent)
  2. Watcher 的依赖收集

    Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            // 依赖收集
            if (Dep.target) {
              dep.depend()
              // 子对象依赖收集
              if (childOb) {
                childOb.dep.depend()
              }
            }
            return value
          },
          set: function reactiveSetter(newVal) {
            const value = getter ? getter.call(obj) : val
            if (value === newVal) return
            
            val = newVal
            // 新值可能是对象,继续做响应式处理
            childOb = observe(newVal)
            // 通知更新
            dep.notify()
          }
    })
    class Dep {
      constructor() {
        this.subs = []
      }
      depend() {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
      notify() {
        for (let i = 0, l = subs.length; i < l; i++) {
          const sub = subs[i]
          sub.update()
        }
      }
    }

    我们再次将响应式核心代码和Dep的实现贴在这里,方便查看。

    • 当数据被读取,也就是get操作时,会依次调用每个Dep实例的depend方法
    • Dep.target就是当前正在进行依赖收集的watcher,他会执行addDep方法
    • 当Watcher执行addDep的时候,会使用dep.addSub(this)将当前的watcher添加到的的dep实例的subs数组中,完成依赖收集
  3. Watcher 的更新

    • 当数据被修改,也就是set操作时,会依次调用每个Dep实例的notify方法,notify方法会通知当前 Dep 实例的所有sub,也就是所依赖的所有watcher进行更新,执行Watcher的update方法
    • update方法会直接使用this.run()或者使用queueWatcher队列来使用this.run()
    • this.run()会直接调用this.get()方法,也就是执行getter,这个getter就是updateComponent,会执行render函数生成虚拟节点,然后使用update方法生成真实DOM进行视图更新
    • 在队列执行run之前会执行watcher.before(),会触发beforeUpdate钩子
    • 在队列执行run之后会触发updated钩子
  4. 批量更新

    • queueWatcher 函数中,会将更新函数放入到nextTick队列中,且添加waiting标志位,在flushSchedulerQueue执行更新函数的时候waiting为true,更新函数只会进入队列而不执行。当flushSchedulerQueue执行完成后,waiting标志位会变为false,队列中的更新函数会执行
    • vue组件中的nextTick回调会写在数据更新之后,这个回调也会放在nextTick的队列中,且顺序在更新函数之后
    • 等更新函数执行完成后,才会执行组件中的nextTick回调,保证了能够拿到最新的DOM

About

vue2 源码解析,处理流程

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 96.8%
  • JavaScript 1.7%
  • Other 1.5%