3. 「vue@2.6.11 源码分析」vue.js 首次执行做了哪些事情

我心飞翔 分类:vue

我们都知道页面渲染从new Vue开始,但是实际上代码在这之前先注册了Vue构造函数和各种能力才能保证new Vue的正常运作。从开发者角度看,可以看到Vue的大致全貌,其暴露了哪些东西。

注意:下面代码分析路径是按照构建入口按照引用关系倒着分析的,实际脚本的执顺序和下面分析顺序是相反的

// src/platforms/web/entry-runtime-with-compiler.js: 构建入口(runtime + compile version)
import Vue from './runtime/index'

// src/platforms/web/runtime/index.js
import Vue from 'core/index'

// src/core/index.js
import Vue from './instance/index' // Vue类函数的定义

js文件加载过程

构建入口

从构建入口开始(runtime + compile version)

  1. 通常我们使用vue-loader + webpack,在.vue文件中编写代码,这种情况构建vue文件时会将template直接转成 render 函数
  2. 下面构建版本是带有模板编译能力的运行时,可以在运行时进行模板编译 ```js // src/platforms/web/entry-runtime-with-compiler.js

import Vue from './runtime/index'

const mount = Vue.prototype.$mount Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean ): Component { // 如果开发者没有提供 render,则将从 el -> render 函数 if (!options.render) { //... 核心逻辑,通过el获取 template(innerHTML\outerHTML),在调用模板编译方法 compileToFunctions 将 template 转为 render 函数 } return mount.call(this, el, hydrating) // hydrating 和 ssr 相关,后面不介绍该场景。 }

Vue.compile = compileToFunctions // Vue.compile,用户可以手动调用该方法在运行时进行模板构建 export default Vue

该文件主要通过拦截 Vue.prototype.$mount 方法添加运行时编译模板能力。

## 运行时构建入口
src/platforms/web/runtime/index.js

```js
// src/platforms/web/runtime/index.js

import Vue from 'core/index'
import { mountComponent } from 'core/instance/lifecycle'
import { patch } from './patch' // 

Vue.config.xxx = xxx // 都有哪些作用呢❓

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives) // 内置命令:model、show
extend(Vue.options.components, platformComponents) // 内置组件:Transition、TransitionGroup

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop // 从vnode到dom的核心方法

// public mount method
Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean): Component {
  el = el && inBrowser ? query(el) : undefined // 转成dom
  return mountComponent(this, el, hydrating) // 组件渲染的入口方法
}

export default Vue

src/core/vdom/patch.js

// src/core/vdom/patch.js
import { createPatchFunction } from 'core/vdom/patch'
export const patch: Function = createPatchFunction({ nodeOps, modules })

core

src/core/index.js

看到在 src/core下,说明是平台复用的,并且也是vue运行时的核心入口。静态方法(全局方法)和实例方法分开定义,静态方法放在initGlobalApi中定义,Vue类函数放在 core/instance/index中定义

// src/core/index.js
// 定义Vue构造函数何其实例方法(定义在原型上)
import Vue from './instance/index'

initGlobalAPI(Vue) // 静态方法和选项的定义

export default Vue
  1. Vue构造函数何其实例方法(定义在原型上)
  2. 静态方法和选项的定义

Vue类和实例方法定义

Vue构造函数定义

// src/core/instance/index.js

function Vue (options) {
  this._init(options)
}

initMixin(Vue) // Vue.prototype._init
stateMixin(Vue) // Vue.prototype.$data | $props | $set | $get | $watch
eventsMixin(Vue) // Vue.prototype.$on | $once | $off | $emit
lifecycleMixin(Vue) // Vue.prototype._update | $forceUpdate | $destroy

// Vue.prototype.$nextTick | _render,
// 另外这里提供了 render 函数中可能会用到的一些工具函数等
// (见 installRenderHelpers 方法,如_v = createTextVNode、_n = toNmber等等 )
renderMixin(Vue) 

export default Vue

实例方法定义

Vue.prototype

  1. 初始化入口: _init
  2. 状态相关:data | props | set | get |
  3. 事件相关:on | once | off | emit
  4. 生命周期相关: _update | forceUpdate | destroy
  5. 渲染相关:$nextTick | _render

其中 下划线开头的方法是实例私有方法(_init_update_render),不允许外部调用,这三个私有方法也是组件实例化过程的三个核心步骤

静态方法(Vue.xxx)、选项(Vue.options.xxx)

initGlobalApi()

// src/core/global-api/index.js

import { observe } from 'core/observer/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config 
  Object.defineProperty(Vue, 'config', configDef) // 1. Vue.config

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // 2. Vue.util,其中 extend(to, from): 合并from对象的键值到to对象上
  Vue.util = { warn, extend, mergeOptions, defineReactive } 

  // 3. Vue.set/get./nextTick
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 4. Vue.observable
  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => { 
    observe(obj)
    return obj
  }

  // 5. Vue.options
  Vue.options = Object.create(null) // 【关键】:创建 Vue.options
  ASSET_TYPES.forEach(type => { // ASSET_TYPES: 'component', 'directive', 'filter' => Vue.options.components/.directives/.filters
    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(Vue.options.components, builtInComponents) // 添加内置组件:Keep-Alive 到 Vue.options.components

  initUse(Vue) // 6. Vue.use
  initMixin(Vue) // 7. 【关键】:Vue.mixin:通过mergeOptions将mixin和 Vue.options 进行策略合并
  initExtend(Vue) // 8. 【关键】: Vue.extend: 原型继承Vue类,返回一个构造函数Sub,核心点是合并子组件的options的处理 => 相当于每个组件都会生成一个构造函数,并且有一个cid标识
  initAssetRegisters(Vue) // 9. 【关键】*:全局组件、全局指令、全局过滤器的注册:Vue.component、Vue.filter、Vue.directive,全局注册的对象如组件会被保存到 Vue.options.component**s**/filters/directives 中
}

这里的核心有两类

  1. 全局API
    1. Vue.config
    2. Vue.set、Vue.get、Vue.nextTick
    3. Vue.use
    4. Vue.mixin:策略合并 Vue.options 和 mixin 中的选项
    5. Vue.extend:开发者提供选项,通过原型继承来生成组件的构造函数,
    6. Vue.component、Vue.componentfilter、Vue.componentdirective:如注册的全局组件会被保存到 Vue.options.components中
  2. 全局选项:Vue.options
    1. Vue.options.components、Vue.options.filters、Vue.options.directives
    2. Vue.options._base = Vue

小结

  1. 定义了 Vue 类函数 + 挂载类实例方法
  2. 挂载类全局Api和全局Options,API - Vue.js

另外,看到有部分内容放在platforms下提供,如Vue.prototype.$mount,这是因为和平台和构建版本有关

回复

我来回复
  • 暂无回复内容