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

   2023-03-08 学习力0
核心提示:一、虚拟DOM:因为DOM操作非常消耗性能,在操作DOM时,会出现DOM的回流(Reflow:元素大小或者位置发生改变)与重绘(元素样式的改变)使DOM重新渲染。现在的框架Vue和React很少直接操作DOM,因为两者都是数据驱动视图,只会对数据进行增删改的操作因此,二者

一、虚拟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的不同,然后重新渲染

二、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, oldStartIdxoldEndIdxnewStartIdxnewEndIdx ,然后进行一个循环,在循环过程中

    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值递增了,但对应的内容却错误了

  

三、模板编译

零、前置知识点: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 执行 patchdiff 算法

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

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

   

四、初次渲染与更新过程

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

  

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

   

五、异步渲染--this$nextTick()

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

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

  

六、组件化

  

  • 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操作使页面渲染性能降低,加载速度变慢,影响用户体验等问题。

七、响应式

  • 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

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • vue3+TS 自定义指令:长按触发绑定的函数
    vue3+TS 自定义指令:长按触发绑定的函数而然间看到一个在vue2中写的长按触发事件的自定义指定,想着能不能把他copy到我的vue3项目中呢。编写自定义指令时遇到的几个难点1.自定义指令的类型在ts中写任何东西都要考虑到类型的问题,自定义指令的类型问题依然存
    03-08
  • Vue生命周期 vue生命周期几个阶段
    Vue生命周期 vue生命周期几个阶段
    官网解释一、Vue的生命周期Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom - 渲染、更新 - 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。Vue2生命周期:1.beforeCreate(创建前)数据观测和初始化事件还未开始,此时 dat
    03-08
  • vue 中安装并使用echart
    vue 中安装并使用echart
    本文为博主原创,转载请注明出处:1.安装echart 依赖:  安装命令: npm install echarts --save  在vscode 的终端窗口进行执行,如图所示:   执行完之后,查看 项目中的echart 版本依赖是否添加成功:  package-lock.json 中有具体的echart 依赖
    03-08
  • day04-Vue01
    day04-Vue01
    Vue011.Vue是什么?Vue(读音/vju:/,类似于view)是一个前端框架,依据构建用户界面Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或者项目整合支持和其他类库结合使用开发复杂的单页应用非常方便Vue是Vue.js的简称官网:Vue.js - 渐进式 JavaScr
    03-08
  • Vue + Element 自定义上传封面组件
    Vue + Element 自定义上传封面组件
    前一段时间做项目,频繁使用到上传图片组件,而且只上传一个封面,于是想着自定义一个图片封面上传组件。先来看一下效果:                      第一张图片是上传之前,第二张图片是上传成功后,第3张图片是鼠标放上去之后的效果! 首先整理需
    03-08
  • 基于ZR.VUE 前端的改造,页面刷新报错
     问题描述:前后端分离开发,分开部署. 页面刷新 直接报404 错误的解决办法提示:  先在 .env.development 中 配置 VUE_APP_BASE_API , 将 '/' 替换为 后端地址 'http://localhost:8888/'如果是对应的发布的正式环境,也要修改  .env.production 的VUE_APP_
    03-08
  • Vue3 企业级优雅实战 - 组件库框架 - 9 实现组
    上文搭建了组件库 cli 的基础架子,实现了创建组件时的用户交互,但遗留了 cli/src/command/create-component.ts 中的 createNewComponent 函数,该函数要实现的功能就是上文开篇提到的 —— 创建一个组件的完整步骤。本文咱们就依次实现那些步骤。(友情提示
    03-08
  • vue-3 this概念
    一、this概念官方是这样说的:在 setup()内部,this 不会是该活跃实例的引用因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同这在和其它选项式 API 一起使用 setup() 时可能会导致混淆啥意思呢
    03-08
  • vue3和百度地图关键字检索 定位 点击定位
    vue3和百度地图关键字检索 定位 点击定位
    效果图在index.html中引入百度地图开放平台  去申请你的ak 非常的简单可以自己百度 一下!-- 这个用官网给的有好多警告 更具百度的把 https://api.map.baidu.com/getscript?v=2.0ak=xxxxxxxxxxxxxxxxxxxxx 换为这个就没有那么多 报错了 --scripttype="text/j
    03-08
  • Vue集成lodop插件实现打印功能 vue打印console
    Vue集成lodop插件实现打印功能 vue打印console
    目录VUE简单使用lodop1.创建LodopFuncs.js文件2.在打印功能vue页面引入LodopFuncs3.执行打印方法4.打印接口函数官网样例说明5.完整页面示例VUE 集成LODOP插件打印Lodop、C-Lodop使用说明及样例http://www.lodop.net/LodopDemo.htmlVUE简单使用lodop1.创建Lodo
    03-08
点击排行