vue2.7.16源码 - Vue构造函数

82 阅读5分钟

src/core/instance/index.ts - 构造函数创建

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
import type { GlobalAPI } from 'types/global-api'

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

// 往构造函数原型上新增_init方法
// _init方法是构造函数具体实现内容
//  合并配置项
// 关联组件父子关系
// 一些私有属性创建例如$slot,$emit等
// 触发 beforeCreate
// 仅inject 初始化
// 进行props data computed等的初始化
// 进行Provider初始化
// 触发 created
// 如果组件已经挂载 调用 $mount
initMixin(Vue)
// 对 $data $props等进行响应式处理,拦截set访问
// 挂载$set $delete $watch
stateMixin(Vue)
// 事件系统实现  挂载 $on $emit等
eventsMixin(Vue)
// 挂载 _update  $forceUpdate $destroy 等方法
lifecycleMixin(Vue)
// 挂载render函数 
renderMixin(Vue)

export default Vue as unknown as GlobalAPI

src/core/instance/init.ts - 构造函数中_init实现

  • initMixin 实现

    • 触发前置的生命周期,初始化状态和合并配置项,关联父子关系
    • 
      let uid = 0
      
      export function initMixin(Vue: typeof Component) {
        Vue.prototype._init = function (options?: Record<string, any>) {
          const vm: Component = this
          // 组件唯一id
          vm._uid = uid++
      
          let startTag, endTag
          /* istanbul ignore if */
          if (__DEV__ && config.performance && mark) {
            startTag = `vue-perf-start:${vm._uid}`
            endTag = `vue-perf-end:${vm._uid}`
            mark(startTag)
          }
      
          // 标记是vue的实例 无需重新new
          vm._isVue = true
          // 跳过观测
          vm.__v_skip = true
          // 用于收集副作用
          vm._scope = new EffectScope(true /* detached */)
          // 对父组件产生副作用
          vm._scope.parent = undefined
          vm._scope._vm = true
          // 合并配置
          if (options && options._isComponent) {
            // 跳过内置组件的合并
            initInternalComponent(vm, options as any)
          } else {
          // 合并构造函数的配置到组件上
          // 校验props inject 的属性
            vm.$options = mergeOptions(
              resolveConstructorOptions(vm.constructor as any),
              options || {},
              vm
            )
          }
          /* istanbul ignore else */
          if (__DEV__) {
            initProxy(vm)
          } else {
            vm._renderProxy = vm
          }
          // 保留实例到_self上
          vm._self = vm
          // 关联组件父子关系
          initLifecycle(vm)
          // 新增_events属性
          initEvents(vm)
          // 新增$slots $scopedSlots  $createElement 等方法
          initRender(vm)
          // 触发生命周期函数
          callHook(vm, 'beforeCreate', undefined, false /* setContext */)
          // 初始化inject
          initInjections(vm) // 
          // 初始化prop data computed watch等 事关数据合并覆盖
          initState(vm)
          // 初始化Provider,里面会有查找过程
          initProvide(vm)
          // 触发created 钩子
          callHook(vm, 'created')
      
          /* istanbul ignore if */
          if (__DEV__ && config.performance && mark) {
            vm._name = formatComponentName(vm, false)
            mark(endTag)
            measure(`vue ${vm._name} init`, startTag, endTag)
          }
      
          // 如果已经挂载过了执行$mount
          if (vm.$options.el) {
            vm.$mount(vm.$options.el)
          }
        }
      }
      
  • initInternalComponent 实现

    • export function initInternalComponent(
        vm: Component,
        options: InternalComponentOptions
      ) {
        const opts = (vm.$options = Object.create((vm.constructor as any).options))
        // 合并父组件实例和 VNode
        const parentVnode = options._parentVnode
        opts.parent = options.parent
        opts._parentVnode = parentVnode
       //  提取组件 VNode 中的关键数据
        const vnodeComponentOptions = parentVnode.componentOptions!
        opts.propsData = vnodeComponentOptions.propsData
        opts._parentListeners = vnodeComponentOptions.listeners
        opts._renderChildren = vnodeComponentOptions.children
        opts._componentTag = vnodeComponentOptions.tag
      
       // 处理渲染函数(如动态插槽)
        if (options.render) {
          opts.render = options.render
          opts.staticRenderFns = options.staticRenderFns
        }
      }
      
  • src/core/instance/lifecycle.ts - initLifecycle 实现

    • 关联组件父子关系,新增parent,parent,children,_watcher等属性
    • export function initLifecycle(vm: Component) {
        const options = vm.$options
      
        // 查找父组件 并且往父元素children属性插入当前组件
        let parent = options.parent
        if (parent && !options.abstract) {
          while (parent.$options.abstract && parent.$parent) {
            parent = parent.$parent
          }
          parent.$children.push(vm)
        }
       // 记录父组件
        vm.$parent = parent
        // 记录根节点 使用父元素的$root  如果自己是跟根节点使用自己做为$root
        vm.$root = parent ? parent.$root : vm
      
        vm.$children = []
        vm.$refs = {}
      
        // 记录Provider链条
        vm._provided = parent ? parent._provided : Object.create(null)
        
        // 初始化一些其他的私有属性
        vm._watcher = null
        vm._inactive = null
        vm._directInactive = false
        vm._isMounted = false
        vm._isDestroyed = false
        vm._isBeingDestroyed = false
      }
      
  • src/core/instance/events.ts - initEvents 实现

    • 新增_events 属性,调用updateComponentListeners,更新父组件listeners
    • export function initEvents(vm: Component) {
        vm._events = Object.create(null)
        vm._hasHookEvent = false
        // 获取父组件传递的 自定义事件
        // 子组件需要保留这些自定义事件,以便后续执行
        const listeners = vm.$options._parentListeners
        if (listeners) {
          updateComponentListeners(vm, listeners)
        }
      }
      
  • src/core/instance/render.ts - initRender 实现

    • export function initRender(vm: Component) {
        // 当前渲染的 VNode
        vm._vnode = null
        // 静态树缓存
        vm._staticTrees = null  
        const options = vm.$options
        // 获取父组件虚拟dom
        const parentVnode = (vm.$vnode = options._parentVnode!)
        // 父组件上下文
        const renderContext = parentVnode && (parentVnode.context as Component)
        // 获取插槽需要渲染的内容
        vm.$slots = resolveSlots(options._renderChildren, renderContext)
        // 初始化 $scopedSlots
        // 编译为函数,存储在 vm.$scopedSlots,延迟到子组件渲染时执行
        vm.$scopedSlots = parentVnode
          ? normalizeScopedSlots(
              vm.$parent!,
              parentVnode.data!.scopedSlots,
              vm.$slots
            )
          : emptyObject
        // 保留虚拟dom创建函数
        vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
        vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
      
        // 获取父组件的data数据
        const parentData = parentVnode && parentVnode.data
      
        /* istanbul ignore else */
        if (__DEV__) {
          defineReactive(
            vm,
            '$attrs',
            (parentData && parentData.attrs) || emptyObject,
            () => {
              !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
            },
            true
          )
          defineReactive(
            vm,
            '$listeners',
            options._parentListeners || emptyObject,
            () => {
              !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
            },
            true
          )
        } else {
        // 把$attrs和$listeners变为响应式数据
          defineReactive(
            vm,
            '$attrs',
            (parentData && parentData.attrs) || emptyObject,
            null,
            true
          )
          defineReactive(
            vm,
            '$listeners',
            options._parentListeners || emptyObject,
            null,
            true
          )
        }
      }
      
  • src/core/instance/lifecycle.ts - callHook 实现

    • export function callHook(
        vm: Component,
        hook: string,
        args?: any[],
        setContext = true
      ) {
        // #7573 disable dep collection when invoking lifecycle hooks
        pushTarget()
        const prevInst = currentInstance
        const prevScope = getCurrentScope()
        setContext && setCurrentInstance(vm)
        const handlers = vm.$options[hook]
        const info = `${hook} hook`
        if (handlers) {
          for (let i = 0, j = handlers.length; i < j; i++) {
            invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
          }
        }
        if (vm._hasHookEvent) {
          vm.$emit('hook:' + hook)
        }
        if (setContext) {
          setCurrentInstance(prevInst)
          prevScope && prevScope.on()
        }
      
        popTarget()
      }
      
  • src/core/instance/inject.ts - initInjections 实现

    • inject早于prop 和 data
    • export function initInjections(vm: Component) {
        const result = resolveInject(vm.$options.inject, vm)
        if (result) {
          toggleObserving(false)
          Object.keys(result).forEach(key => {
            /* istanbul ignore else */
            if (__DEV__) {
              defineReactive(vm, key, result[key], () => {
                warn(
                  `Avoid mutating an injected value directly since the changes will be ` +
                    `overwritten whenever the provided component re-renders. ` +
                    `injection being mutated: "${key}"`,
                  vm
                )
              })
            } else {
              defineReactive(vm, key, result[key])
            }
          })
          toggleObserving(true)
        }
      }
      
      export function resolveInject(
        inject: any,
        vm: Component
      ): Record<string, any> | undefined | null {
        if (inject) {
          // inject is :any because flow is not smart enough to figure out cached
          const result = Object.create(null)
          const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject)
      
          for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            // #6574 in case the inject object is observed...
            if (key === '__ob__') continue
            const provideKey = inject[key].from
            if (provideKey in vm._provided) {
              result[key] = vm._provided[provideKey]
            } else if ('default' in inject[key]) {
              const provideDefault = inject[key].default
              result[key] = isFunction(provideDefault)
                ? provideDefault.call(vm)
                : provideDefault
            } else if (__DEV__) {
              warn(`Injection "${key as string}" not found`, vm)
            }
          }
          return result
        }
      }
      
  • src/core/instance/state.ts - initState

    • 初始化data prop等数据配置
    • export function initState(vm: Component) {
        const opts = vm.$options
        if (opts.props) initProps(vm, opts.props)
      
        // Composition API
        initSetup(vm)
      
        if (opts.methods) initMethods(vm, opts.methods)
        if (opts.data) {
          initData(vm)
        } else {
          const ob = observe((vm._data = {}))
          ob && ob.vmCount++
        }
        if (opts.computed) initComputed(vm, opts.computed)
        if (opts.watch && opts.watch !== nativeWatch) {
          initWatch(vm, opts.watch)
        }
      }
      
  • src/core/instance/inject.ts - initProvide 实现

    • import { resolveProvided } from 'v3/apiInject'
      
      export function initProvide(vm: Component) {
        const provideOption = vm.$options.provide
        if (provideOption) {
          const provided = isFunction(provideOption)
            ? provideOption.call(vm)
            : provideOption
          if (!isObject(provided)) {
            return
          }
          const source = resolveProvided(vm)
          // IE9 doesn't support Object.getOwnPropertyDescriptors so we have to
          // iterate the keys ourselves.
          const keys = hasSymbol ? Reflect.ownKeys(provided) : Object.keys(provided)
          for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            Object.defineProperty(
              source,
              key,
              Object.getOwnPropertyDescriptor(provided, key)!
            )
          }
        }
      }
      

