【Vue.js设计与实现解读-1】

Vue设计与实现阅读-1

    • 1、命令式和声明式
    • 2、性能
    • 3、虚拟DOM性能
    • 4、运行时和编译时
    • 5、总结

前言
最近工作清闲了些,想着很久没有看书,Vue.js设计与实现这本书看了好几次都没有读完,趁着这个机会边读边记录一下吧。如果有理解的不正确的地方,欢迎指出。

1、命令式和声明式

命令式: 关注过程

例如实现这样一个代码

 -获取 id 为 app 的 div 标签-设置他的文本内容为hello world-为他绑定点击事件-点击时弹出提示:ok
const div = document.querySelector('#app');
div.innerText = 'hello world';
div.addEventListener('click', () => {alert('ok');
});

声明式: 关注结果

同样用声明式的写法实验上面的功能

<div @click="() => alert('ok')">hello world</div>

上面这个html模板采用的是Vue实现的方式,可以看出,我们提供的是一个结果,vue内部去帮我封装了这个过程,就像我们告诉vue:我需要一个div,他的文本内容是hello world,有一个绑定事件。可以猜测,Vue.js的内部实现是命令式的,暴露给用户的是声明式的。

2、性能

声明式代码的性能不优于命令式代码的性能。
例如,还是上面的例子,我们需要修改 div 中文本的内容为 hello Vue。

命令式的写法,直接调用相关命令

div.textContent = 'hello Vue' // 直接修改

声明式写法

// 修改前
<div @click="() => alert('ok')">hello world</div>
// 修改后
<div @click="() => alert('ok')">hello Vue</div>

从上面可以看出,命令式代码明确知道哪里发生了变更,只需要做必要的修改就行,但声明式还不一定能做到这一点。对于一个框架来说,为了实现最优的更新性能,他需要找到前后的差异且只更新变化的地方。
如果我们把直接修改的性能消耗定义为A,找出差异的消耗定义为B,则

	命令式代码的更新性能消耗 = A声明式代码的更新新能消耗 = A + B

在性能层面命令式是更好的选择,为什么Vue.js要选择声明式设计方案。原因在于声明式的可维护性更强。在上面的例子中可以看到,如果采用声明式开发,我们需要维护实现目标的整个过程(DOM创建、更新、删除等),而命令式我们只需要关注结果就行。

3、虚拟DOM性能

声明式代码的更新新能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗。如果能够最小化找出差异的性能消耗,就可以让声明式代码的性能无限接近于命令式代码的性能。所谓的虚拟DOM,就是为了最小化找出差异这一步的性能消耗而出现的。

innerHTML创建页面的过程: 构造 HTML 字符串,将该字符串赋值给 DOM 元素的 innerHTML 属性。

const html = `<div><span></span></div>
`
div.innerHTML = html
 innerHTML创建页面的性能:HTML 字符串拼接的计算量 + innerHTML 的 DOM 计算量

虚拟DOM创建页面过程:第一步,创建js对象(真实 dom),第二步递归遍历虚拟 dom 树并创建真实 dom

 虚拟dom创建页面的性能:创建JS对象的计算量 + 创建真实 DOM 的计算量

从创建页面来看,两者差距不大,都是需要新建所有 DOM 元素。但是继续看下更新页面时的性能

innerHTML更新页面的过程:相当于重新构建HTML字符串,再设置 DOM 元素的 innerHTML 属性。相当于更改了一个文字,也需要重新设置 innerHTML 属性,等价于销毁所有旧的 DOM 元素,再全量创建新的 DOM 元素。

虚拟DOM更新页面过程:创建 js 对象(虚拟DOM树),比较新旧虚拟 DOM, 找到变化的元素更新他。

对比:在更新页面的时候,虚拟 DOM 在 js 层面比创建页面时多了个找 diff 的性能消耗,但是比较是 js 层面的运算,不会产生数量级的差异,再观察 DOM 层面的运算,虚拟 DOM 只需要更新边户的COM, 而 innerHTML 需要全量更新。

4、运行时和编译时

设计框架,我们有三种选择,纯运行时、运行时+编译时、纯编译时。

假设设计一个框架,提供一个 Render 函数,用户可以为该函数提供一个属性结构的数据对象,Render 函数会根据对象递归将数据渲染成 DOM 元素。
规定树形结构如下:
const obj = {tag: 'div',children: [{tag: 'span',children: 'hello world'}]
}

每个对象有两个属性,tag 标签名称, children 可以是数组,表示子节点,可以是文本表示文本子节点。
Render 函数实现

function Render(obj, root) {// 创建标签const el = document.createElement(obj.tag);// 如果是文本子节点if (typeof obj.children === 'string') {// 创建文本元素const text = document.createTextNode(obj.children);// 添加进标签元素el.appendChild(text);} else if (obj.children) {// 子节点,递归调用render 构造子元素obj.children.forEach((child) => Render(child, el))}// 将元素添加到根节点root.appendChild(el);
}

