前言
前段时间抽空研究了一下Vue源码,这是源码系列的第一篇,关于Vue源码的整体流程,接下来会不定期更新Vue源码系列。
从new Vue(options)开始说起 先来个最简单的例子
// main.js
import App from './App'
new Vue({
el: '#app',
router,
components: { App },
template: `<ul><app></app></ul>`
})
// App.vue
<template>
<div id="app">
</div>
</template>
开始执行new Vue(options)的_init函数
Vue.prototype._init = function (options) {
var vm = this;
if (options && options._isComponent) { // 这里是子组件vueComonent的_init过程
initInternalComponent(vm, options); // 这里就是生成子组件的vm.$options并添加属性
} else { // 这里是new Vue过程,生成Vue实例vm.$options并进行修饰
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// 接下来这里的init就是往vm或者vm.$options上添加属性
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
// 这里是new Vue时执行vm.$mount实现挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
这里有必要对initInternalComponent函数进行探究
function initInternalComponent (vm, options) {
// 第一句很重要,vm.$options继承了Sub.options(当前组件vm的构造函数),
// 所以当调用子组件的render函数的时候vm.$options.render就能访问到render函数了
var opts = vm.$options = Object.create(vm.constructor.options);
var parentVnode = options._parentVnode;
opts.parent = options.parent;
opts._parentVnode = parentVnode; // 这里生成vm.$options._parentVnode,
// 提供给后面执行vm._render时的vm.$vnode = _parentVnode;
var vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
// Sub.options
beforeCreate: (3) [ƒ, ƒ, ƒ]
beforeDestroy: [ƒ]
components: {HelloWorld: {…}}
data: ƒ data()
destroyed: [ƒ]
directives: {}
filters: {}
methods: {toogle: ƒ}
mounted: [ƒ]
name: "App"
props: {hobby: {…}, aaa: {…}}
render: ƒ () // 子组件会用到的render函数
staticRenderFns: []
watch: {hobby: ƒ}
_Ctor: {}
__file: "src/App.vue"
_base: ƒ Vue(options)
_compiled: true
__proto__: Object
解释完initInternalComponent函数(这是子组件才会进入的逻辑),应该开始挂载的步骤,执行vm.options.el)
Vue.prototype.$mount = function (el) {
el = el && query(el); // 将options.el转为真实的html节点
var options = this.$options;
// vm.$mount的这里是Vue实例,会看options是否有用户手写的render函数,没有进入if逻辑
if (!options.render) {
var 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');
}
var ref = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render; //生成vm.$options.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中用到的render会有三种情况:
// 1.如果是vm.$mount的vm指的是Vue实例,进入上面的生成render函数逻辑
// 2.Vue实例,用户手写的render函数
// 3.vueComponent的实例,import子组件时获取到的render函数。
return mount.call(this, el, hydrating) -> mountComponent(this, el, hydrating)
}
然后执行mountComponent函数
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
callHook(vm, 'beforeMount');
var updateComponent;
updateComponent = function () {
// new Watcher的时候执行,先执行作为参数的vm._render()函数。
// 生成vnode作为参数传入vm._update中。
vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
// 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;
callHook(vm, 'mounted'); // 这个时候执行mounted生命周期函数,前面的new Watcher中已经把节点生成并插入页面中了,所以这个是在节点渲染完毕后执行的,顺序是先子后父。
}
return vm
}
//vm._render()执行的就是这个函数,其实就是执行之前$mount中添加进去的vm.$options.render
//render函数是什么具体要看当前的vm是谁的实例。
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
// Vue实例中是没有_parentVnode的,所以为undefined,
// 而VueComponent实例中,_parentVnode就是组件vnode,占位符vnode。
// 在执行父组件的patch过程的时候,会执行cereateElm ->
// createComponent -> init -> createComponentInstanceForVnode生成子组件的vm实例时
// 会将当前的组件vnode赋值给_parentVnode传入子组件vm._init(options)中
vm.$vnode = _parentVnode;
// render self
var vnode;
currentRenderingInstance = vm;
// new Vue时render函数是这样的
// (function anonymous() {
// with(this){return _c('ul',[_c('app')],1)} // 会先执行参数_c('app'),再执行_c('ul')
// })
//new VueComponent时render函数是这样的
//var render = function() {
// var _vm = this
// var _h = _vm.$createElement
// var _c = _vm._self._c || _h
// return _c(
// "div",
// { attrs: { id: "app1" } },
// [
// _c("hello-world"),
// _vm._v(" "),
// _c("div", [_vm._v(_vm._s(_vm.hobby.ball))]),
// _vm._v(" "),
// _c("h1", { on: { click: _vm.toogle } }, [_vm._v("button")])
// ],
// 1
// )
//}
vnode = render.call(vm._renderProxy, vm.$createElement);
// set parent
vnode.parent = _parentVnode;
return vnode
};
执行render函数,会进入_c -> createElement -> _createElement
function _createElement (
context,
tag,
data,
children,
normalizationType
) {
if (typeof tag === 'string') {
var Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
// if逻辑是当tag是html节点的时候,也就是普通的节点,直接调用new Vnode生成vnode。
// else逻辑是tag是自定义节点,其实也就是一个引入的组件。先进入resolveAsset函数,
// resolveAsset函数的作用就是获取components里面的组件的内容,例如本例中的App,
// 返回的Ctor就是App.vue里面的内容。
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag);
}
}
}
VueComponent实例的render之后进入createComponent函数
function createComponent (
Ctor,
data,
context,
children,
tag
) {
var baseCtor = context.$options._base;
// 这一句很关键,生成vueComponent构造函数
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// 执行Ctor = baseCtor.extend(Ctor)进入如下函数
// Vue.extend = function (extendOptions) {
// extendOptions = extendOptions || {};
// var Super = this;
// var SuperId = Super.cid;
// var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
// if (cachedCtors[SuperId]) {
// return cachedCtors[SuperId]
// }
// var Sub = function VueComponent (options) {
// this._init(options);
// };
// Sub.prototype = Object.create(Super.prototype);
// Sub.prototype.constructor = Sub;
// Sub.cid = cid++;
// 将子组件App.vue里面的内容加入Sub构造函数的options属性里面,子组件的vm.$mount为什么能通过
// vm.$options访问到render,其实就是因为这里将render函数添加到Sub构造函数中。
// Sub.options = mergeOptions(
// Super.options,
// extendOptions
// );
// // 子组件的props和computed是在这里去init初始化的
// if (Sub.options.props) {
// initProps$1(Sub);
// }
// if (Sub.options.computed) {
// initComputed$1(Sub);
// }
// // 加入Vue构造函数中的属性
// Sub.extend = Super.extend;
// Sub.mixin = Super.mixin;
// Sub.use = Super.use;
// return Sub
// }
data = data || {};
// 获取传入子组件的props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
// install component management hooks onto the placeholder node
installComponentHooks(data);
// installComponentHooks之后data的值如下,之后执行patch的时候会调用i.init就会调用这里的hook.init(i = vnode.data)
// hook:
// destroy: ƒ destroy(vnode)
// init: ƒ init(vnode, hydrating)
// insert: ƒ insert(vnode)
// prepatch: ƒ prepatch(oldVnode, vnode)
// __proto__: Object
// on: undefined
// __proto__: Object
// return a placeholder vnode
var name = Ctor.options.name || tag;
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
return vnode
}
像这个例子中template = <ul><app></app></ul>,render函数是_c('ul',_c('app')),生成的组件vnode就会被当做children参数传入ul的vnode中。
执行完_render函数之后生成vnode,作为参数开始执行_update函数。
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el; // Vue实例vm.$el有值,就是new Vue(options)传入的options: {el: '#app'},
//vueComponent时,这里还没有值,会在下面patch时为vm.$el的赋值
var prevVnode = vm._vnode;
// function setActiveInstance(vm) {
// 将当前的vm与子组件vm构建父子关系,子组件vm.$options.parent = 当前vm(父)
// var prevActiveInstance = activeInstance;
// activeInstance = vm;
// 执行完patch执行此函数,将activeInstance还原回当前vm的父级vm,其实意思就是在进入patch
// 过程的时候需要当前的vm与子组件vm构建父子关系,patch结束后跳出回到当前vm的循环,
// activeInstance当然就是当前vm的父级vm
// return function () {
// activeInstance = prevActiveInstance;
// }
// }
var restoreActiveInstance = setActiveInstance(vm);
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
// 第一次的渲染过程都是没有prevVnode的,所以prevVnode存在应该发生在更新过程
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
restoreActiveInstance(); // 执行完patch调用
vm._vnode = vnode; // vm = new Vue()时,vnode是组件vnode,如果vm = new Sub()时,vnode是
// App.vue文件里面的template的内容
if (!prevVnode) {
// 初始化进入
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// 更新时进入
vm.$el = vm.__patch__(prevVnode, vnode);
}
};
function patch (oldVnode, vnode, hydrating, removeOnly) {
var insertedVnodeQueue = [];
if (isUndef(oldVnode)) {
// 没有oldVnode,说明此时是第一次执行渲染组件vnode时,更新的话也会有oldVnode。
createElm(vnode, insertedVnodeQueue);
} else {
// oldVnode是否是真实的dom
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 此时是执行重新渲染,进入patchVnode,prepatch和diff算法都是在此逻辑之下。
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {
// replacing existing element
var oldElm = oldVnode.elm; // div#app
var parentElm = nodeOps.parentNode(oldElm); //body,后续插入页面用
// create new node
createElm(
vnode,
insertedVnodeQueue
);
// 这里的作用是子组件vnode(占位符vnode)去寻找它下面的不是占位符vnode的elm
if (isDef(vnode.parent)) {
var ancestor = vnode.parent;
var patchable = isPatchable(vnode);
while (ancestor) {
ancestor.elm = vnode.elm;
ancestor = ancestor.parent;
}
}
// 移除最外层的父节点
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
}
都会进入createElm函数
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// 如果不是组件vnode,进来后啥也不做
// function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
// var i = vnode.data; // vnode.data是只有是组件vnode时才会有的
// if (isDef(i)) {
// var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
// if (isDef(i = i.hook) && isDef(i = i.init)) {
// 这里就会执行上面定义的hook.init。
// i(vnode, false /* hydrating */);
// }
// 可以看出组件vnode的插入是在这里进行的
// if (isDef(vnode.componentInstance)) {
// initComponent(vnode, insertedVnodeQueue);
// insert(parentElm, vnode.elm, refElm);
// if (isTrue(isReactivated)) {
// reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
// }
// return true
// }
// }
// }
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if (isDef(tag)) {
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
setScope(vnode);
// 如果存在children,那么将递归调用createElm函数,parentElm是调用createElm时传递进来的参
// 数,也就是说如果递归调用createElm函数的话,当前的vnode.elm就会被作为children的
// parentElm
{
createChildren(vnode, children, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--;
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
hook.init = function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
}
function createComponentInstanceForVnode (
vnode, // we know it's MountedComponentVNode but flow doesn't
parent // activeInstance in lifecycle state
) {
//这是传入vm.init(options)中的参数,所以前面Vue.prototype._init中获取到的
// options._isComponent和options._parentVnode都是这里传过去的。
var options = {
_isComponent: true,
_parentVnode: vnode,
parent: parent
};
// check inline-template render functions
var inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
return new vnode.componentOptions.Ctor(options)
}