src/core/instance/state.ts - stateMixin实现

  • 实现 datadata props $set等原型方法

    • export function stateMixin(Vue: typeof Component) {
        const dataDef: any = {}
        dataDef.get = function () {
          return this._data
        }
        const propsDef: any = {}
        propsDef.get = function () {
          return this._props
        }
        if (__DEV__) {
          dataDef.set = function () {
            warn(
              'Avoid replacing instance root $data. ' +
                'Use nested data properties instead.',
              this
            )
          }
          propsDef.set = function () {
            warn(`$props is readonly.`, this)
          }
        }
        // 对$data进行get处理, 禁止set访问
        Object.defineProperty(Vue.prototype, '$data', dataDef)
        Object.defineProperty(Vue.prototype, '$props', propsDef)
      
        // 挂载$set 和 $delete
        Vue.prototype.$set = set
        Vue.prototype.$delete = del
      
        Vue.prototype.$watch = function (
          expOrFn: string | (() => any),
          cb: any,
          options?: Record<string, any>
        ): Function {
          const vm: Component = this
          // cb如果是对象 需要进行拆解
          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()
          }
        }
      }
      
  • src/core/instance/state.ts - createWatcher实现

    • function createWatcher(
        vm: Component,
        expOrFn: string | (() => any),
        handler: any,
        options?: Object
      ) {
      // 拆解hanlder 和 options 再次调用$watch
        if (isPlainObject(handler)) {
          options = handler
          handler = handler.handler
        }
        if (typeof handler === 'string') {
          handler = vm[handler]
        }
        return vm.$watch(expOrFn, handler, options)
      }
      