元素使用

Render(obj, document.boby);

可以发现,用户在使用Render函数渲染内容的时候,可以直接提供一个树形数据对象给 Render 对象,不涉及其他额外步骤。
有一天用户抱怨,手写树形数据对象太麻烦,不直观,想支持用类型HMTL标签的方式描述树形结构对象。发现刚写的Render函数并不能满足。刚实现的Render函数,是一个纯运行时的框架。

为满足用户需求,使用编译的手段 把HTML 标签变异成树形结构的数据对象。

<div><span>hello world</span>
</div>------ 经过编译 ------const obj = {tag: 'div',children: [{tag: 'span',children: 'hello world'}]}

假设 你编写了一个 Compiler 的程序,他的作用是将 HTML 字符串编译成树形结构的对象,用户需要分别调用 Compiler 和 Render 函数就能实现。

const html = `<div><span>hello world</span></div>
`
const obj = Compiler(html);
Render(obj, document.boby);

此时 框架变成了运行时 + 编译时。既支持运行时,用户可以直接提供数据对象无需编译;又支持编译时,用户可以提供 HTML字符串,我们将它编译成数据对象交给运行时处理。
那编译器可以把 HTML 变成成数据对象,应该也可以编译成命令式代码。

<div><span>hello world</span>
</div>------ 经过编译 ------const div = document.createElement('div');
const span = document.createElement('span');
span.innerText = 'hello world';
div.appendChild(span);
document.boby.appendChild(div);

这样我们只需要一个 Compiler 函数就行。这就成了一个纯编译时的框架。我们不支持任何运行时内容,用户的代码通过编译器编译后才能运行。

一个框架可以是上面说的这三种的任意一种

纯运行时,无编译过程,因此没办法分析用户提供的内容。如果加入编译,我们可以分析用户提供内容,看到哪些发生改变,哪些没有,
那我们可以在编译时提取这些信息,传递给 Render 函数,Render函数根据这些信息进一步优化。但是如果是纯编译时,她可以分析用户提供的内容,
由于不需要任何运行时,而是直接编译,那性能会更好,但是因为用户提供的内容必须通过编译才能使用,所以有损灵活性

Vue3保持运行时+编译时的架构,在保持灵活性的基础上能够尽可能地去优化。

5、总结

  • 命令式和声明式,命令式关注过程,声明式关注结果
  • 声明式的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗
  • 虚拟dom存在的意义在于使找出差异的性能消耗最小化
  • 运行时和编译时的差异,vue3是编译时+进行时的框架。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/598398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#12新功能主构造函数:实用,简洁利落

大家还记得记录(record)类型吗&#xff1f;记录类型很像一个构造函数&#xff0c;那么类可以像record类型一样使用吗&#xff1f;C#12就推出了这样的功能&#xff0c;这就是主构造函数。“现在可以在任何 class 和 struct 中创建主构造函数。主构造函数不再局限于 record 类型。…

安全防御之入侵检测与防范技术

安全防御中的入侵检测与防范技术主要涉及到入侵检测系统&#xff08;IDS&#xff09;和入侵防御技术&#xff08;IPS&#xff09;。 入侵检测系统&#xff08;IDS&#xff09;是一种对入侵行为自动进行检测、监控和分析的软件与硬件的组合系统。IDS通过从计算机网络或系统中的若…

梯度、散度、旋度

目录 梯度Gradient 散度Divergence 旋度Curl 梯度Gradient 即函数在该点处沿着该方向&#xff08;此梯度的方向&#xff09;变化最快&#xff0c;变化率最大&#xff08;为该梯度的模&#xff09;&#xff1b; Scalar -> Vector &#xff0c;表示标量变化最快的方向&…

uniapp子组件向父组件传值

目录 子组件向父组件传值子组件1子组件2 父组件最后 子组件向父组件传值 子组件1 <template><view class"content"><view v-for"(item,index) in list" :key"index">{{item}}</view></view> </template>&…

FPGA——VIVADO生成固化文件,掉电不丢失

VIVADO生成固化文件 (1)加入代码(2)生成bin文件&#xff0c;并且下载 (1)加入代码 设计文件(.xdc)中加入这段代码: set_property CFGBVS VCCO [current_design] set_property CONFIG_VOLTAGE 3.3 [current_design] set_property BITSTREAM.GENERAL.COMPRESS true [current_de…

echarts实现点击不同的柱子实现类目的不同名字

