美文网首页
Vue diff 算法与虚拟dom

Vue diff 算法与虚拟dom

作者: 南枫小谨 | 来源:发表于2021-05-23 16:54 被阅读0次

为什么需要虚拟Dom

在不使用虚拟dom的情况下,修改一个节点会引起整个页面的重绘。比如又一个元素进行了修改(删除),剩余的9个元素都需要加载重绘。虚拟Dom就是有一个虚拟的节点树和原有节点数进行比对,做到了更小量更新元素更新。
main.js

import { createElement, render,renderDom } from './element'
import diff from './diff'
import patch from './patch';
// 旧节点
let myDom = createElement('div',{class:'container',style:'color:red'},
[
    createElement('p',{class:'item'},'child1'),
    createElement('p',{class:'item'},'child2'),
    createElement('p',{class:'item'},'child3'),
    createElement('input',{class:'item',value:'I am input'})
])
// 新节点
let myDom2 = createElement('div',{class:'container'},
[
    createElement('p',{class:'item'},'child1'),
    createElement('p',{class:'item'},'child2'),
    createElement('p',{class:'item'},'child3'),
    createElement('p',{class:'item'},'child4')
])
// 比较新旧节点状态,返回补丁
let patches = diff(myDom,myDom2)
// 渲染旧的节点
let el = render(myDom)
patch(el,patches)
renderDom(document.getElementById('app'),el)

diff.js

 let _index = 0
 function diff(oldNode,newNode){
    // diff 算法的本质就是打补丁,如果有需要更改的元素就设置相应的补丁
    // 同级、同位置进行比较,不会出现越级比较
    // 存储当前修改的元素地址和修改类型
    let patches ={}
    // index 下标,用来记录修改元素的下标
    let index = 0

    setPatch(oldNode,newNode,index,patches)
    return patches
}
// 设置类型,补丁类型
const ATTRS='ATTRS' // 修改或者删除该元素属性
const TEXT='TEXT'    // 节点文本修改或者删除
const REMOV='REMOV'   // 删除节点
const REPLACE='REPLACE'  //替换节点
function setPatch(oldNode,newNode,index,patches){
    // 存储需要打补丁的类型
    let currentPatch = []
    if(!newNode){
        // 如果没有新节点,就说明此时的旧节点是删除状态
        currentPatch.push({type:REMOV,_index})
        if(currentPatch.length>0){
            patches[_index]= currentPatch
        }
        
    }else if (isString(oldNode)&& isString(newNode)){
       
        // 判断是否一致
        if (oldNode !== newNode){
            currentPatch.push({type:TEXT,text:newNode})
        }
        if(currentPatch.length>0){
            patches[_index]= currentPatch
        }
    }
    else if(newNode.type === oldNode.type){
        // 如果节点类型都相同,那就比较节点属性是否相同
        let attrs  = diffAttr(oldNode.props,newNode.props)
        // attrs 返回内容,说明需要补丁
        if(Object.keys(attrs).length>0){
            currentPatch.push({type:ATTRS,attrs,index})
        }
        if (currentPatch.length > 0) {
            patches[_index] = currentPatch
        }
        // 递归子节点
        diffChild(oldNode.children,newNode.children,index,patches)
    }else{
        // 节点替换
        currentPatch.push({ type: REPLACE, newNode })
        if (currentPatch.length > 0) {
            patches[_index] = currentPatch
        }
    }

}
function isString(node){
    return Object.prototype.toString.call(node) ==="[object String]"
}
function diffChild(oldChild,newChild,index,patches){
    // 该函数的功能是递归每一个子节点,然后进行diff补丁
    if(Array.isArray(oldChild)){
        oldChild.forEach((child,idx) => {
            setPatch(child,newChild[idx],++_index,patches)
        });
    }else{
        setPatch(oldChild,newChild,++_index,patches)
    }
   
}
function diffAttr(oldAttrs,newAttrs){
    // 函数的功能是判断当前的新旧节点的属性是否相同,如果不同,新的节点属性覆盖旧的节点属性
    // 维护的属性的补丁
    let patch = {}
    //判断旧的节点是否和新的相同,如果不同新的替换旧的
    for(let key in oldAttrs){
        if(oldAttrs[key] !== newAttrs[key]){
            patch[key] = newAttrs[key]
        }
    }
    for(let key in newAttrs){
        // 新的节点属性如果在旧的节点属性中不存在就补丁更新
        if(!oldAttrs.hasOwnProperty(key)){
            patch[key] = newAttrs[key]
        }
    }
    return patch

}
export default diff;

