虚拟dom和diff算法
前言

代码实现
虚拟节点
虚拟dom是什么?虚拟dom就是一个节点描述对象,即一个对象。
h:渲染一个组件,渲染一个节点


渲染:把vnode渲染成domElement
把虚拟节点渲染成真实dom

第1步:创建元素/文本节点
这里只是第一层,而且div上的属性还没加
export function render(vnode,container){
let ele=createDomElementFromVnode(vnode);
container.appendChild(ele)
}
function createDomElementFromVnode(vnode) {
let {type,key,props,children,text}=vnode;
if(type){
vnode.domElement=document.createElement(type)
}else{
vnode.domElement=document.createTextNode(text)
}
return vnode.domElement;
}
第2步:给domElement加属性
export function render(vnode,container){
let ele=createDomElementFromVnode(vnode);
// let {children=[]}=vnode;
// children.forEach(child=>{
// render(child,ele);
// })
container.appendChild(ele)
}
function createDomElementFromVnode(vnode) {
let {type,key,props,children,text}=vnode;
if(type){
vnode.domElement=document.createElement(type)
updateProperties(vnode)
// 视频里是放在了这里
children.forEach(child=>render(child,vnode.domElement))
}else{
vnode.domElement=document.createTextNode(text)
}
return vnode.domElement;
}
function updateProperties(newVnode,oldProps={}){
let domElement=newVnode.domElement;
let newProps=newVnode.props;
for(let prop in oldProps){
if(!newProps[prop]){
delete domElement[prop]
}
}
// style需要特殊处理
for(let prop in oldProps.style){
if(!newProps.style[prop]){
domElement.style[prop]=''
}
}
/**
* @todo className dom事件等
*/
for(let prop in newProps){
if(prop==='style'){
// style需要特殊处理
for(let styleProp in newProps.style){
domElement.style[styleProp]=newProps.style[styleProp]
}
}else{
domElement[prop]=newProps[prop]
}
}
}
总结:
1.主要接收type props children来创建虚拟节点。虚拟dom就是一个对象,把type和key单独提取出来;
若是文本包成一个只有text属性的vnode
- 通过render渲染成真实节点,把真实节点插到容器中
先根据type区分是元素还是文本节点。
元素节点:把属性赋到元素节点上。比较老属性和新属性的差异,并且算出最新的赋到最新的元素上。递归渲染
文本节点:
domdiff
涉及到相关dom操作
获取父节点 elem.parentNode
替换孩子 replaceChild(newChild, oldChild)
文本内容 textContent='xxx'
export function patch(oldVnode,newVnode) {
/**
* 类型不同 直接替换
*/
if(oldVnode.type !== newVnode.type){
// 底层肯定是dom操作
return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode),oldVnode.domElement)
}
if(oldVnode.text){
if(oldVnode.text !== newVnode.text){
return oldVnode.domElement.textContent=newVnode.text;
}
}
newVnode.domElement=oldVnode.domElement;
updateProperties(newVnode,oldVnode.props)
}
updateChildren

列表diff比对*
对常见的dom操作优化

比较头指针

比较尾指针

分了2种情况

vue的话数据变化的时候才会走这个patch
mvvm => 调用patch
暴力比对


