Vue demo
先给出vue简单的使用demo,通过创建一个Vue的实例,<div id="app"></div>将被替换成template模版中的内容,a,b的值也会被换成data属性的值
<div id="app"></div>
var vm = new Vue({
el: '#app',
template:
`<div>
<p>{{a}}</p>
<p>{{b}}</p>
<div :class="a">btn1</div>
<div @click="plus()">btn2</div>
<div>
<div>
<div>str1</div>
<div>str2</div>
</div>
<div>str3</div>
</div>
</div>`,
data(){
return {
a: 1,
b: 2
}
}
})
模版渲染
以下的分析代码都经过作者简化,只为简单清楚的解析vue的实现逻辑
首先根据参数template属性生成render函数
function Vue (options) {
this._init(options);
}
Vue.prototype._init = function (options) {
var vm = this;
//参数el属性存在,就调用mount方法;初始化时也可以不传el属性,后续调用mount方法
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function () {
var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this);
//由template参数得到render方法
var render = ref.render;
//由template参数得到最大静态渲染树
var staticRenderFns = ref.staticRenderFns;
};
下面看下compileToFunctions生成render方法的具体实现
var ast = parse(template.trim(), options);
optimize(ast, options);
var code = generate(ast, options);
首先根据template字符串生成ast对象,parse函数主要是通过正则表达式将str转换成
树结构的对象,ast对象基本结构如下:
然后对ast对象进行优化,找出ast对象中所有的最大静态子树(可以简单理解为不包含参数data属性的dom节点,每次data数据改变导致页面重新渲染的时候,最大静态子树不需要重新计算生成),基本实现逻辑如下:
function optimize (root, options) {
//将ast对象的所有节点标记为是否静态
markStatic(root);
markStaticRoots(root, false);
}
function markStatic (node) {
//当前节点是否静态
node.static = isStatic(node);
//递归标记node的子节点是否静态
for (var i = 0, l = node.children.length; i < l; i++) {
var child = node.children[i];
markStatic(child);
//只要有一个子节点非静态,父节点也非静态
if (!child.static) {
node.static = false;
}
}
}
function markStaticRoots (node, isInFor) {
//将包含至少一个非文本子节点(node.type === 3代表文本节点)的节点标记为最大静态树的根节点
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true;
return
} else {
node.staticRoot = false;
}
//当前node节点不是静态根节点,递归判断子节点
if (node.children) {
for (var i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for);
}
}
}
本例中的最大静态树:
最终生成的渲染函数code如下
其中render是整个模版的渲染函数,staticrenderfns是静态树的渲染函数,staticrenderfns中的函数只会初始化一次,后续不需要再计算
render函数中用到的一些方法如下
function installRenderHelpers (target) {
target._o = markOnce;
target._n = toNumber;
//转换为string对象
target._s = toString;
target._l = renderList;
target._t = renderSlot;
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;
target._f = resolveFilter;
target._k = checkKeyCodes;
target._b = bindObjectProps;
//生成虚拟文本节点
target._v = createTextVNode;
target._e = createEmptyVNode;
target._u = resolveScopedSlots;
target._g = bindObjectListeners;
}
//生成虚拟dom节点
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
得到render函数后会继续调用下面的方法
function mountComponent (
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
//对vue实例新建一个Watcher监听对象,每当vm.data数据有变化,Watcher监听到后负责调用updateComponent进行dom更新
vm._watcher = new Watcher(vm, updateComponent, noop);
)
render函数调用后(vm._render())会生成vnode对象,也就是大家熟知的虚拟dom树,调用update方法就能根据vonde更新真实的浏览器dom。
接下来我们分析下updatecomponents是如何调用的,这就涉及到了vue经典的watch机制(此处先简单介绍,下一篇会有较详细的分析)。
//new Watcher时会先调用一次updateComponent,后续会监听vm.data的变化
var Watcher = function Watcher (vm,expOrFn){
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
}
this.get();
}
Watcher.prototype.get = function get () {
value = this.getter.call(vm, vm);
}
最后再讲一下 vm._update方法的实现
Vue.prototype._update = function (vnode, hydrating) {
var prevVnode = vm._vnode;
vm._vnode = vnode;
//判断vnode是否初始化过
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
// vm.$el是vm对象挂载的节点,本例是<div id="app"></div>
// vm.$options._parentElm是组件挂载的节点(父节点),后面介绍组件时分析
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
}
//vm.__pathch__方法
function function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm){
//根据vnode生成element并插入parentElm
createElm(vnode, insertedVnodeQueue, parentElm, refElm);
}
//下面主要介绍初始化dom的实现
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
//根据vnode节点生成浏览器Element对象
vnode.elm = nodeOps.createElement(tag, vnode);
var children = vnode.children;
//递归将vnode子节点生成Element对象
createChildren(vnode, children, insertedVnodeQueue);
//将生成的vnode.elm插入到浏览器的父节点当中
insert(parentElm, vnode.elm, refElm);
}
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true);
}
//当vnode是文本节点时停止递归
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text));
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。