Vue 2.0 源码分析

1,646 阅读1分钟

Vue2.0 版本

注意: 我们这里说的版本,不是 Vue 发布的版本,而是 Vue 的打包编译之后的版本。我们都知道 Vue 2+ 是使用 rollup 打包的,Vue 是分为:

  • 能编译模板的版本 entry-runtime-with-compiler
  • 只有运行时的版本 entry-runtime

为什么选用 运行时版本

entry-runtime-with-compiler 内部重写挂载函数 $mount, 原因是 compiler 版本是针对 Vueoptions 中含有 template 字段进行单独处理的。含有 template 字段就需要单独的用 Vue compiler 进行编译。我们一般在单文件内部写 template 不会使用 option 的 template 选项,所以直接使用运行时版本即可

暴露的 Vue 函数

从源码看 Vue 本身是一个 函数, 它在 vue/src/core/instance/index.js 目录下

// code

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

// code
export default Vue

定义了 Vue构造函数(即:ES5 中的类) 之后,执行混入:

  • 初始化
  • 状态混入
  • 事件混入
  • 生命周期混入
  • 渲染函数混入

导入 Vue

导入 Vue 后,JS 就开始创建 Vue 的运行时:

  • 定义一个构造函数 Vue(类)
  • 创建 Vue 总的运行时

运行时:initMixin

创建了 Vue.prototype._init 方法, 这方法将在 Vue 实例化后运行的,也就是说运行时是以 new Vue 实例为界限,我们写代码其实是 Vue 内部的,不是 Vue 外部的。Vue 外部的是 new Vue创建的实例。这是典型的面向对象编程思想。

initEvents 中 target.$on(event, fn) 为什么能访问 $on ?

initEvents 重要是初始化的是:listeners, 此时 实例上还没有 $on,但也不会影响运行,因为这些时间不会被触发。

这里可以很好的理解 $listeners api 的使用方法.

运行时:initRender

在 vm 实例上:

  • _vnode
  • _staticTrees
  • $slots
  • $scopedSlots
  • _c
  • $createElement

重要点就是创建以上的 vm 实例方法和属性, 其中 $createElement 是创建元素和组件,它的返回值是创建的 vnode 节点,到从位置,我们接触到的还都是 vnode

运行时:callHook, beforeCreate

vm._hasHookEvent = false, vm.$emit 没有定义,这个明显不会触发

pushTarget() 没有传入 target, 这里要补充 Class 的 target 属性

运行时:initInjections

defineReactive 的处理 inject 选项

运行时:initState

  • initProps
  • initMethods
  • initData/ observe(data ={})
  • initComputed
  • initWatch

$mount

挂载函数 Vue.prototype.$mount 在,/vue@2.6.9/src/platforms/web/runtime/index.js 中定义

/* @flow */

import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'

import {
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
  setTimeout(() => {
    if (config.devtools) {
      if (devtools) {
        devtools.emit('init', Vue)
      } else if (
        process.env.NODE_ENV !== 'production' &&
        process.env.NODE_ENV !== 'test'
      ) {
        console[console.info ? 'info' : 'log'](
          'Download the Vue Devtools extension for a better development experience:\n' +
          'https://github.com/vuejs/vue-devtools'
        )
      }
    }
    if (process.env.NODE_ENV !== 'production' &&
      process.env.NODE_ENV !== 'test' &&
      config.productionTip !== false &&
      typeof console !== 'undefined'
    ) {
      console[console.info ? 'info' : 'log'](
        `You are running Vue in development mode.\n` +
        `Make sure to turn on production mode when deploying for production.\n` +
        `See more tips at https://vuejs.org/guide/deployment.html`
      )
    }
  }, 0)
}

export default Vue
  • mountedComponent
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    // coes
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    // codes
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
  • _update
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
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

本质上是调用了 __patch__ 方法

Vue.prototype.__patch__ = inBrowser ? patch : noop

// path
export const patch: Function = createPatchFunction({ nodeOps, modules })
  • createPatchFunction 返回一个 patch 函数,createPatchFunction 本身是一个极为复杂的函数,设计到虚拟dom,diff 算法等等。。