patch.js

import { render, Element } from './element'

let allPatches;
let index = 0;
function patch(node, patches) {
    allPatches = patches
    setDom(node)

}
function setDom(node) {
    // 修改补丁及节点
    let currentPatch = allPatches[index++]
    let childNodes = node.childNodes
    // 递归补丁
    childNodes.forEach(item => setDom(item));
    if (currentPatch) {
        // 如果有补丁,打补丁
        doPatch(node, currentPatch)

    }
}
function doPatch(node, patches) {
    // 打补丁的过程
    patches.forEach(patch => {
        console.log('~~~~~patch', patch)
        switch (patch.type) {
            case 'ATTRS':
                for (const key in patch.attrs) {
                    let value = patch.attrs[key]
                    // 有值添加该属性
                    if (value) {
                        node.setAttribute(key, value)
                    } else {
                        node.removeAttribute(key)
                    }
                }
                break;
            case 'TEXT':
                node.textContent = patch.text
                break;
            case 'REMOV':
                console.log('REMOV',node.parentNode,node)
                node.parentNode.removeChild(node)
                break;
            case 'REPLACE':
                // 替换节点,如果是文本节点,就追加文本节点,否则就替换元素节点
                let newNode = patch.newNode instanceof  Element ? render(patch.newNode):document.createTextNode(patch.newNode)
               node.parentNode.replaceChild(newNode,node)
                break;
        }
    })

}
export default patch

element.js

function Element(type, props, children) {
    this.type = type
    this.props = props
    this.children = children

}
function createElement(type, props, children) {
    return new Element(type, props, children)
}
function render(obj) {
    // 创建节点
    let el = document.createElement(obj.type)
    for (let key in obj.props) {
        el.setAttribute(key, obj.props[key])
    }

    if (Array.isArray(obj.children)) {
        obj.children.forEach(element => {
            // 递归操作,如果当前child不是文本,就继续进行操作,否则创建文本节点
            element = element instanceof Element ? render(element) : document.createTextNode()
            el.appendChild(element)
        });
    }
    else if (typeof obj.children === 'string') { // 如果是字符串当成数组进行递归
        el.appendChild(document.createTextNode(obj.children))
    }

    return el
}
function renderDom(node, target) {
    node.appendChild(target)
}
export { createElement, render, renderDom, Element }

相关文章

  • 第十七天

    1.你怎么理解vue中的diff算法? diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即dif...

  • vue系列---vue-diff

    1.vue-diff 是什么? 提到vue的diff算法就不得不提一个名词 虚拟dom(Virtual DOM) ...

  • react VS Vue diff算法

    react diff diff算法的作用:数据更改,生成相应的虚拟DOM,与真实DOM作对比,通过diff算法,对...

  • 理解vue2.x之diff算法

    了解diff算法前,应该先了解虚拟DOM(VNode),在vue中是先创建VNode,再通过diff算法看哪个节点...

  • Vue3 源码解析(五):Patch 算法

    与 React 一样,Vue 在处理虚拟 DOM 的更新时,也有自己的 diff 算法 —— patch。 什么是...

  • 虚拟dom和diff算法

    虚拟DOM和diff算法 diff:精细化比对最小量更新 真实DOM和虚拟DOM 虚拟DOM:用JavaScrip...

  • 基于React理解虚拟DOM(Virtual DOM)和Diff

    我在之前的文章《虚拟DOM(Virtual DOM)中动态更新视图的diff算法》中,基于vue描述Virtual...

  • Vue diff 算法与虚拟dom

    为什么需要虚拟Dom 在不使用虚拟dom的情况下,修改一个节点会引起整个页面的重绘。比如又一个元素进行了修改(删除...

  • Vue3.0 的性能优化

    1.diff算法优化 vue2.0 中虚拟dom 是全量对比;vue3.0 中新增了静态标记(patchFlag)...

  • Diff 算法、key

    概念 DOM diff 就是对比两棵虚拟 DOM 树的算法。当组件变化时,会得到一个新的虚拟 DOM,diff 算...

网友评论

      本文标题:Vue diff 算法与虚拟dom

      本文链接:https://www.haomeiwen.com/subject/idgajltx.html