gpt4 book ai didi

vue原理:diff、模板编译、渲染过程等

转载 作者:我是一只小鸟 更新时间:2023-02-14 06:31:19 25 4
gpt4 key购买 nike

1、虚拟DOM:

因为DOM操作非常 消耗性能 ,在操作DOM时,会出现DOM的 回流(Reflow:元素大小或者位置发生改变) 与 重绘(元素样式的改变) 使DOM重新渲染.

现在的框架Vue和React很少直接操作DOM,因为两者都是数据驱动视图,只会对数据进行增删改的操作 。

因此,二者使用虚拟DOM(vdom)来解决控制DOM操作的问题:

原理: 使用Js模拟DOM结构 ,把DOM的计算转移为Js的计算,使用diff算法计算出最小的变更,然后根据变更操作DOM 。

学习diff算法需要借助snabbdom这个vdom库的源码,vue也是参考它实现的 。

                          
                            import {
  init,
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
  h,
} from 
                          
                          "snabbdom"
                          
                            ;

const patch 
                          
                          =
                          
                             init([
  
                          
                          
                            //
                          
                          
                             Init patch function with chosen modules
                          
                          
  classModule, 
                          
                            //
                          
                          
                             makes it easy to toggle classes
                          
                          
  propsModule, 
                          
                            //
                          
                          
                             for setting properties on DOM elements
                          
                          
  styleModule, 
                          
                            //
                          
                          
                             handles styling on elements with support for animations
                          
                          
  eventListenersModule, 
                          
                            //
                          
                          
                             attaches event listeners
                          
                          
                            ]);

const container 
                          
                          = document.getElementById("container"
                          
                            );

                              const vnode  
                          
                          
                             = h("div#container.two.classes", { on: { click: someFn } }, [ h("span", { style: { fontWeight: "bold" } }, "This is bold"), " and this is just normal text", h("a", { props: { href: "/foo" } }, "I'll take you places!"), ]); 
                             // 
                             Patch into empty DOM element – this modifies the DOM as a side effect 
                          
                          
                              patch(container, vnode);  
                             const newVnode 
                          
                          
                            = h(
  "div#container.two.classes",
  { on: { click: anotherEventHandler } },
  [
    h(
      "span",
      { style: { fontWeight: "normal", fontStyle: "italic" } },
      "This is now italic type"
    ),
    " and this is still just normal text",
    h("a", { props: { href: "/bar" } }, "I'll take you places!"),
  ]
);


                          
                          
                            //
                          
                          
                             Second  ` patch `  invocation
                          
                          
                            patch(vnode, newVnode)
                          
                          ; 
                          
                            //
                          
                          
                             Snabbdom efficiently updates the old view to the new state
                          
                        

其中有两个关键函数:

  • h  函数 返回一个vnode,他是使用js对象表示的虚拟DOM结构。接收 sel(选择器)、data(对DOM的js描述)、children(这个虚拟DOM的子vnode元素)
  • patch  函数 的作用:一是将vnode渲染为真实的DOM 挂载 至页面;二是使用diff算法 比对 两个vnode的不同,然后重新渲染

2、Diff算法:

  • 概述:

diff 比对两个新旧vnode的过程主要是在 patch 函数 (patchVnode函数) 中进行 。

  。

正常情况下两棵树之间作比对,那么第一遍历tree1,第二遍历tree2,第三排序,三次遍历,时间复杂度为 O(n ^ 3) 节点太多,算法就不可用 。

框架中diff算法的优化:

  • 同级比对,不跨级
  • tag不相同 ,则直接删除重建,不再深度比对(有可能tag不相同但是tag下面的子元素还是相同的,但是我们不管了,只要tag不相同就删掉,因为深度比较复杂度太高)
  • tag和key,两者都相同 ,则认为是相同节点,不再深度比对, 时间复杂度优化至 O(n)
  • 生成vnode:

    h 函数用来生成vnode,vnode函数如下:

 返回一个js对象结构的虚拟DOM(vnode):

     1.children和text是不能共存的,要么里面是纯text文本,要么是子元素 。

     2.elm 就是vnode对应的那个DOM元素 。

     3.key 就相当于 v-for 里面的 key,是我们在使用 v-for 的时候需要自己手动加上 。

  • patch函数:

初始化: 第一次执行patch,patch(container,vnode),创建空的vnode,关联传入的dom 。