src/core/instance/events.ts - eventsMixin 实现

  • 事项onon emit onceonce off 等原型方法
export function eventsMixin(Vue: typeof Component) {
  const hookRE = /^hook:/
  // $on 支持一对多和一对一,一对多需要遍历数据进行监听
  Vue.prototype.$on = function (
    event: string | Array<string>,
    fn: Function
  ): Component {
    const vm: Component = this
    if (isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      ;(vm._events[event] || (vm._events[event] = [])).push(fn)
      // 这里做了优化 判断key是否存在
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

// 内部包装了一下,调用完成后解除订阅
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on() {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }
// 删除订阅
  Vue.prototype.$off = function (
    event?: string | Array<string>,
    fn?: Function
  ): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event!]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event!] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

// 触发订阅
  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (__DEV__) {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
            `${formatComponentName(
              vm
            )} but the handler is registered for "${event}". ` +
            `Note that HTML attributes are case-insensitive and you cannot use ` +
            `v-on to listen to camelCase events when using in-DOM templates. ` +
            `You should probably use "${hyphenate(
              event
            )}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

src/core/instance/lifecycle.ts - lifecycleMixin 实现

  • 挂载 _update forceUpdateforceUpdate destroy 等方法,实现虚拟dom到真实dom的更新
  • $destroy 实现销毁逻辑,触发销毁声明周期
export function lifecycleMixin(Vue: typeof Component) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    //  首次渲染 将 VNode 渲染为真实 DOM
    if (!prevVnode) {
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // 比较新旧 VNode,局部更新 当前DOM
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    let wrapper: Component | undefined = vm
    // 进行新旧节点比较
    while (
      wrapper &&
      wrapper.$vnode &&
      wrapper.$parent &&
      wrapper.$vnode === wrapper.$parent._vnode
    ) {
      wrapper.$parent.$el = wrapper.$el
      wrapper = wrapper.$parent
    }
  }

// 强制刷新
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    // 已经销毁不再执行
    if (vm._isBeingDestroyed) {
      return
    }
    // 触发 beforeDestroy
    callHook(vm, 'beforeDestroy')
    // 开始销毁
    vm._isBeingDestroyed = true
    //从父组件移除
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    vm._scope.stop()
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    vm._isDestroyed = true
    //  更新真实dom
    vm.__patch__(vm._vnode, null)
    // 触发卸载完成
    callHook(vm, 'destroyed')
    // 卸载订阅函数
    vm.$off()
    // 
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    //  移除父组件绑定
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

src/core/instance/render.ts - renderMixin 实现

  • 挂载原型方法 $nextTick 和 _render
  • 往原型上增加一些辅助函数
export function renderMixin(Vue: typeof Component) {
  // 挂载一些辅助函数
  installRenderHelpers(Vue.prototype)

  // 挂载nextTick函数
  Vue.prototype.$nextTick = function (fn: (...args: any[]) => any) {
    return nextTick(fn, this)
  }

  // 挂载render函数
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    // loader编译完后会把template变为render函数
    const { render, _parentVnode } = vm.$options

     // 执行slot和scopeSlot
    if (_parentVnode && vm._isMounted) {
      vm.$scopedSlots = normalizeScopedSlots(
        vm.$parent!,
        _parentVnode.data!.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
      if (vm._slotsProxy) {
        syncSetupSlots(vm._slotsProxy, vm.$scopedSlots)
      }
    }
    // 标记当前组件在父组件中的位置
    vm.$vnode = _parentVnode!
    // render self
    const prevInst = currentInstance
    const prevRenderInst = currentRenderingInstance
    let vnode
    try {
      setCurrentInstance(vm)
      currentRenderingInstance = vm
      // 调用用户定义的 render 函数或编译后的模板函数
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e: any) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (__DEV__ && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(
            vm._renderProxy,
            vm.$createElement,
            e
          )
        } catch (e: any) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = prevRenderInst
      setCurrentInstance(prevInst)
    }
    // 确保返回的是合法的 VNode 必须是单根节点
    if (isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (__DEV__ && isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
            'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // 设置当前实例的 _vnode,用于后续的 patch 阶段
    vnode.parent = _parentVnode
    return vnode
  }
}