在上一节中我们已经将代码整理了一下,这一节我们要做的是比较新旧dom,然后通过diff算法判断虚拟dom时候有变化,是否需要刷新视图。为了让代码更加清晰,我们这边还是做了简化,假设父节点下只有一个元素。并且手动调用更新方法,暂时不做数据双向绑定更新视图。
理解patch函数
不管是第一次挂载到视图还是后续的手动更新,我们都要触发构建新的dom,这时候我们可以将这个逻辑写到update的函数里面,但是 要根据不同的情况触发不同的更新逻辑,所以用到了patch函数
;(function () {
function vnode (tag, data, children, text, elm) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
}
function normalizeChildren (children) {
if (typeof children === 'string') {
return [createTextVNode(children)]
}
return children
}
function createTextVNode (val) {
return new vnode(undefined, undefined, undefined, String(val))
}
function createElement (tag, data, children) {
return new vnode(tag, data, normalizeChildren(children), undefined, undefined);
}
function createElm (vnode) {
var tag = vnode.tag;
var data = vnode.data;
var children = vnode.children;
if (tag !== undefined) {
vnode.elm = document.createElement(tag);
if (data.attrs !== undefined) {
var attrs = data.attrs;
for (var key in attrs) {
vnode.elm.setAttribute(key, attrs[key])
}
}
if (children) {
createChildren(vnode, children)
}
} else {
vnode.elm = document.createTextNode(vnode.text);
}
return vnode.elm;
}
function createChildren (vnode, children) {
for (var i = 0; i < children.length; ++i) {
vnode.elm.appendChild(createElm(children[i]));
}
}
function sameVnode (vnode1, vnode2) {
return vnode1.tag === vnode2.tag
}
function emptyNodeAt (elm) {
return new vnode(elm.tagName.toLowerCase(), {}, [], undefined, elm)
}
function patchVnode (oldVnode, vnode) {
var elm = vnode.elm = oldVnode.elm;
var oldCh = oldVnode.children;
var ch = vnode.children;
if (!vnode.text) {
if (oldCh && ch) {
updateChildren(oldCh, ch);
}
} else if (oldVnode.text !== vnode.text) {
elm.textContent = vnode.text;
}
}
function updateChildren (oldCh, newCh) {
// 假设每一个元素下面只有一个子元素
if (sameVnode(oldCh[0], newCh[0])) {
patchVnode(oldCh[0], newCh[0])
} else {
patch(oldCh[0], newCh[0])
}
}
function patch (oldVnode, vnode) {
var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property
if (!isRealElement && sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode);
} else {
if (isRealElement) {
oldVnode = emptyNodeAt(oldVnode);
}
var elm = oldVnode.elm;
var parent = elm.parentNode;
createElm(vnode);
parent.insertBefore(vnode.elm, elm);
parent.removeChild(elm);
}
return vnode.elm
}
function initData (vm) {
var data = vm.$data = vm.$options.data;
var keys = Object.keys(data);
var i = keys.length
// 代理之后可以直接使用this.key 而不是this.data.key
while (i--) {
proxy(vm, keys[i])
}
}
function proxy (vm, key) {
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get: function () {
return vm.$data[key]
},
set: function (val) {
vm.$data[key] = val
}
})
}
function Vue (options) {
var vm = this;
vm.$options = options;
initData(vm);
vm.mount(document.querySelector(options.el))
}
Vue.prototype.mount = function (el) {
var vm = this;
vm.$el = el;
vm.update(vm.render())
}
Vue.prototype.update = function (vnode) {
var vm = this;
var prevVnode = vm._vnode;
vm._vnode = vnode;
if (!prevVnode) {
vm.$el = vm.patch(vm.$el, vnode);
} else {
vm.$el = vm.patch(prevVnode, vnode);
}
}
Vue.prototype.patch = patch;
Vue.prototype.render = function () {
var vm = this;
return vm.$options.render.call(vm)
}
var vm = new Vue({
el: '#app',
data: {
message: 'Hello world',
isShow: true
},
render () {
return createElement(
'div',
{
attrs: {
'class': 'wrapper'
}
},
[
this.isShow
? createElement(
'p',
{
attrs: {
'class': 'inner'
}
},
this.message
)
: createElement(
'h1',
{
attrs: {
'class': 'inner'
}
},
'Hello world'
)
]
)
}
})
// test
setTimeout(function () {
vm.message = 'Hello';
vm.update(vm.render())
}, 1000)
setTimeout(function () {
vm.isShow = false;
vm.update(vm.render())
}, 2000)
})();
diff算法简介
diff算法一直是比较难的,想深入了解可以参考
[组件更新]https://ustbhuangyi.github.io/vue-analysis/v2/reactive/component-update.html#新旧节点相同