六、缓存虚拟节点
“ 自己动手实践,就会更加深刻的理解**”**
上一篇中,我们使用了闭包来缓存某些数据,将多次的遍历转换为一次遍历。今天这篇,我们使用闭包来缓存 vnode,当数据发生改变时,无需重新生成 vnode,而是直接对 vnode 中的 {{data}} 进行替换。
01、combine
我们需要一个函数,来将带有模板 {{data}} 的 vnode与data结合,生成不带有模板的 vnode,此函数为 combine
/**
* 带{{}}的vnode + data -> vnode
* @param {MyVNode} vnode 虚拟节点
* @param {*} data 数据
*/
function combine(vnode, data) {
const {tag, text, type, data: attrs, children} = vnode;
let _vnode;
switch(type) {
case 1: // 元素节点
_vnode = new MyVNode(tag, attrs, text, type);
for (const child of children) {
_vnode.appendChild(combine(child, data));
}
break;
case 3: // 文本节点
const newText = text.replace(regMustache, function (_, g) {
return getValueByPath(data, g.trim());
})
_vnode = new MyVNode(tag, attrs, newText, type)
break;
}
return _vnode;
}
02、闭包缓存render函数
值得注意的是,Vue中使用的是 AST + data => vnode,而这里简化,跳过AST,用vnode来代替。
/**
* 通过AST生成render函数。
* 目的是缓存AST。
* 这里用 vnode 来模拟AST。
*/
MyVue.prototype.createRenderFn = function() {
const vnode = getVNode(this._template);
/**
* Vue: AST + data => VNode
* MyVue: 带{{}}的VNode + data => VNode
*/
return function render() {
return combine(vnode, this._data);
}
}
03、其他函数
MyVue.prototype.$mount = function() {
this._render = this.createRenderFn(); // 带缓存的render函数
this.mountComponent();
}
MyVue.prototype.mountComponent = function () {
this._update(this._render())
}
/**
* Vue: diff算法更新vnode
* MyVue(简化): 将vnode生成DOM放入文档中
*/
MyVue.prototype._update = function (newDOM) {
this._parent.replaceChild(parseVNode(newDOM), this._template);
}
04、效果图
html部分代码:
<header id="root">
<p>{{name}}</p>
<p>{{message}}</p>
<p>{{name}} -- {{message}}</p>
<p>{{deep.firstLevel.secondLevel}}</p>
</header>
<script>
let app = new MyVue({
el: '#root',
data: {
name: 'romeo',
message: 'wants to be rich',
deep: {
firstLevel: {
secondLevel: 'awesome'
}
}
}
})
</script>
源代码在github~