第一章:权衡的艺术
1.1命令式和声明式
jq是命令式,关注过程,jq针对简化dom 而产生的
vue是 声明式,关注实现,不严格地说就像是在命令式(关注过程)基础上再对标签进行了一次封装(关注实现)
jQuery | 命令式 | 关注过程 | 简化dom 而产生的 |
---|---|---|---|
Vue | 声明式 | 关注实现 | 针对用户产生声明部分 |
而事实也是,vue内部是命令式,但呈现给用户(程序员)的是声明式的部分
1.2性能与可维护性的权衡
性能对比原则或理论:基于以上比较,理论上声明式在性能上是不能超过命令式的
//不过命令式要代码一一对应实现,初次学习,感觉vue会在复用性上优化性能
而且在声明式的部分,是无法做到内部命令式的部分操作的,vue的把命令的部分交给了自己的源码,用户只需操作得到自己想要的即可,确实提升开发效率
例如:`
而声明式不需要我们关心DOM元素的创建更新删除
//本质都是对dom的操作,声明式原本性能并不比定义式强,只是复用起来,省去了许多声明,从而在整体上优化了性能;官网文档有和react的比较,再写
而之所以性能不如定义式,在于,面向用户的声明式要进行B(代替找出差异消耗的性能)
对于B:一个框架在对元素进行修改(场景就是维护项目),需要
比对需要修改的地方,这里所消耗的性能`用B代替
面向用户的命令式部分就是多了这部分B,
//挺抽象哈,这个例子很形象的帮我理解了两种差别
1.3虚拟DOM的性能到底如何
前置条件:由于很难写出绝对极致的命令式语句;即使写出了,也消耗了相当高的成本,投入产出并不高;因此产生了虚拟DOM,执行B,寻求产出比的下限
以上内容其实是针对JavaScript中的DOM来讲的,我们首先要清楚框架的核心其实就是简化DOM操作目前来讲,当然他配套的生态也十分的重要
在学习《JavaScript DOM编程艺术》一书中提到相同的问题;就是innerHTML的特殊性,下面展开来讲:
innerHTML创建网页需要构造一段HTML字符串
const html ='<div><span>...</span></div>'
接着讲此字符串赋值给DOM元素的innerHTML属性
div.innerHTML =html
远没有上面两句话那么简单:为了渲染页面,首先字符串要解析成DOM树,涉及到DOM层的运算必然不如JavaScript层面
用1万次测试
因为是万次级别的测试,但我们仍看出了性能差别上命令部分比原生js几乎差了1倍
自此inner HTML创建网页的性能公式:A+B,
A:HTML字符串拼接的计算量+inner HTML的DOM计算量
那么虚拟DOM是如何渲染我们的页面的?
//准确来说是创建
两步:
1.创建JavaScript对象(这里的对象可以理解为真实的DOM)
2.递归遍历虚拟DOM树并创建真实DOM(虚拟dom的虚拟原来是指遍历虚拟dom树)
虚拟DOM的性能公式:A十B;A:创建.TavaScripti的计算量B:创建真实DOM的计算量
可以看出innerHTML更新页面的过程就是重新构建HTML字符串,再重新设置DOM;
这需要我们即使只修改了一个文字,也要全部执行者3部操作;而且是销毁
全部旧DOM元素,全量创建新的DOM元素
//这点上我觉得和jq中学习深浅拷贝有关系;简单来说就是命令式会覆盖(比如jq对象的浅拷贝覆盖,深拷贝保留)
看看我们虚拟dom是怎么更新页面数据的:
B的对比不同在这方面起到了不同
也就是:虚拟dom只更新需要更新的局部内容,这就是虚拟dom的优势,你可能会说上面的diff性能消耗,这一算法在后面会分析,这里提一句diff算法在JavaScript层面运算,不会产生数量级的差异(差异有但不明显,忽略);反正能原生层面解决的东西性能肯定不会差
于是各自性能与什么条件有关就显现在下面了:
正在上传…重新上传取消
innerHTML在页面越大时,更新性能消耗越大;虚拟dom和更新量有关
//这里vue的虚拟dom是不是用的diff算法来监测不同的,留个疑问
粗略比较下原生,虚拟dom和innerhtml这三者在更新页面时的性能:
正在上传…重新上传取消
这里原生JavaScript指的是它的dom操作
所谓心智指的是:传统的增删改查,也就是:手动创建,删除,修改大量dom原素
innerHTML在字符串拼接方面表现的有些接近声明了
//命令式和声明式只是个帮助我们理解的名词,不要认为vue有,其他的框架就不能有类似的地方
1.4运行时和编译时
那么综合上面各项指标的取舍,有没有鱼和熊掌兼得的东西
//折中
设计框架时有三种选择:纯运行时的,运行时+编译时的或纯编译时的
聊聊纯运行时:
假设设计了个框架,提供了一个Render函数,用户只需要提供一个树形结构的数据对象;之后这个函数会递归地将数据渲染成DOM元素
规定树形结构的数据对象如下:
const obj = {
tagL: '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,使用el作为root参数
obj.children.array.forEach((child)=>Render(child,el))
}
//元素添加到 root
root.appendChild(el)
}
用户实现提交,无需额外学习知识,不需要额外步骤,为Render提供一个树形结构的数据对象即可;
// 用户实现
const obj = {
tag: 'div',
children: [
{
tag: 'span',
children: 'hello world',
},
],
};
//渲染到body下
// Render(onj, document.body)
直到有一天用户抱怨:不愿意手写数据结构对象,而且不直观,能不能用类似HTML标签的方式描绘数据结构对象呢,你看了看Render函数,摇了摇头,暂不支持,以上就是一个纯运行时的框架
为了满足用户需求,你开始思考能不能引入编译的手段,把HTML编译成树形结构的数据对象,这样我们就可以继续使用Render函数了
为此我们要写一个名为Complier的程序,作用就是实现上面把HTML字符串(标签里的东西)编译成树形结构的数据对象
用户怎么用呢,最简单的就是让用户分别调用Compiler函数和Render函数:
//设计运行时+编译时框架
const html = `<div>
<span> hello world</span>
</div>`
// 看起来``和()在这里作用似乎是一样的
//调用Complier 编译器得到属性结构的数据对象
const obj= Compiler(html)
//再调用Render进行渲染
Render(obj,document.body)
以上 构成了运行时加编译时的框架;准确讲是运行时编译
具体来讲它支持运行时,用户可以直接提供数据对象而无需编译;又支持编译时,用户可以提供(?)字符串,我们将其编译为数据对象后交付运行处理
也就是代码运行时才开始编译,而这会产生一定性能的开销,因此我们也可以在构建阶段就执行compiler将用户提供的内容编译好,等运行就不用编译了,这对性能是友好的
这引起我们的思考,既然编译器可以把HTML字符串编译成数据对象,那为何不直接编译成命令式代码呢?
于是
这样我们只需要一个compliler函数就可以了,甚至连Render都不需要了
然后这就变成了纯编译的框架;然而我们不支持任何运行内容时(这是指什么?),用户代码只能通过编译器编译后才能执行
上面用例子解释了3种方法
那他们都有哪些优缺点呢,在什么场景下能发挥各自的优势呢?
总结如下:
纯运行自然效率强,但无法分析用户提供的内容;如果加入编译,则解决这个问题,传递给我们的Render函数,执行优化;如果是纯编译,则灵活性不高,心智负载大因此vue3出现了
1.5总结
首先
我们讨论了命令式和声明式的差异,其中命令式关注过程(就是日常按步骤把功能实现为代码),声明式关注结果(更加)
命令式在理论上的性能是极致的,心智损耗也更大;声明式减少了心智负担,相应的在性能上有“一定”的牺牲
框架设计者要把这种牺牲尽可能想办法损耗最小化
//仁慈的独裁者,这种理想只出现在开源社区
接着
我们讨论了虚拟DOM的性能(框架设计者为了解决上面问题),给出了一个公式“声明式的更新性能消耗=找出差异的性能消耗+直接修改的性能消耗”
//和上面A+B的顺序颠倒了下,无先后
从而
我们明白虚拟DOM的意义在于使找出差异(B)性能消耗最小化
发现
我们发现不能以偏概全地描述原生,虚拟DOM,innerHTML的性能,而应该把比较放在具体的场景中:这与页面大小,变更部分的大小有关还与是创建页面还是更新页面有关,而选择哪种变更,结合了心智负担和可维护性等因素综合考虑
最终
我们发现虚拟DOM综合来看是个不错的选择
最后我们提了下vue是个运行时+编译时的框架,并对比,发现它在保持灵活性的基础上还能用过编译手段分析用户提供的内容,从而进一步提升了更新性能
//这使得vue不是简单的取舍,而是真正优化了DOM节点操作