更新: 判断是否是相同的vnode, tag(sel选择器)和key相同 ,则认为是相同节点,执行 patchVnode函数进行比对 。

  否则删除重建,不做深度比对 。

        。

  • patchVnode函数(比对):
    • 如果新Vnode有 children,没有 text (vnode.text === undefined)(text和children不能同时存在)
    1. 如果新旧vnode都有 children ,调用 updateChildren() ,再继续进行更新
    2. 如果新vnode有 children ,旧vnode无 children,调用  addVnodes()  添加 children 到 elm 上
    3. 如果新vnode无 children ,旧vnode有 children,调用 removeVnodes() 移除 children
    4. 如果新旧vnode都无 childre且旧vnode有 text,则把elm的 text 设置为空
    • 如果新Vnode没有 children,只有 text 且值也不同,就移除旧vnode的children

        。

  • updateChildren函数:

原理:

    针对新旧 children   定义四个index, oldStartIdx , oldEndIdx , newStartIdx , newEndIdx ,然后进行一个循环,在循环过程中 。

    idx会一边累加或者一边累减,startIdx会累加,endIdx会累减,在这个过程中,指针会慢慢地往中间去移动,当指针重合的时候,说明遍历结束了,循环结束.

      在每一轮循环过程中的具体的对比过程是:

    如果出现下面情况中的一种: 开始和开始节点去对比,结束和结束节点对比,结束和开始节点对比 ,那么就执行 patchVnode() 函数,进行 递归 比较, 。

    并且指针累加或者累减,往中间移动。 进行下一轮循环的时候,指针就指到下一个了  children 。

    如果都没有上面的四种情况,首先会拿新节点 key ,能否对应上 oldChildren 中的某个节点的  key  。
    • 如果没有对应上,说明这个节点是新的,找个地方插入进去新的就好。
    • 如果对应上了,还要判断 sel 是否相等,如果 sel 不相等,那还是没对应上,说明节点是新的,那也找地方插入新的。
    • 如果 sel 相等, key 相等,那么继续对这两个相同的节点执行 patchVnode 方法,递归比较。

      。

  • v-for中key的作用
    • 如果不使用 key ,diff 算法中 没有 key值能够对应上,会认为节点更新,之后会销毁对应的vnode,重新渲染元素
    • 如果检测出新节点中的 key 在旧节点上有对应的 key ,在进行交换位置的操作时,就没有必要销毁,由此提升性能 。

    • key值需要唯一值,如果使用 index,例如在一个 li 数组中 头部插入某些dom元素,index值递增了,但对应的内容却错误了

3、模板编译 。

零、前置知识点:JS的 with 语法 。

with 语法:改变 {} 内自由变量的查找规则,,将 {} 内自由变量,当作 obj 的属性来查找 如果找不到匹配的 obj 属性,就会报错 with 要慎用, 它打破了作用域的规则,易读性变差 。

vue模板编译成什么?

模板不是html , 有指令、插值、JS 表达式,能实现判断、循环 。

html是标签语言,只有JS才能实现判断、循环(图灵完备的) 。

因此,模板一定是转换为某种JS代码,模板怎么转成js代码的过程就是模板编译 。

安装 vue template complier 这个库,查看编译输出值:

                          
                             // 引入 
                            
const
compiler = require( ' vue-template-compiler ' ) // 插值 // const template = ` <p>{{message}}</p> ` // 编译 const res = compiler.compile(template) console.log(res. render )

打印结果:

                          with(
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            p
                          
                          
                            '
                          
                          ,[_v(_s(message))])}
                        

其中 this 在vue中就是 vm 实例,所以 _c、_v、_s 就是vue源码中的一些函数 。

                          
                            //
                          
                          
                             从 vue 源码中找到缩写函数的含义
                          
                          
                            function installRenderHelpers (target) {
    target._c 
                          
                          = createElement
                          
                            //
                          
                          
                            创建vnode
                          
                          
    target._o =
                          
                             markOnce;
    target._n 
                          
                          =
                          
                             toNumber;
    target._s 
                          
                          =
                          
                             toString;
    target._l 
                          
                          =
                          
                             renderList;
    target._t 
                          
                          =
                          
                             renderSlot;
    target._q 
                          
                          =
                          
                             looseEqual;
    target._i 
                          
                          =
                          
                             looseIndexOf;
    target._m 
                          
                          =
                          
                             renderStatic;
    target._f 
                          
                          =
                          
                             resolveFilter;
    target._k 
                          
                          =
                          
                             checkKeyCodes;
    target._b 
                          
                          =
                          
                             bindObjectProps;
    target._v 
                          
                          =
                          
                             createTextVNode;
    target._e 
                          
                          =
                          
                             createEmptyVNode;
    target._u 
                          
                          =
                          
                             resolveScopedSlots;
    target._g 
                          
                          =
                          
                             bindObjectListeners;
    target._d 
                          
                          =
                          
                             bindDynamicKeys;
    target._p 
                          
                          =
                          
                             prependModifier;
}
                          
                        

