Vue 源码分析(一)引入vue后做了些什么?

208 阅读4分钟

在一般情况下,我们在构建vue项目时,都是一上来就直接new Vue(...),但是实际上,在你引入vue之后,一些有趣的事情就已经发生了......让我们一起来了解一下。
在阅读本篇文章前,你需要先了解的一些js基础知识有:js原型、修改对象属性默认特性的方法defineProperty(defineProperties)、bind、es6相关知识等......
我这里拿的是node_modules下的vue包,先看一下项目的目录结构:
src
|──compiler(编译vue模板)
|──core(vue核心逻辑)
|──platforms(vue跨平台的逻辑)
|──server(vue服务端渲染)
|──sfc(vue单文件组件 Single-File Component )
|——shared(vue的一些公共方法和常量)

这里先知道有这么几个文件夹就行了,先混个眼熟。然后我们开始吧......

我们常用的是vue入口文件的位置是:src/platforms/web/entry-runtime.js 里面啥逻辑都没写......除了导出一个从./runtime/index中引入的Vue。可以忽略。
那我们就看下./runtime/index.js中写了啥。
第一行:
import Vue from 'core/index'
好家伙。又引入了一个从core/index中导出的Vue。有点绕啊,不过问题不大。直接进去看看写了啥。
第一行:
import Vue from './instance/index'
又来!行吧。再看一下。进入'./instance/index'文件。 终于找到Vue构造定义的位置了。先整个鸡腿奖励下自己。

开始正题!

先贴一下src/core/instance/index.js下的入口源码

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'

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

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue


这里先是声明一个Vue的构造函数,构造函数的逻辑超级简单判断一下是不是生产环境或者是不是new构造函数的方式(window环境下可以直接set NODE_ENV=development将环境变量改成developoment或者production),然后就是传入用户输入的options,执行原型上的_init方法.这里的options一般就是我们用脚手架生成的main.js中传入的参数,像下面的这个数据结构一样:
{
    router,
    store,
    render:h=>(App)
}

当然这里只是声明Vue的构造函数,在我们没有使用new关键字调用构造函数前,this._init(options)方法 自然也不会执行。

继续往下面看......
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

这里依次执行了5个核心的函数(很重要),接下来我们依次介绍一下每个函数内部都干了些什么。

initMixin(Vue)

还是先看一下源码

export function initMixin (Vue: Class<Component>) {
	Vue.prototype._init = function (options?: Object) {
       //...初始化逻辑
    }
}

initMixin方法

这里的initMixin方法本质是只是给Vue的原型对象定义了一个_init方法(就是new Vue()的时候需要执行的那个方法),将初始化的内容单独拆分出来独立成一个文件,也可以说是模块化的一种实现。这里删除了代码的具体实现部分,因为本章只谈引入Vue后发生了什么。后面的章节会一一介绍......
剩下的几个mixin方法也是类似的套路......

stateMixin(Vue)

export function stateMixin (Vue: Class<Component>) {
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function (newData: Object) {
    }
    propsDef.set = function () {
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      cb.call(vm, watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

可以看到stateMinx方法主要是给Vue的原型对象定义
$data属性---------值为this._data
$props属性--------值this._props
$set属性----------值为set
$delete属性----------值为del
$watch属性--------值为一个函数 需要注意的是$watcher执行后会new Wactcher实例,并且返回一个unwatchFn函数,解除监听。

eventsMixin

export function eventsMixin (Vue: Class<Component>) {
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
       //绑定事件逻辑
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
      //一次性绑定事件逻辑
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    //解除绑定事件逻辑
  }

  Vue.prototype.$emit = function (event: string): Component {
     //触发事件逻辑
  }
}

给Vue原型定义$on、$once、$off、$emit四个事件相关的函数

lifecycleMixin

export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
   //讲虚拟dom更新成实际dom
  }
  Vue.prototype.$forceUpdate = function () {
     //强制页面刷新
  }
  Vue.prototype.$destroy = function () {
    //摧毁组件
  }
}

给Vue原型对象定义_update、$forceUpdate、$destory函数

renderMixin

export function renderMixin (Vue: Class<Component>) {
  installRenderHelpers(Vue.prototype)
  Vue.prototype.$nextTick = function (fn: Function) {
    //将代码添加到异步队列
  }
  Vue.prototype._render = function (): VNode {
    //生成虚拟dom
  }
}

function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}

给Vue原型对象定义$nextTick、_render函数以及一些运行时的辅助方法
当src/core/instance/index.js中的代码执行完毕后,我们又回到了core/index中,现在来看看这里面干了啥......

贴一下src/core/index.js的源码

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

initGlobalAPI

看下源码

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
  //添加options属性  
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  //标识一些基础构造器
  Vue.options._base = Vue
  //注册一个全局的抽象组件keep-alive   
  extend(Vue.options.components, builtInComponents)

  initUse(Vue) //添加Vue.use方法
  initMixin(Vue)//添加Vue.mixin方法
  initExtend(Vue)//添加Vue.extend方法
  initAssetRegisters(Vue)//添加注册组件 指令 过滤器的方法
}

相对于前一个core/instance/index的代码逻辑,这里主要是给Vue构造函数上添加各种属性和方法;

好了经过这一番操作下来。Vue会变成什么样子?如果你对前面的内容记得不清楚,可以直接看下面的截图:

SAV~WEAYE6)471@%ONYH2.png

再看一眼Vue原型上有哪些新鲜的玩意。

SBLTCX)@JI3VG043}BM}2.png

最后我们回到最初的'./runtime/index.js'

贴一下'./runtime/index.js'的源码

// 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)
}

这里主要是给在Vue.options上定义了两个默认指令v-show和v-model,注册两个默认的组件Transition、TransitionGroup。然后给原型对象添加一个__patch__和$mount方法。 看下代码结果:

1__)40)~YGELAH{$O}I%GGK.png

}%[JH]VZDT33X7Q%T$E}C%C.png

总结

可以很清楚的看到在你new Vue实例前,Vue已经在自己的构造函数和原型对象上添加了很多的方法和属性。至于这些方法和属性有什么用。我们后面再一一说。