# 了解vue

网上对vue源码的分析很多,大致了解下,先宏观再微观 阅读源码前需了解的有

  • es6
  • Rollup(vue采用的构建工具,打包库适用)
  • flow(类型检查,和typescript类似)
├── src ----------------------------------- 这个是最应该关注的目录,包含了源码
│   ├── compiler -------------------------- 编译器代码的存放目录,将 template 编译为 render 函数
│   ├── core ------------------------------ 存放通用的,与平台无关的代码
│   │   ├── observer ---------------------- 响应系统,包含数据观测的核心代码
│   │   ├── vdom -------------------------- 包含虚拟DOM创建(creation)和打补丁(patching)的代码
│   │   ├── instance ---------------------- 包含Vue构造函数设计相关的代码
│   │   ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码
│   │   ├── components -------------------- 包含抽象出来的通用组件
│   ├── server ---------------------------- 包含服务端渲染(server-side rendering)的相关代码
│   ├── platforms ------------------------- 包含平台特有的相关代码,不同平台的不同构建的入口文件也在这里
│   │   ├── web --------------------------- web平台
│   │   │   ├── entry-runtime.js ---------- 运行时构建的入口,不包含模板(template)到render函数的编译器,所以不支持 `template` 选项,我们使用vue默认导出的就是这个运行时的版本。大家使用的时候要注意
│   │   │   ├── entry-runtime-with-compiler.js -- 独立构建版本的入口,它在 entry-runtime 的基础上添加了模板(template)到render函数的编译器
│   │   │   ├── entry-compiler.js --------- vue-template-compiler 包的入口文件
│   │   │   ├── entry-server-renderer.js -- vue-server-renderer 包的入口文件
│   │   │   ├── entry-server-basic-renderer.js -- 输出 packages/vue-server-renderer/basic.js 文件
│   │   ├── weex -------------------------- 混合应用
│   ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包
│   ├── shared ---------------------------- 包含整个代码库通用的代码

构建相关=> 运行时版 + Compiler = 完整版,compiler将字符串模板编译为 render 函数

# 构造函数

打开./instance/index.js

// 从五个文件导入五个方法(不包括 warn)
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'

// 定义 Vue 构造函数
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)
}
// 将 Vue 作为参数传递给导入的五个方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// 导出 Vue
export default Vue

vue实际上就是一个用 Function 实现的类,那为什么不用Class实现呢,后面有很多 xxxMixin 的函数调用,并把 Vue 当参数传入,它们的功能都是给 Vue 的 prototype 上扩展一些方法,Vue 按功能把这些扩展分散到多个模块中去实现,而不是在一个模块里实现所有,这种方式是用 Class 难以实现的。这么做的好处是非常方便代码的维护和管理

先看initMixin

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    // ... _init 方法的函数体,
  }
}
// 添加_init内部初始化方法 在上面构造函数上有执行

再看stateMixin

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) {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
//设置set让 $data,$props只读
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  //添加$set $delete
  Vue.prototype.$set = set
  Vue.prototype.$delete = del
//添加$watch
  Vue.prototype.$watch = function {
    //$watch 函数体
  }
}

接下来是eventsMixin

//添加4个事件方法
export function eventsMixin (Vue: Class<Component>) {
  Vue.prototype.$on = function {
   //$on
  }
  Vue.prototype.$once = function  {
    //$once
  }
  Vue.prototype.$off = function {
    //$off
  }
  Vue.prototype.$emit = function{
    // $emit
  }
}

下一个是lifecycleMixin

//添加3个方法
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function{}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}
}

最后一个renderMixin

  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)
  //$nextTick
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }
  Vue.prototype._render = function (): VNode{}
  //在vue原型添加一系列方法
  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
    }

# vue构造函数的静态方法属性(全局api)

打开core/index.js

//Vue 的出生文件导入 Vue
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
//ssr相关
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
})
//存储了当前 Vue 的版本号
Vue.version = '__VERSION__'

