本文 vue 版本为 2.5.17, 剖析的是 Runtime + Compiler 构建进去的 Vue.js。在 Vue.js 2.0 中,最终都是通过 render 函数渲染,如果含有 template 属性,则须要将 template 编译成 render 函数,那么这个编译过程会发⽣运⾏时,所以须要带有 Compiler 编译器的版本。本文为 vue 源码介绍系列的第一篇,次要演绎整合 vue 实例化,将 render 函数转为 vnode 到生成挂载实在 dom 次要流程,具体细节请查看源码。第二篇将介绍组件化过程。
vue 源码绝对比较复杂,须要急躁重复了解及调试,不懂就多调试问百度,罗马不是一日建成的,置信保持就会有播种哈~
具体调试能够下载 vue.js,而后具体做断点 debugger 调试。
<script src="./vue.js"></script>
<div id="app"></div>
<script>
var vm = new Vue({
el: '#app',
render(h) {
return h('div', { attr: { class: 'classname'} },
[
'first item',
h('h2', { style: {color: 'orange'} }, 'second item')
]
)
},
data: {message: 'Hello',}
})
</script>
先上图剖析流程
1. 定义 Vue
function Vue (options) {
if ("development" !== 'production' &&
!(this instanceof Vue)
) {warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
initMixin(Vue); // 定义 _init
stateMixin(Vue); // 定义 $set $get $delete $watch 等
eventsMixin(Vue); // 定义事件 $on $once $off $emit
lifecycleMixin(Vue); // 定义 _update $forceUpdate $destroy
renderMixin(Vue); // 定义 _render 返回虚构 dom
2. initMixin
实例化 Vue 时,执行 _init, _init 定义在 initMixin 中
Vue.prototype._init = function (options) {
// 合并 options
if (options && options._isComponent) {initInternalComponent(vm, options); // 组件合并
} else {
// 非组件合并
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
initLifecycle(vm); // 定义 vm.$parent vm.$root vm.$children vm.$refs 等
initEvents(vm); // 定义 vm._events vm._hasHookEvent 等
initRender(vm); // 定义 $createElement $c
callHook(vm, 'beforeCreate'); // 挂载 beforeCreate 钩子函数
initInjections(vm); // resolve injections before data/props
initState(vm); // 初始化 props methods data computed watch 等办法
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created'); // 挂载 created 钩子函数
if (vm.$options.el) {vm.$mount(vm.$options.el); // 实例挂载渲染 dom
}
};
3. $mount
vue 最终都是通过 render 函数将 dom 编译为虚构 dom
// 构建 render 函数
if (!options.render) {// 如果没有 render 属性,那么将 template 模版编译转为 render}
// 最初调用 mount
return mount.call(this, el, hydrating)
// mount 调用 mountComponent
return mountComponent(this, el, hydrating)
4. mountComponent
通过 new Watcher 调用执行 updateComponent, vm._render 获取虚构 dom, vm._update 将虚构 dom 转为实在的 dom 并挂载到页面
// hydrating 代表服务端渲染 hydrating => false
updateComponent = function () {vm._update(vm._render(), hydrating); // 关键点
};
5. _render
_render 执行 render 函数 返回 vnoe
Vue.prototype._render = function () {
// 此处的 vm._renderProxy 等价于 vm
vnode = render.call(vm._renderProxy, vm.$createElement);
}
$createElement 次要是参数重载,整合为对立格局后调用 _createElement 函数
function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {
// 参数重载
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {normalizationType = ALWAYS_NORMALIZE;}
return _createElement(context, tag, data, children, normalizationType)
}
_createElement 次要是依据 tag 标签判断是组件还是一般 node 标签,返回对应的 vnode 虚构 dom
function _createElement (context, tag, data, children, normalizationType) {if (typeof tag === 'string') {
// platform built-in elements
vnode = new VNode(config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
}else{
// direct component options / constructor
vnode = createComponent(tag, data, context, children);
}
}
6. _update
_update 次要实现 vnode 转化为理论的 dom,注入到页面的同时并销毁页面模版。
定义 _update
// _update => __patch__
Vue.prototype._update = function (vnode, hydrating) {if (!prevVnode) {
// 初始化时
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// 更新时
vm.$el = vm.__patch__(prevVnode, vnode);
}
}
定义 __patch__
// __patch__ => patch
Vue.prototype.__patch__ = inBrowser ? patch : noop;
定义 patch,
// 利用函数柯里化,将服务端和浏览器的差别集成到 modules,nodeOps 为 dom 元素操作方法汇合
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({nodeOps: nodeOps, modules: modules});
定义 createPatchFunction
function createPatchFunction (backend) {return function patch (oldVnode, vnode, hydrating, removeOnly) {
// 创立新节点
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
// 销毁节点
if (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode);
}
}
}
定义 createElm,依据 vnode 创立实在 dom
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
// createChildren 函数由子到父,深序递归调用 createElm
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue);
}
// 子节点插入到父节点
insert(parentElm, vnode.elm, refElm);
}
以上就是 vue 从实例化,到调用 render 函数生成 vnode,vnode 通过 patch 转为实在 dom 节点,并挂载到页面的流程状况。接下来将第二篇将接扫 vue 组件化和生命周期过程。