前言
隔离在家没办法出去浪,那就挑战一下源码吧。本篇是我学习Vue源码的记录,可能有错漏,有不足,甚至半途而废,但是无论怎么样,既然开始学习了,那就留下一些痕迹。不足,错漏欢迎指正。
简单的介绍下我的研究对象,当前版本2.6.11。为了在编译期尽早发现由类型错误引起的bug,又不影响代码正常运行(不需要运行时动态检查类型)的情况下,Vue用Flow做静态类型检查,所以在阅读源码前最好先了解一下关于Flow的相关知识。Flow与TypeScript功能基本一致,熟悉了解TS的大致可以忽略。至于为什么选择Flow而不是TS,大致是由于作者重构时,Babel 和 ESLint 都有对应的Flow插件以支持语法,可以完全沿用现有的构建配置,改动成本较小。
Vue源码目录
阅读一个项目源码,尤其是Vue这类项目,我们很容易就不知道从哪里入手。既然这样,对于NPM托管的项目,都会有一个package.json文件,那我们就从它开始,这或许也是一种杀熟吧。
项目通常会配置script字段作为 NPM 的执行脚本,那我们就来看一看这部分吧。
//代码有点长,我们先截取一部分
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
"dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
"dev:test": "karma start test/unit/karma.dev.config.js",
"dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
}
由图可知,当我们运行npm run dev的时候,会执行scripts里面"dev",翻译一下就是用rollup打包JS模块,-w是watch,-c指定配置文件scripts/config.js,将TARGET设置为web-full-dev,那我们就看下TARGET在config.js文件中是如何设置的。
const builds = {
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
}
}
从上图中,我们可以看出文件的入口是'web/entry-runtime-with-compiler.js'文件,然而,在我们寻找这个文件的时候却发现找不到相关目录。怎么办!怎么办!!还能怎么办,继续看源码发现:entry: resolve('web/entry-runtime-with-compiler.js'),顺藤摸瓜研究resolve方法。(resolve:scripts/config.js)
const aliases = require('./alias')
const resolve = p => {
const base = p.split('/')[0]
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
return path.resolve(__dirname, '../', p)
}
}
从resolve方法中,我们发现并打开了alias.js
const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
sfc: resolve('src/sfc')
}
在这里,我们找到了web的路径,查看entry-runtime-with-compiler.js,我们终于找到了Vue的路径。
import Vue from './runtime/index'
查看文件,发现并不是我们所想的那样,但是我们在这个文件中发现了另一个Vue的引入
import Vue from 'core/index'
我们继续查看core/index.js
import Vue from './instance/index' //引入Vue实例
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue) //初始化全局接口
/* 以下主要是给Vue添加属性或方法的
* Vue.prototype.$isServer,Vue.prototype.$ssrContext,Vue.FunctionalRenderContext
* 用ES5的Object.defineProperty()定义只读的(无set)且不可枚举(enumurable默认false)的属性或方法
*/
Object.defineProperty(Vue.prototype, '$isServer', {
//$isServer属性代理了core/util/env中的isServerRendering
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
return this.$vnode && this.$vnode.ssrContext
}
})
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
//Vue版本
Vue.version = '__VERSION__'
//导出Vue
export default Vue
在这里,我们终于看到了期待已久的Vue核心代码。这篇js中最重要的是:import Vue from './instance/index' (引入Vue实例)和initGlobalAPI(Vue) (初始化全局接口)。下面我们也将着重描述这两点,这也将是本篇的重点。
1、引入Vue实例:src/core/instance/index
import { initMixin } from './init'
//构造函数,当new Vue(options) 会自动执行这个函数
function Vue (options) {
//如果不是生产环境,且不是通过关键字new来创建对象,就在控制台打印一个warning
//由此可知Vue只能通过 new 关键字初始化,然后调用 this._init 方法
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
//调用_init()方法
this._init(options)
}
//方法实例化
initMixin(Vue)// 在Vue的原型上添加了_init方法。在执行new Vue()的时候,this._init(options)被执行
stateMixin(Vue)// 在vue的原型上定义了属性: $data、$props,方法:$set、$delete、$watch
eventsMixin(Vue)// 在原型上添加了四个方法: $on $once $off $emit
lifecycleMixin(Vue)// 在Vue.prototye上添加了三个方法:_update $forceUpdate $destory
renderMixin(Vue)// 在原型上添加了方法:$nextTick _render _o _n _s _l _t _q _i _m _f _k _b _v _e _u _g _d _p
//导出Vue
export default Vue
-
this._init()方法:src/core/instance/init.js
import ...
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
//if 语句块,在istanbul.js计算覆盖率的时候会被忽略
/* 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)
}
//避免被观察到标志
vm._isVue = true
// merge options
if (options && options._isComponent) {
// 优化内部组件实例化
// 因为动态选项合并非常慢,而且没有内部组件选项需要特殊处理
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
vm._self = vm
//初始化生命周期、事件中心、渲染等等
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) //对props、methods、data、computed和wathcer等属性做了初始化操作
initProvide(vm) // resolve provide after data/props
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)
}
//检测是否有el属性,如果有,则调用vm.$mount方法挂载vm,挂载的目标就是把模板渲染成最终的DOM
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
-
$mount:src/platform/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
//mount缓存了原型上的$mount方法
const mount = Vue.prototype.$mount
//重新定义$mount方法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
//Vue 不能挂载在body、documentElement这样的根节点上
/* 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
}
const options = this.$options
//如果没有render方法,则会把el或者template字符串转换成render方法
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)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
//调用compileToFunctions将template转换成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')
}
}
}
//调用缓存了原型上$mount方法的mount挂载
return mount.call(this, el, hydrating)
}
备注:在 Vue 2.0 版本中,所有Vue组件的渲染最终都需要render
方法
-
mount缓存的原型上的$mount方法:src/platform/web/runtime/index.js
import { mountComponent } from 'core/instance/lifecycle'
Vue.prototype.$mount = function (
el?: string | Element, //el:挂载的元素,可以是字符串,也可以是DOM对像
hydrating?: boolean //hydrating:服务端渲染相关,在浏览器环境下不需要传第二个参数
): Component {
//el是字符串且在浏览器环境下会调用query()方法转换成DOM对象
el = el && inBrowser ? query(el) : undefined
//调用mountComponent方法
return mountComponent(this, el, hydrating)
}
-
mountComponent:src/core/instance/lifecycle.js
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
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
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)
//调用vm._render方法生成虚拟Node
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
//调用vm._update更新DOM
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
//实例化Watcher,在它的回调函数中会调用updateComponent()方法
//Watcher在初始化的时候会执行回调函数,或者是当vm实例监测到数据发生变化的时候执行回调函数
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
//vm.$vnode:Vue实例的父虚拟Node,当它为Null时,节点是根节点
if (vm.$vnode == null) {
//vm._isMounted为true,表示这个实例已经挂载
vm._isMounted = true
//执行mounted钩子函数
callHook(vm, 'mounted')
}
return vm
}
-
_render:src/core/instance/render.js
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let 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 = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
从render()
函数中的可以看出createElement
方法就是vm.$createElement
方法
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
由此可以看出:vm._render()是
通过执行createElement()
方法,返回vnode
-
createElement:src/core/vdom/create-elemenet.js
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
//createElement()方法实际上是对_createElement()方法的封装,它允许传入的参数更加灵活。最后调用_createElement()
return _createElement(context, tag, data, children, normalizationType)
}
_createElement
export function _createElement (
context: Component, //VNode 的上下文环境
tag?: string | Class<Component> | Function | Object, //标签
data?: VNodeData, //VNode 的数据
children?: any, //当前 VNode 的子节点
normalizationType?: number //子节点规范的类型
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
//根据normalizationType的类型,将children规范成不同的VNode类型。
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
//创建VNode
let vnode, ns
//如果tag是string类型
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
//如果是内置的一些节点,则直接创建一个普通VNode
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { //如是已注册的组件名,则通过createComponent()创建组件类型的VNode
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
//创建未知的VNode标签
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
//如果tag是Component类型,则直接调用createComponent创建组件类型的VNode节点
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
-
_update:src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// 首次渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 更新渲染
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// 更新 __vue__
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
由上可以看出_update方法的核心就是调用 vm.__patch__ 方法。这个方法在不同平台上的定义是不同的,此处我们取web平台上的定义,它在 src/platforms/web/runtime/index.js
/*
* 判断当前环境是否在浏览器环境中,是否存在Window对象
* 如果是在server环境下,就是一个空函数,因为在服务端渲染中,没有真实浏览器的DOM环境,所以不需要把VNode最终转换成DOM
* 这样做是为了方便跨平台的处理
*/
Vue.prototype.__patch__ = inBrowser ? patch : noop
由上可知,在浏览器环境下__patch__调用了patch方法。path:src/platforms/web/runtime/patch.js
//path=createPatchFunction()方法的返回值
//nodeOps:封装了一系列DOM操作的方法
//modules:定义了一些模块的钩子函数的实现
export const patch: Function = createPatchFunction({ nodeOps, modules })
createPatchFunction:src/core/vdom/patch.js
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
// Vue定义了一系列的辅助方法...
//返回patch方法
return function patch (oldVnode, vnode, hydrating, removeOnly) {
/*
* oldVnode:旧的VNode节点,可以不存在或是一个DOM对象
* vnode:执行_render后返回的VNode的节点
* hydrating:是否服务端渲染,非服务端渲染情况下为false
* removeOnly:给transition-group用,非服务端渲染情况下为false
*/
if (isUndef(vnode)) {//当前VNode未定义、老的VNode定义了,调用销毁钩子
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {//老的VNode未定义,初始化。
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {//当前VNode和老VNode都定义了,执行更新操作
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 修改已有根节点 patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
} else {
//已有真实DOM元素,处理oldVnode
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
//当oldVnode是服务端渲染的元素,hydrating记为true
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
//调用insert钩子函数
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
//如果不是服务端渲染或者合并到真实DOM失败,则创建一个空的oldVnode节点替换它
oldVnode = emptyNodeAt(oldVnode)
}
//替换已有元素
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// 创建新节点
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// 递归更新父级占位节点元素
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
//销毁旧节点
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
//调用insert钩子
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
2、initGlobalAPI:初始化全局接口
//主要作用就是在Vue上扩展一些全局定义的方法,在Vue的官方文档中的关于全局API的内容都在这
import ...
export function initGlobalAPI (Vue: GlobalAPI) {
const configDef = {}
configDef.get = () => config
//在非生产环境下修改了配置文件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)
//Vue.util暴露的方法最好不要依赖,因为它可能经常会发生变化,是不稳定的。
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
//
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)
//给vue 创建 ASSET_TYPES 的 空对象
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)
//Vue的静态方法
initUse(Vue) //Vue.use()
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}