likes
comments
collection
share

Vue2 源码分析(一): 入口platforms/web/runtime/index.js & initGlobalAPI

作者站长头像
站长
· 阅读数 28

杂记:

Vue 采用Flow 做了静态类型检查,Flow 是类似 Typescript 的静态类型检查工具
Vue 2.6.14版本用了rollup 打包

根据打包配置,我们仅仅看web 端的源码,入口文件在 src/platforms/web/ 下

Vue 以 '_' 开头的变量不会追踪变化

拉取到vue源码后,执行yarn build, 在script/build 下打印所有打包列表:

Vue2 源码分析(一): 入口platforms/web/runtime/index.js & initGlobalAPI

从打包脚本可以一步一步找到入口文件,我们从 platforms/web/runtime/index.js下分析

入口文件:在Vue原型上绑定了$mount 方法,$mount的具体实现是我们需要取关注的,后续会讲,这里记住是在入口初始化的就好

/* @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, isChrome } 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 */
Vue.nextTick(() => {
  if (config.devtools) {
    if (devtools) {
      devtools.emit('init', Vue)
    } else if (process.env.NODE_ENV !== 'production' && isChrome) {
      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' &&
    config.productionTip !== false &&
    inBrowser && 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

核心代码入口: core/index.js

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'

initGlobalAPI(Vue)

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

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

Vue.version = '__VERSION__'

export default Vue

引入Vue后,调用了initGlobalAPI(Vue),我们先看initGlobalAPI

/* @flow */ 
// core/global-api/index.js

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

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

  Vue.options = Object.create(null)
  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(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

initGlobalAPI:

1、在Vue 原型上初始化了config 和 util,可以直接打印Vue.config和Vue.util 查看

  • config 就是定义了一些会全局用到的配置
  • util 导出了四个方法:

见下图打印:

Vue2 源码分析(一): 入口platforms/web/runtime/index.js & initGlobalAPI

2、在Vue 原型上定义了 set, del, nextTick 方法

3、在Vue 原型上初始化了options

4、调用 initUse,在Vue 原型上定义use 方法,主要用于vue 使用第三方插件 Vue.use()

5、调用 initMixin:

    在Vue 原型上定义mixin 方法,Vue 的extend 和 mixin 属性都会走这个方法去实现合并策略。
  • initMixin 中向Vue.config 上的 optionMergeStrategies 对象上添加了属性以及属性值属性一般就是合并的key,值就是对应该属性的合并策略;

  • 每种key 的合并策略:

    •       data,provide的合并策略一样: 将当前元素没有的属性合并赋值到data 对象上,如果当前元素有同名的data key,则被合并的会不生效;

    •       生命钩子函数会已数组的方式合并;

    •       filters, directivs, components 的合并策略比较复杂,在parent 的基础上合并了构造函数中的和当前子元素的; todo: 在mixin 中定义filter,在组件A 中定义同名filter且引用mixin, 按道理会以mixin 生效

    •       watch 会以数组的方式合并;

    •       props、methods、inject、computed 的合并方式是一样的,同名的以当前元素生效

6、调用initExtend ,通过类继承,实现Vue 组件间的继承逻辑

7、调用initAssertType 实现Vue.directive(),Vue.component(),Vue.filter() 方法

Vue2 源码分析(二)# Vue构造函数 & this._init

Vue2 源码分析(三)# 观察者模式 & 深入响应式原理

Vue2 源码分析(四): 深入响应式原理-demo篇

问题1:vue 源码中的cached 函数作用是什么,为什么要为纯函数创建版本