近段时间开始写vue项目,以及在面试中遇到关于vue原理的问题,现打算开始看看Vue及全家桶的源码,并记录下自己的理解,如有错误,还望大佬们帮忙指出:
1.关于vue虚拟dom渲染到真实dom的简单实现
// 虚拟DOM元素的类,构建实例对象,用来描述DOM
class Element {
constructor(type, props, children) {
this.type = type; //对应的dom元素名称
this.props = props; //对应的类名,style等等
this.children = children; //对应的子元素
}
}
// 创建虚拟DOM,返回虚拟节点(object)
function createElement(type, props, children) {
return new Element(type, props, children);
}
// render方法将虚拟dom转换为真实dom
function render(domObj){
//根据type渲染为真实的dom元素
let el = document.createElement(domObj.type)
//遍历自身props添加
for(let key in domObj.props) {
//设置属性(只考虑了class,暂时没有考虑到style样式。。。)
el.setAttribute(key, domObj.props[key])
}
//遍历子元素
domObj.children.forEach(child => {
let chileEl = (child instanceof Element) ? render(child) : document.createTextNode(child)
el.appendChild(chileEl)
})
return el;
}
//将元素dom插入页面内
function renderDom(el,target){
target.appendChild(el)
}
// 首先引入对应的方法来创建虚拟DOM
let virtualDom = createElement('ul', {class: 'list'}, [
createElement('li', {class: 'item'}, ['子元素1']),
createElement('li', {class: 'item'}, ['子元素2']),
createElement('li', {class: 'item'}, ['子元素3'])
]);
console.log(virtualDom)
let el = render(virtualDom);
console.log(el);
renderDom(el,document.getElementById('root'))

比较两棵DOM树的差异是Virtual DOM算法最核心的部分.简单的说就是新旧虚拟dom 的比较,如果有差异就以新的为准,然后再插入的真实的dom中,重新渲染。 借网络一张图片说明:

如图可以看出,树的渲染对比,需要经过新旧虚拟dom的对比,并且只在同层级进行比较: 通过第一步的虚拟dom简单实现可以看出,节点的对比主要为以下四种情况:
-
此节点是否被移除 -> 添加新的节点
-
属性是否被改变 -> 旧属性改为新属性
-
文本内容被改变-> 旧内容改为新内容
-
节点要被整个替换 -> 结构完全不相同 移除整个替换
以下是模仿节点被移除:
/**
* 变化虚拟dom
*/
//keyIndex记录遍历顺序
let keyIndex = 0
// 遍历
function diff(oldEle, newEle) {
let patches = {}
keyIndex = 0
walk(patches, 0, oldEle, newEle)
return patches
}
//分析变化
function walk(patches, index, oldEle, newEle) {
let currentPatches = []
//这里应该有很多的判断类型,这里只处理了删除的情况...
if (!newEle) {
currentPatches.push({ type: 'remove' })
}
else if (oldEle.type == newEle.type) {
//比较儿子们
walkChild(patches, currentPatches, oldEle.children, newEle.children)
}
//判断当前节点是否有改变,有的话把补丁放入补丁集合中
if (currentPatches.length) {
patches[index] = currentPatches
}
}
function walkChild(patches, currentPatches, oldChilds, newChilds) {
if (oldChilds) {
for (let i = 0; i < oldChilds.length; i++) {
let oldChild = oldChilds[i]
let newChild = newChilds[i]
walk(patches, ++keyIndex, oldChild, newChild)
}
}
}
/**
* 将变化的补丁插入到真实dom
*/
let index = 0;
let allPatches;
function patcFunc(trueNode,patch){
allPatches = patch
showNow(trueNode)
}
//操作真实dom
function showNow(trueNode) {
let currentPatches = allPatches[index]
index++
(trueNode.childNodes || []) && trueNode.childNodes.forEach(child => {
showNow(child)
})
if (currentPatches) {
doPatch(trueNode, currentPatches)
}
}
//根据type是移除,则移除dom中的元素
function doPatch(ele, currentPatches) {
currentPatches.forEach(currentPatch => {
if (currentPatch.type == 'remove') {
ele.parentNode.removeChild(ele)
}
})
}
// 首先引入对应的方法来创建虚拟DOM
let virtualDom = createElement('div', {class: 'list'}, [
createElement('div', {class: 'item'}, ['周杰伦']),
createElement('div', {class: 'item'}, ['林俊杰']),
createElement('div', {class: 'item'}, ['王力宏'])
]);
console.log('旧的dom', virtualDom)
//变化
let newDom = createElement('div', {class: 'list'}, [
createElement('div', {class: 'item'}, ['林俊杰']),
createElement('div', {class: 'item'}, [''])
]);
let patch = diff(virtualDom,newDom)
let el = render(virtualDom);
patcFunc(el,patch)
renderDom(el,document.getElementById('root'))

总结的说,vue在创建虚拟dom的时候会以一个规定的格式来创建虚拟dom需要的数据,然后当发生改变的时候,新的虚拟dom的数据和旧的虚拟dom的数据会进行对比,按照层级进行对比(diff的比较方式,如果上一层的子节点不一样,就直接替换,不再比较子节点),不相同的便放到一个patch补丁里,然后再将patch补丁里的数据去操作当前真实dom,看是否节点需要改变,删除等等。