实现效果如下图: 首先实现echarts堆叠柱状图数据为0的不占用x轴空间 option {tooltip: {trigger: axis,axisPointer: {type: shadow},},legend: {},grid: {left: 3%,right: 4%,bottom: 3%,containLabel: true},xAxis: [{type: category,position: bottom,data: [园区内, 园区…

Linux_apachectl 网页优化

1.1 网页压缩与缓存 在使用 Apache 作为 Web 服务器的过程中&#xff0c;只有对 Apache 服务器进行适当的优化配 置&#xff0c;才能让 Apache 发挥出更好的性能。反过来说&#xff0c;如果 Apache 的配置非常糟糕&#xff0c; Apache 可能无法正常为我们服务。因此&#xff0c…

静态网页设计——BNA热火队介绍(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 使用技术&#xff1a;HTMLCSSJS 主要内容&#xff1a;对BNA的球队热火队进行介绍。 基本内容 1、首页 首页分为三部分&#xff0c;以html进行分割&#xff0c;最上方…

HTTP打怪升级之路

新手村 上个世纪80年代末&#xff0c;有一天&#xff0c;Tim Berners-Lee正在工作&#xff0c;他需要与另一台计算机上的同事共享一个文件。他尝试使用电子邮件&#xff0c;但发现电子邮件不能发送二进制文件。Tim Berners-Lee意识到&#xff0c;他需要一种新的协议来共享二进制…

初级 Vue 算法题。

题目描述 请编写一个 Vue 组件&#xff0c;实现一个简单的表单验证器。表单中有一个输入框和一个提交按钮。当用户点击提交按钮时&#xff0c;如果输入框为空&#xff0c;则显示错误提示信息&#xff1b;否则&#xff0c;显示成功提交的提示信息。 示例 <template><…

静态网页设计——喜羊羊与灰太狼(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1Ta4y1B75m/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS 主要内容&a…

docker学习笔记04-可视化界面Portainer

1.Portainer简介 Portainer 是一款开源的容器管理工具&#xff0c;旨在帮助用户更轻松地管理 Docker 环境。无论您是 Docker 新手还是经验丰富的开发人员&#xff0c;Portainer 都提供了直观的用户界面&#xff0c;使您能够方便地创建、部署和监控容器。 安装 Portainer 非常…

用Delphi调用阿里云的OpenAPI更新动态域名解析记录

用Delphi调用阿里云的OpenAPI更新动态域名解析记录 家里一直是电信的宽带&#xff0c;虽然只是200M下行30M上行的平均水平&#xff0c;但是对于平时下载量不大的我来说已经绰绰有余了&#xff0c;很多时候需要从外网访问家里的NAS的资料&#xff0c;但是因为不是固定IP&#xf…

Spring Boot中进行分库编程

在Spring Boot中使用分库&#xff08;Sharding&#xff09;需要根据具体情况而定。分库是一种解决单数据库性能瓶颈和数据量大的解决方案&#xff0c;通过将数据分散到多个数据库中&#xff0c;可以提升数据库的读写性能和数据存储能力。但是&#xff0c;分库也会带来一些复杂性…

Java项目:109SpringBoot超市仓管系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 超市仓管系统基于SpringBootMybatis开发&#xff0c;系统使用shiro框架做权限安全控制&#xff0c;超级管理员登录系统后可根据自己的实际需求配角色&…

PostgreSQL 分区

由于大量数据存储在数据库同一张表中&#xff0c;后期性能和扩展会受到影响。所以需要进行表分区&#xff0c;因为它可以将大表分成较小的表&#xff0c;从而减少内存交换问题和表扫描&#xff0c;最终提高性能。庞大的数据集被分成更小的分区&#xff0c;更易于访问和管理。 …

入门Python数据分析最好的实战项目

本篇将继续上一篇数据分析之后进行数据挖掘建模预测&#xff0c;这两部分构成了一个简单的完整项目。结合两篇文章通过数据分析和挖掘的方法可以达到二手房屋价格预测的效果。 下面从特征工程开始讲述。 特征工程 特征工程包括的内容很多&#xff0c;有特征清洗&#xff0c;…

DevOps(9)

目录 45.如何在Linux中将一个文件附加到另一个文件&#xff1f; 46.解释如何使用终端找到文件&#xff1f; 47.解释如何使用终端创建文件夹&#xff1f; 48.解释如何使用终端查看文本文件&#xff1f; 49.解释如何在Ubuntu LAMP堆栈上启用curl&#xff1f; 50.解释如何在…

Spring Cloud Gateway 缓存区异常

目录 1、问题背景 2、分析源码过程 3、解决办法 最近在测试环境spring cloud gateway突然出现了异常&#xff0c;在这里记录一下&#xff0c;直接上干货 1、问题背景 测试环境spring cloud gateway遇到以下异常 DataBufferLimitException: Exceeded limit on max bytes t…

kali系统

概述 实际上它就是一个预安装了很多安全工具的Debian Linxu。 nmap&#xff08;Network Mapper&#xff09;扫描 nmap是一个免费开放的网络扫描和嗅探的工具包&#xff0c;也叫网络映射器。 nmap的强大之处在于简单易用。看一下nmap的基本功能&#xff1a; 探测一组主机是…