转化后:createElement 函数的作用是创建一个 vnode 。

                          with(
                          
                            this
                          
                          ){
                          
                            return
                          
                           createElement(
                          
                            '
                          
                          
                            p
                          
                          
                            '
                          
                          ,[createTextVNode(toString(message))])}
                        
  • 表达式编译:表达式会转变为js代码,然后把结果放到vnode里面去
                          
                            const
                          
                           template =  ` <p>{{flag ? message : 
                          
                            '
                          
                          
                            no message found
                          
                          
                            '
                          
                          }}</p>
                          
                             ` 
with(
                          
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            p
                          
                          
                            '
                          
                          ,[_v(_s(flag ? message : 
                          
                            '
                          
                          
                            no message found
                          
                          
                            '
                          
                          ))])}
                        
  • 动态属性:同理
                          
                            const
                          
                           template =
                          
                              ` 
    
                          
                          <div id=
                          
                            "
                          
                          
                            div1
                          
                          
                            "
                          
                          
                            class
                          
                          =
                          
                            "
                          
                          
                            container
                          
                          
                            "
                          
                          >
        <img :src=
                          
                            "
                          
                          
                            imgUrl
                          
                          
                            "
                          
                          />
    </div>
                          
                            
 ` 

with(
                          
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            div
                          
                          
                            '
                          
                          
                            ,
     {staticClass:
                          
                          
                            "
                          
                          
                            container
                          
                          
                            "
                          
                          ,attrs:{
                          
                            "
                          
                          
                            id
                          
                          
                            "
                          
                          :
                          
                            "
                          
                          
                            div1
                          
                          
                            "
                          
                          
                            }},
     [_c(
                          
                          
                            '
                          
                          
                            img
                          
                          
                            '
                          
                          ,{attrs:{
                          
                            "
                          
                          
                            src
                          
                          
                            "
                          
                          :imgUrl}})])
                          
}
  • 条件:使用三元表达式来创建不同的vnode节点
                          
                            //
                          
                          
                             条件
                          
                          
                            const
                          
                           template =
                          
                              ` 
    
                          
                          <div>
        <p v-
                          
                            if
                          
                          =
                          
                            "
                          
                          
                            flag === 'a'
                          
                          
                            "
                          
                          >A</p>
        <p v-
                          
                            else
                          
                          >B</p>
    </div>
                          
                            
 ` 
with(
                          
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            div
                          
                          
                            '
                          
                          ,[(flag === 
                          
                            '
                          
                          
                            a
                          
                          
                            '
                          
                          )?_c(
                          
                            '
                          
                          
                            p
                          
                          
                            '
                          
                          ,[_v(
                          
                            "
                          
                          
                            A
                          
                          
                            "
                          
                          )]):_c(
                          
                            '
                          
                          
                            p
                          
                          
                            '
                          
                          ,[_v(
                          
                            "
                          
                          
                            B
                          
                          
                            "
                          
                          )])])}
                        
  • 循环:通过  _l  (  renderList  )函数,传入数组或者对象,即可返回列表vnode
                          
                            //
                          
                          
                            循环
                          
                          
                            const
                          
                           template =
                          
                              ` 
    
                          
                          <ul>
        <li v-
                          
                            for
                          
                          =
                          
                            "
                          
                          
                            item in list
                          
                          
                            "
                          
                           :key=
                          
                            "
                          
                          
                            item.id
                          
                          
                            "
                          
                          >{{item.title}}</li>
    </ul>
                          
                            
 ` 
with(
                          
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            ul
                          
                          
                            '
                          
                          ,_l((list),function(item){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            li
                          
                          
                            '
                          
                          ,{key:item.id},[_v(_s(item.title))])}),
                          
                            0
                          
                          )}
                        
  • 事件:on属性包含所有的事件绑定
                          
                            //
                          
                          
                            事件
                          
                          
                            const
                          
                           template =
                          
                              ` 
    
                          
                          <button @click=
                          
                            "
                          
                          
                            clickHandler
                          
                          
                            "
                          
                          >submit</button>
                          
                            
 ` 
