2. 初始化相关代码分析
2.1 initGlobalAPI(Vue) 初始化Vue的全局静态API
平时开发通过 new Vue({...}) 去创建了根实例,当然在此之前,Vue已经做了一些前期的准备工作。Vue 的核心代码都在 src/core 目录中,我们先来看看 core/index.js 这个入口文件,这部分代码逻辑很简单。
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'
// 初始化全局API
initGlobalAPI(Vue)
// 下面代码是服务端ssr渲染使用,web端可以忽略
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
})
// 添加 vue 版本号这个静态变量
Vue.version = '__VERSION__'
export default Vue
我们主要关注的 initGlobalAPI(Vue) 这个函数,它定义在 core/global-api/index.js 文件中,主要给构造函数,添加诸如 Vue.set/delete/use/mixin/extend/component/directive/filter 这些静态方法。
/* @flow */
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 { observe } from 'core/observer/index'
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
export function initGlobalAPI (Vue: GlobalAPI) {
// config
// 这个是给 Vue 设置的 config 属性,不要手动的去替换这个对象,
// 如果替换,vue 会给 warn 提示
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的静态方法: Vue.set/delete/nextTick
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// 用于标识 Weex 多实例场景中,通过“base”标识普通对象组件的构造函数。
// 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)
// Vue的静态方法: Vue.use/mixin/extend
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
// Vue的静态属性方法:Vue.component/directive/filter
initAssetRegisters(Vue)
}
其中 initAssetRegisters(Vue),通过静态变量数组 [ 'component', 'directive','filter'] 遍历创建了Vue.component/directive/filter 这三个静态属性方法。 静态变量配置在 src/shared/constants.js 文件中,方法定义在 core/global-api/assets.js 文件中。
export const SSR_ATTR = 'data-server-rendered'
// 注册全局API时候使用
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
// 生命周期函数使用
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
]
/* @flow */
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
// Vue.comoponent/directive/filter 静态方法的绑定
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
2.2 定义Vue构造函数、实例方法
Vue 这个构造函数,定义在 core/instance/index.js 文件中。从代码中可以看到,用工厂模式,执行不同的混入函数,对 Vue.prototype 原型进行加工,给实例添加对应的属性方法。
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')
}
// 构造函数中执行 Vue.prototype._init 方法
this._init(options)
}
// 实例初始化方法: Vue.prototype._init
initMixin(Vue)
// 实例数据状态相关方法: Vue.prototype.$data/$props/$set/$delete,$watch
stateMixin(Vue)
// 实例事件相关方法: Vue.prototype.$on/$once/$off/$emit
eventsMixin(Vue)
// 实例生命周期相关方法:Vue.prototype._update/$forceUpdate/$destory
lifecycleMixin(Vue)
// 实例渲染相关方法:Vue.prototype.$nextTick/_render
renderMixin(Vue)
export default Vue
2.3 new Vue(options)
执行 new Vue() 创建组件实例,同时 this._init(options) 初始化方法被执行,合并用户配置、初始化周期、事件、数据、属性等。
new Vue({
data: {...},
props: {...},
methods: {...},
computed: {...}
...
})
这部分处理逻辑在 core/instance/indexjs 文件中,与 _init() 相关的主要看 initMixin 这个函数。
/* @flow */
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options // 合并用户配置
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self // 抛出vue实例本身
vm._self = vm
// 初始化属性:vm.$parent/$root/$children/$refs
initLifecycle(vm)
// 初始化父组件传入的 _parentListeners 事件。
initEvents(vm)
// 初始化render相关:vm.$slot/scopedSlots/_c/$createElement
initRender(vm)
// 调用生命钩子 beforeCreate
callHook(vm, 'beforeCreate')
// 在data/props之前解析注入
initInjections(vm) // resolve injections before data/props
// 初始化相关用户配置的数据响应式:vm._props/_data, 以及computed、watch、methods
initState(vm)
// 在 data/props 之后提供数据
initProvide(vm) // resolve provide after data/props
// 调用生命钩子 created
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
......
2.4 执行 $mount 进行挂载
执行 mount执行挂载,目录是为了生成vnode,进而转换为真实DOM执行更新。mount 方法在 web 端相关两个 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js 构建文件中都有定义。我们这里分析 entry-runtime-with-compiler.js 带 compiler 版本的入口文件。关于 Vue scripts 脚本构建相关的内容,大家可以参考我之前写的 这篇文章 的第2章节。
entry-runtime-with-compiler.js 版本,是在 src/platform/web/runtime/index.js 版本的基础上,加 compiler 相关的功能逻辑。它首先保存 runtime 版本的 mount = Vue.prototype.mount方法。再重写Vue.prototype.mount 方法。如果用户传入 template 模板,就通过编译,转换成 render 函数。最后通过先前保存的 mount 方法进行挂载。下面我们在再来复习一下这个 $mount 实现逻辑。
......
// 1. 保存 runtime 版本 Vue.prototype 上的 $mount 方法
const mount = Vue.prototype.$mount
// 2. 重写 Vue.prototype 上的 $mount(加上 compiler 相关功能逻辑)
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
// 处理 options 配置
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
// 3. 存在 template 选项内容,就进行编译。
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
// 编译获取 render 函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
// 4. 编译结束,调用 runtime 版本的 $mount 方法进行挂载
return mount.call(this, el, hydrating)
}
......
最后,代码执行 mount.call(this, el, hydrating)。实际上复用了 runtime/index.js 中的定义的 $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 } 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
// 定义了公共的 $mount 方法
// 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 */
....
export default Vue
公共 $mount 方法实际上调用了 mountComponent 函数,它 core/instance/lifecycle.js 文件中定义,在mountComponent 函数中,实例化一个渲染Watcher,此时 Watcher 内部逻辑中调用定义的 updateComponent 函数。updateComponent 被调用, vm._render 执行生成 vnode,最终调用 _update 将 vnode 更新成 DOM,代码注释如下。
...
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
// 调用 beforeMount 钩子
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */ // web端可以忽略
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
// 定义updateComponent方法,渲染 watcher 内部会调用。
// 如果 updateComponent 被调用,render 方法先执行,生成 vnode。
// 最后执行 _update 方法,进行DOM更新,new Vue() 走的是创建DOM逻辑。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// 初始化渲染 watcher,内部逻辑会调用 updateComponent。
// 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
// 如果 vm.$vnode === null 当前 vm 的父 vnode 为null。
// 即判断 vm 当前实例为 Vue 的根实例.
// vm.$vnode 在上面的 updateChildComponent 方法中有的定义 vm.$vnode = parentVnode
// 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 // 标记该Vue根实例挂载结束
callHook(vm, 'mounted') // 执行钩子 mounted。
}
return vm
}
...
2.5 执行 _render 生成 vnode
vm._render 方法在之前的内容中有提到,它定义 instance/index.js 文件中,它是在 Vue 构造函数定义的时候,给Vue添加的实例方法。
具体逻辑在 src/core/instance/render.js 文件中。其他代码逻辑可以先不关注,主要关注,vnode = render.call(vm._renderProxy, vm.$createElement) 这部分调用。
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
// 给实例初始化render方法
Vue.prototype._render = function (): VNode {
...
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
// 调用用户定义 render 函数生成vnode
vnode = render.call(vm._renderProxy, vm.$createElement)
}
...
return vnode
}
}
render.call 执行,传入了 vm.createElement,这里就是用户可以通过手写render函数,用来生成vnode的实现。示例如下,其中h就是vm.createElement。
<div id="app">
{{title}}
</div>
<script>
window.app = new Vue({
data: {
title: 'vue render'
},
// 手写 render 函数,h === vm.$createElement
render(h) {
return h(
'div',
{
attrs: {
id: 'demo'
}
},
this.title
);
}
}).$mount('#app');
</script>
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com