export default Vue

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.'
      )
    }
  }
  //加config
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // 以及 util 下的四个方法都不被认为是公共API的一部分,要避免依赖他们,
  //但是你依然可以使用,只不过风险你要自己控制
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  Vue.options = Object.create(null)//空对象
  //ASSET_TYPES=['component',  'directive', 'filter']
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue
//extend来自于shared/util.js,builtInComponents 的属性混合到 Vue.options.components
//builtInComponents 代码
//import KeepAlive from './keep-alive'
// export default {
//   KeepAlive
// }
  extend(Vue.options.components, builtInComponents)
//到这options
// Vue.options = {
// 	components: {
// 		KeepAlive
// 	},
// 	directives: Object.create(null),
// 	filters: Object.create(null),
// 	_base: Vue
// }
//use方法,安装插件
    function initUse (Vue: GlobalAPI) {
    Vue.use = function (plugin: Function | Object) {
        // ...
        }
    }
  initUse(Vue)

function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
    //合并options
    this.options = mergeOptions(this.options, mixin)
    return this
    }
}
  initMixin(Vue)
  function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1
  //Class inheritance
  Vue.extend = function (extendOptions: Object): Function {
    // ...
  }
  //添加cid extend
}
  initExtend(Vue)
  function initAssetRegisters (Vue: GlobalAPI) {
 //Create asset registration methods.
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // ......
    }
  })
}
//vue又多三个方法component,directive,filter
  initAssetRegisters(Vue)
}

# 平台化

Vue是一个 Multi-platform 的项目(web和weex),不同平台可能会内置不同的组件、指令,或者一些平台特有的功能,主要看web平台
原来的config在core/config.js

Vue.config = {
  optionMergeStrategies: Object.create(null),
  silent: false,
  productionTip: process.env.NODE_ENV !== 'production',
  devtools: process.env.NODE_ENV !== 'production',
  performance: false,
  errorHandler: null,
  warnHandler: null,
  ignoredElements: [],
  keyCodes: Object.create(null),
  isReservedTag: no,
  isReservedAttr: no,
  isUnknownElement: no,
  getTagNamespace: noop,
  parsePlatformTagName: identity,
  mustUseProp: no,
  _lifecycleHooks: LIFECYCLE_HOOKS
}

打开platforms/web/runtime/index.js

// 覆盖初始化的config
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
//安装平台指令和组件
    platformDirectives={
        model,
        show
    }
extend(Vue.options.directives, platformDirectives)
    platformComponents={
        Transition,
        TransitionGroup
    }
extend(Vue.options.components, platformComponents)
//vue.options变为
Vue.options = {
	components: {
		KeepAlive,
		Transition,
		TransitionGroup
	},
	directives: {
		model,
		show
	},
	filters: Object.create(null),
	_base: Vue
}
//是浏览器__patch__为patch方法,否则为空函数noop
Vue.prototype.__patch__ = inBrowser ? patch : noop

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

# with compiler

运行版本已经成型,打开entry-runtime

import Vue from './runtime/index'
export default Vue

完整版的 Vue,入口文件是 entry-runtime-with-compiler.js

// 导入 运行时 的 Vue
import Vue from './runtime/index'
// ... 其他 import 语句

// 从 ./compiler/index.js 文件导入 compileToFunctions
import { compileToFunctions } from './compiler/index'

// 根据 id 获取元素的 innerHTML 
const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

// 使用 mount 变量缓存 Vue.prototype.$mount 方法
const mount = Vue.prototype.$mount
// 重写 Vue.prototype.$mount 方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // ... 函数体省略
}

/**
 * 获取元素的 outerHTML
 */
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
  }
}

// 在 Vue 上添加一个全局API `Vue.compile` 其值为上面导入进来的 compileToFunctions
Vue.compile = compileToFunctions

// 导出 Vue
export default Vue