with(
                          
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            button
                          
                          
                            '
                          
                          ,{on:{
                          
                            "
                          
                          
                            click
                          
                          
                            "
                          
                          :clickHandler}},[_v(
                          
                            "
                          
                          
                            submit
                          
                          
                            "
                          
                          )])}
                        
  • v-model:原理就是  value  的  attr  加  input  事件监听的语法糖 最后执行  render  函数,生成vnode
                          
                            //
                          
                          
                            v-model
                          
                          
                            const
                          
                           template =  ` <input type=
                          
                            "
                          
                          
                            text
                          
                          
                            "
                          
                           v-model=
                          
                            "
                          
                          
                            name
                          
                          
                            "
                          
                          >
                          
                             ` 

                          
                          
                            //
                          
                          
                            主要看 input 事件
                          
                          
with(
                          
                            this
                          
                          ){
                          
                            return
                          
                           _c(
                          
                            '
                          
                          
                            input
                          
                          
                            '
                          
                          ,{directives:[{name:
                          
                            "
                          
                          
                            model
                          
                          
                            "
                          
                          ,rawName:
                          
                            "
                          
                          
                            v-model
                          
                          
                            "
                          
                          ,value:(name),expression:
                          
                            "
                          
                          
                            name
                          
                          
                            "
                          
                          }],attrs:{
                          
                            "
                          
                          
                            type
                          
                          
                            "
                          
                          :
                          
                            "
                          
                          
                            text
                          
                          
                            "
                          
                          },domProps:{
                          
                            "
                          
                          
                            value
                          
                          
                            "
                          
                          :(name)},on:{
                          
                            "
                          
                          
                            input
                          
                          
                            "
                          
                          :function($
                          
                            event
                          
                          ){
                          
                            if
                          
                          ($
                          
                            event
                          
                          .target.composing)
                          
                            return
                          
                          ;name=$
                          
                            event
                          
                          .target.value}}})}
                        
  • 总结

模板编译的过程 :模板编译为render函数,执行render函数后返回vnode 。

之后再基于 vnode 执行 patch 和 diff 算法 。

注意:使用webpack,vue-loader,会在开发环境编译模板,所以最后打包出来产生的代码就没有模板代码,全部都是 render 函数形式 。

  • render 函数: 在vue组件中可以使用 render 代替 template,在某些复杂情况下,可以考虑使用render

4、初次渲染与更新过程 。

  • 初次渲染:
  1. 首先解析模板为 render 函数( 模板编译
  2. 触发响应式,监听data属性,设置 getter、setter
  3. 执行 render 函数,生成 vnode,patch(elm,vnode)

  • 更新过程:
  1. 修改 data,触发 setter (此前在getter中已被监听)
  2. 重新执行 render 函数,生成 newVnode
  3. patch(vnode,newVnode) (diff算法)

5、异步渲染--this$nextTick() 。

  vue组件是异步渲染的。代码没执行完,DOM不会立即渲染。this.$nextTick 会在DOM渲染完成时回调 。

  页面渲染时会将 data 的修改做一个整合,多次 data 的修改 最后只会渲染一个最终值     。

6、组件化 。

  • MVC模式:单向绑定, 即Model绑定到View,使用JS代码更新Model时,View就会自动更新

  • MVVM模式:双向绑定, 实现了View的变动,自动反映在VM,反之亦然。

    对于双向绑定的理解, 就是用户更新了View,Model的数据也自动被更新了 ,这种情况就是双向绑定.

    再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model.

  • MVC与MVVM的区别

MVC和MVVM的区别并不是VM完全取代了C, ViewModel存在目的在于抽离Controller中展示的业务逻辑 , 而不是替代Controller 。

    其它视图操作业务等还是应该放在Controller中实现。也就是说 MVVM实现的是业务逻辑组件的重用 .

    MVC中Controller演变成MVVM中的ViewModel 。

    MVVM通过数据来显示视图层而不是节点操作 。

    MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验等问题.

7、响应式 。

  • vue2:object.defineProperty()

  • vue3:proxy

    。

  。

引用:  。

https://www.shouxicto.com/article/3298.html 。

https://juejin.cn/post/6995232345749979172#heading-2 。

https://juejin.cn/post/6995204870114377741 。

https://blog.csdn.net/gxll499294075/article/details/123667632 。

最后此篇关于vue原理:diff、模板编译、渲染过程等的文章就讲到这里了,如果你想了解更多关于vue原理:diff、模板编译、渲染过程等的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com