最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 分析diff算法与虚拟dom(理解现代前端框架)

    正文概述 掘金(lavie)   2021-02-11   432

    本文全部代码小的已经上传github?

    虚拟DOM

    直观来说,虚拟DOM其实就是用数据结构表示真实的DOM结构。使用它的原因是,频繁的操作DOM会使得网站的性能下降,为了保证性能,我们需要使得DOM的操作尽量精简,我们可以通过操作虚拟DOM的方法,去比较新旧节点的差异然后精确的获取最小的,最为必要的DOM集合,最终挂载到真实的DOM上。因为操作数据结构,远比我们直接修改DOM节点来的快,我们真实的DOM操作在最好的情况下,其实只需要在最后来那么一下,不是吗

    如何表示DOM结构

    分析diff算法与虚拟dom(理解现代前端框架) 分析diff算法与虚拟dom(理解现代前端框架)

    这是一段列表的DOM结构,我们分析一下,其中需要包含的信息有

    1. 标签类型 ul,li...

    2. 标签属性 class,style...

    3. 孩子节点ul->li li->text ...

    无论再复杂的结构,也都是类似的,那么我们在找到DOM结构的共性之后,我们应该怎么表示呢

    分析diff算法与虚拟dom(理解现代前端框架)

    通过这张图我们可以发现,我们可以用对象JS对象轻易地就将它表示出来,几个属性也是非常好理解

    • tagName对应真实的标签类型
    • attrs表示节点上的所有属性
    • child表示该节点的孩子节点

    那这样我们是不是可以给这个虚拟DOM设定一个类 like this

    分析diff算法与虚拟dom(理解现代前端框架)

    function newElement(tag,attr,child){ //创建对象函数
        return new Element(tag,attr,child)
    }
    

    测试一下

    分析diff算法与虚拟dom(理解现代前端框架)

    ok没问题是不是,那现在虚拟DOM其实就已经被创建出来了,那么有了虚拟DOM之后怎么挂载到真实DOM上呢

    生成真实DOM节点

    首先我们会需要一个根据对象属性来设置标签属性的方法

    分析diff算法与虚拟dom(理解现代前端框架)

    然后我们在类的内部添加创建节点的render方法

    分析diff算法与虚拟dom(理解现代前端框架)

    到这里我们就可以通过使用render方法创建真实的DOM节点了,在方法内部,我们通过调用SetVdToDom方法对属性进行设置,然后对子节点进行类型判断,递归到最后剩下的文本节点。

    最后我们通过一个renderDom方法将dom渲染到浏览器看看

    //vdmock.js 部分
    const VdObj1 = newElement('ul',{id: 'list'},[
        newElement('li',{class: 'list-1',style:'color:red' }, ['lavie']),
        newElement('li',{class: 'list-2' }, ['virtual dom']),
        newElement('li',{class: 'list-3' }, ['React']),  
        newElement('li',{class: 'list-4' }, ['Vue']) 
    ])
    const RealDom = VdObj1.render()
    const renderDom = function(element,target){
        target.appendChild(element)
    }
    export default function start(){
       renderDom(RealDom,document.body)
    }
    
    // index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script type="module" src="./vdmock.js"  ></script>
        
        <title>Document</title>
    </head>
    <body >
        <script type="module" >
            import start from './vdmock.js'
            start()
        </script>
    </body>
    </html>
    

    结果如下:

    分析diff算法与虚拟dom(理解现代前端框架)

    虚拟DOM diff

    通过上面方法,我们可以很简单的生成虚拟DOM并且将它渲染到浏览器上面,那么我们在用户进行操作之后,如何计算出前后虚拟DOM之间的差异呢?下面就来介绍一下diff算法

    分析diff算法与虚拟dom(理解现代前端框架)

    我们通过给diff传入新旧的两个节点通过内部的getDiff递归对比节点并存储变化然后返回,下面我们来实现一下getDiff

    获取最小差异数组

    const REMOVE = 'remove'
    const MODIFY_TEXT =  'modify_text'
    const CHANGE_ATTRS = 'change_attrs'
    const TAKEPLACE = 'replace'
    let initIndex = 0
    const getDiff = (oldNode,newNode,index,difference)=>{
        let diffResult = []
        //新节点不存在的话说明节点已经被删除
        if(!newNode){
            diffResult.push({
                index,
                type: REMOVE
            }) //如果是文本节点直接替换就行
        }else if(!oldNode){ //旧节点说明new是新增节点直接添加就行
            diffResult.push({
                type: TAKEPLACE,
                newNode
            })
        }else if(typeof newNode === 'string' && typeof oldNode === 'string'){
            if(oldNode !== newNode){
                diffResult.push({
                    index,
                    value: newNode,
                    type: MODIFY_TEXT
                })
            } //如果节点类型相同则则继续比较属性是否相同
        }else if(oldNode.tagName === newNode.tagName){
            let storeAttrs = {}
            for(let  key in oldNode){
                if(oldNode[key] !== newNode[key]){
                    storeAttrs[key] = newNode[key]
                }
            }
            for (let key in newNode){
                if(!oldNode.hasOwnProperty(key)){
                    storeAttrs[key] = newNode[key]
                }
            }
            if(Object.keys(storeAttrs).length>0){
                diffResult.push({
                    index,
                    value: storeAttrs,
                    type: CHANGE_ATTRS
                })
            } //遍历子节点
            // oldNode.child.forEach((child,index)=>{
            //     console.log(child,111)
            //      getDiff(child,newNode.child[index],++initIndex,difference)
            // }) //如果类型不相同,那么无需对比直接替换掉就行
            newNode.child.forEach((child,index)=>{
                getDiff(oldNode.child[index],child,++initIndex,difference)
           }) 
        }else if(oldNode.tagName !== newNode.tagName){
            diffResult.push({
                type: TAKEPLACE,
                newNode
            })
        } //最后将结果返回
        if(diffResult.length){
            difference[index] = diffResult
        }
    }
    
    

    测试结果如下:

    分析diff算法与虚拟dom(理解现代前端框架)

    分析diff算法与虚拟dom(理解现代前端框架)

    更新dom

    现在我们已经生成了两个虚拟DOM,并且将两个DOM之间的差异用对象的方式保存了下来,接下来,我们就要通过这些来将差异更新到真实的DOM上面去!!!

    分析diff算法与虚拟dom(理解现代前端框架)

    分析diff算法与虚拟dom(理解现代前端框架)

    pace函数会自身进行递归,对当前节点的差异用dofix进行更新

    const doFix = (node,difference) =>{
         difference.forEach(item=>{
             switch (item.type){
                 case 'change_attrs':
                     const attrs = item.value.attrs
                     for( let key in attrs ){
                         if(node.nodeType !== 1) //nodeType为1表示节点
                         return 
                         const value = attrs[key]
                         if(value){
                             SetVdToDom(node,key,value)
                             
                         }else{
                             node.removeAttribute(key)
                         }
                     }
                     break
                     case 'modify_text':
                         node.textContent = item.value
                         break
                    case 'replace': 
                       let newNode = (item.newNode instanceof Element) ? item.newNode.render(item.newNode) : 
                       document.createTextNode(item.newNode)
                        node.parentNode.replaceChild(newNode,node)
                        break
                    case 'remove' :
                        node.parentNode.removeChild(node)
                        break
                    default: 
                        break
             }
         })
    }
    

    万事具备,那我们来测试一下!

    const VdObj1 = newElement('ul',{id: 'list'},[
        newElement('li',{class: 'list-1',style:'color:red' }, ['lavie']),
        newElement('li',{class: 'list-2' }, ['virtual dom']),
        newElement('li',{class: 'list-3' }, ['React']),  
        newElement('li',{class: 'list-4' }, ['Vue']) ,
    ])
    const VdObj = newElement('ol',{id: 'list'},[
        newElement('h2',{class: 'list-1',style:'color:green' }, ['lavieee']),
        newElement('li',{class: 'list-2' }, ['virtual dom']),
        newElement('li',{class: 'list-3' }, ['React']), 
        newElement('li',{class: 'list-4' }, ['Vue']) ,
        newElement('li',{class: 'list-5' }, ['Dva']) ,
        newElement('li',{class: 'list-5' }, ['Dva']) 
     
    ])
    const RealDom = VdObj1.render()
    const renderDom = function(element,target){
        target.appendChild(element)
    }
    export default function start(){
       renderDom(RealDom,document.body)
       const diffs = diff(VdObj1,VdObj)
       fixPlace(RealDom,diffs)
    }
    

    before

    分析diff算法与虚拟dom(理解现代前端框架)

    diff after

    分析diff算法与虚拟dom(理解现代前端框架)

    嘻嘻完美

    通过这几个例子下来,其实虚拟dom的思想就已经可以实现了,我们在使用框架的过程中如果可以梳理清楚其中的核心概念,一定会走的更加踏实。

    最后祝大家新年快乐,牛年offer拿到手软,加薪不加班?!!!!


    起源地下载网 » 分析diff算法与虚拟dom(理解现代前端框架)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元