没有key,则重新创建插入
export function render(vnode,container){
let ele=createDomElementFromVnode(vnode);
// let {children=[]}=vnode;
// children.forEach(child=>{
// render(child,ele);
// })
container.appendChild(ele)
}
function createDomElementFromVnode(vnode) {
let {type,key,props,children,text}=vnode;
if(type){
vnode.domElement=document.createElement(type)
updateProperties(vnode)
// 视频里是放在了这里
// 递归渲染子虚拟节点
children.forEach(child=>render(child,vnode.domElement))
}else{
vnode.domElement=document.createTextNode(text)
}
return vnode.domElement;
}
function updateProperties(newVnode,oldProps={}){
let domElement=newVnode.domElement;
let newProps=newVnode.props;
for(let prop in oldProps){
if(!newProps[prop]){
delete domElement[prop]
}
}
// style需要特殊处理
for(let prop in oldProps.style){
if(!newProps.style[prop]){
domElement.style[prop]=''
}
}
/**
* @todo className dom事件等
*/
for(let prop in newProps){
if(prop==='style'){
// style需要特殊处理
for(let styleProp in newProps.style){
domElement.style[styleProp]=newProps.style[styleProp]
}
}else{
domElement[prop]=newProps[prop]
}
}
}
export function patch(oldVnode,newVnode) {
/**
* 类型不同 直接替换
*/
if(oldVnode.type !== newVnode.type){
// 底层肯定是dom操作
return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode),oldVnode.domElement)
}
if(oldVnode.text){
if(oldVnode.text !== newVnode.text){
return oldVnode.domElement.textContent=newVnode.text;
}
}
// 复用oldNode dom
let domElement=newVnode.domElement=oldVnode.domElement;
updateProperties(newVnode,oldVnode.props) // 更新自身属性
let oldChildren=oldVnode.children;
let newChildren=newVnode.children;
if(oldChildren.length>0 && newChildren.length>0){
// 列表比对 最复杂的
updateChildren(domElement,oldChildren,newChildren)
}else if(oldChildren.length>0){
domElement.innerHTML=''
}else if(newChildren.length>0){
for(let i=0;i<newChildren.length;i++){
domElement.appendChild(createDomElementFromVnode(newChildren[i]));
}
}
}
function updateChildren(parent,oldChildren,newChildren) {
// 设置了头&尾2个指针
let oldStartIndex=0;
let oldStartVnode=oldChildren[oldStartIndex];
let oldEndIndex=oldChildren.length-1;
let oldEndVnode=oldChildren[oldEndIndex];
let newStartIndex=0;
let newStartVnode=newChildren[newStartIndex];
let newEndIndex=newChildren.length-1;
let newEndVnode=newChildren[newEndIndex];
// 2排指针往前走
while (oldStartIndex<=oldEndIndex && newStartIndex<=newEndIndex){
// 判空处理
if(!oldStartVnode){
oldStartVnode=oldChildren[++oldStartIndex]
}else if(!oldEndVnode){
oldEndVnode=oldChildren[--oldEndIndex]
}
if(isSameVnode(oldStartVnode,newStartVnode)){
patch(oldStartVnode,newStartVnode)
// 往后走
oldStartVnode=oldChildren[++oldStartIndex]
newStartVnode=newChildren[++newStartIndex]
}else if(isSameVnode(oldEndVnode,newEndVnode)){
patch(oldEndVnode,newEndVnode)
oldEndVnode=oldChildren[--oldEndIndex];
newEndVnode=newChildren[--newEndIndex]
}else if(isSameVnode(oldStartVnode,newEndVnode)){
patch(oldStartVnode,newEndVnode)
parent.insertBefore(oldStartVnode.domElement,oldEndVnode.domElement.nextElementSibling)
oldStartVnode=oldChildren[++oldStartIndex];
newEndVnode=newChildren[--newEndIndex]
}else if(isSameVnode(oldEndVnode,newStartVnode)){
patch(oldEndVnode,newStartVnode)
parent.insertBefore(oldEndVnode.domElement,oldStartVnode.domElement)
oldEndVnode=oldChildren[--oldEndIndex];
newStartVnode=newChildren[++newStartIndex]
}else{
const map=createMapByKeyToIndex(oldChildren);
let index=map[newStartVnode.key];
if(index==null){
parent.insertBefore(createDomElementFromVnode(newStartVnode),oldStartVnode.domElement)
}else {
patch(oldChildren[i],newStartVnode)
parent.insertBefore(oldChildren[i].domElement,oldStartVnode.domElement)
oldChildren[i]=undefined;
}
newStartVnode=newChildren[++newStartIndex]
}
}
if(newStartIndex<=newEndIndex){
for(let i=newStartIndex;i<=newEndIndex;i++){
let beforeElement=newChildren[newEndIndex+1]==null?null:newChildren[newEndIndex+1].domElement;
parent.insertBefore(createDomElementFromVnode(newChildren[i]),beforeElement)
// parent.appendChild(createDomElementFromVnode(newChildren[newStartIndex]))
}
}
// 移除老队列中多余的
if(oldStartIndex<=oldEndIndex){
for(let i=oldStartIndex;i<=oldEndIndex;i++){
oldChildren[i] && parent.removeChild(oldChildren[i].domElement)
}
}
}
function createMapByKeyToIndex(oldChildren){
let map={}
oldChildren.forEach((child,i)=>{
if(child.key){
map[child.key]=i
}
})
return map;
}
function isSameVnode(oldVnode,newVnode) {
return oldVnode.type===newVnode.type && oldVnode.key===newVnode.key;
}
文章介绍了虚拟DOM的概念,它是一个节点描述对象,通过`h`函数创建。文章详细阐述了从虚拟节点到真实DOM的渲染过程,包括创建元素、添加属性、更新属性以及对比和更新子节点的策略。还提到了关键的`patch`函数用于比较旧的和新的虚拟节点,以实现高效DOM更新。
1310

被折叠的 条评论
为什么被折叠?



