《Vue.js的设计与实现》之框架设计
写在开头
去年为了找份工作,卷过Vue2的源码(在此感谢黄轶老师,贴个链接)。卷过才发现程序员还是需要对自己所使用的“武器”具有十分深刻的理解。毕竟熟知兵器才能大杀四方嘛。但是Vue2随着2.7的发布已经接近尾声,这把兵器只能战现在了。正好618买了本大圣老师推荐的《Vue.js设计与实现》,最近读了读发觉蛮不错的。但是学习还是个需要不断总结的过程,便打算接下来的一段时间准备边看书边把书中的所得进行些总结和分享。让我们手持Vue3战未来吧,AMD YES!!!
权衡的艺术
对于前端来说,性能和易用之间的权衡一直是一个亟待思考的问题。书中将权衡分为四个方向,我这里总结为三个
- 视图框架的权衡
- 使用虚拟DOM的权衡
- 运行时和编译时的权衡
接下来就让我们看看作者的思路吧。
视图框架
视图框架分两种
- Jquery所代表的命令式
- Vue所代表的声明式
对于命令式来说,它更关注过程,即直接对视图进行操控。而声明式则是关注结果,Vue将控制的过程进行了封装(模板编译和diff更新),从而我们只需要关注业务的本身,至于产生的结果则是由Vue处理。
但是我们要记住优化好的命令式的性能永远优于声明式,因为最佳的命令式就是找出差异+直接修改。而声明式的Vue则是在执行声明式操作之前,自己通过diff去寻找差异,这部分的性能消耗是多出的部分。
虚拟DOM的使用
关于虚拟DOM的使用是否一定会提升性能这个老生常谈的问题。我就沿用我曾经看到的一句话,即虚拟DOM的使用只是为了保证性能的下限。
前文介绍到,虚拟DOM的更新(声明式)性能消耗 = 找出差异的性能消耗 + 直接修改(命令式)的性能差异,虽然命令式性能更高,但是想写出绝对优化的命令式是十分困难的。而且执行JavaScript代码的速度远快于修改DOM。
当然也有人说使用innerHTML直接替换呢?首先从新建来说,由于虚拟DOM存在Vnode的处理,所以稍慢于innerHTML。但是当页面需要更新时,虚拟DOM是Diff更新,而innerHTML则是销毁旧DOM再创建新的,这远慢于虚拟DOM。
所以Vue的虚拟DOM的使用虽然不是性能的最优方式,但是对性能和开发易用性之间的权衡来说,仍是值得去使用的。
运行时和编译时
书中提出,针对于设计一个框架一般由三种选择:
- 纯运行时
- 运行时 + 编译时
- 纯编译时
这里简单介绍下
纯运行时:利用元素对象树直接执行render将元素渲染。
运行时+ 编译时(Vue采用):使用Compiler将HTML模板编译成元素对象树,再使用运行时的方式将元素渲染。
纯编译时(Svelte):既然我们可以将HTML模板通过Compiler编译成对象树,那可不可以直接编译成命令式代码呢?当然可以,这就是纯编译的方式。
但是Vue为何选择第二种呢?因为直接变成代码有损灵活性,并且通过静态节点标记等优化,Vue的性能其实和纯编译的差距不大。
核心要素
一个框架的设计,只是功能完成并不代表真正的完成。我们还需要考虑其他方面,如用户开发体验、构建产物和错误处理等。接下来简单介绍下。
开发体验
对于开发体验,Vue提供的warn函数,对错误的异常会发送出清晰友好的警告。其次,提供了自定义的formatter,可以在开发者工具中开启。这样对于响应式数据的输出结果更加清晰明了。
代码体积控制
观看源码是,Vue中每一个warn的执行调用前都会由__DEV__常量,这个常量可以通过rollup.js进行定义(开发为true,生产为false)。这样可以在开发环境具有友好提示的同时减少生产环境中不必要的代码。
Tree-Sharking
关于Tree-Sharking的好处大家基本都是知道的,即可以在输出代码的时候不会将未使用的代码编译进来。但是想使用Tree-Sharking的前提就是使用ESM的静态导入。而Vue3正好满足。
同时,Vue3中增加了/*#__PURE__*/标记,该标记放在未调用函数的前面,表明了该函数不存在副作用,可以被Tree-Sharking清除。
构建产物
- 直接使用script标签使用:其输出结果是IIFE(立即调用函数表达式)
- 使用ESM引用:
- -browser:给type为module的script标签使用,利用__DEV__区分,会直接设置为true或false,与当前环境无关
- -bundler:给webpack等打包工具使用,利用
process.env.NODE_ENV !== 'production'进行区分,与运行的环境有关。
- 使用node的require;针对服务端渲染,使用cjs
特性开关
Vue提供环境变量__VUE_OPTIONS_API__控制一些特性的启用和关闭,后续文章详解。
错误处理
Vue设置了callWithErrorHandling函数,该函数用于执行用户传入的fn函数,并在执行异常的时候统一进行处理。
TypeScript支持
TypeScript的优势就不多说了,无非就是友好提示、更强维护性和类型限制等等。
设计思路
Vue3中将各个功能拆分成一个一个模块。虽然拆分了,但各模块间依旧会互相配合从而形成一个有机的整体。如编译器在编译的时候,会检查内部的属性是否是动态,并加以标记。这样后续渲染器处理的时候就减少了检查变更节点的工作量,从而提升整体的性能。
声明式描述UI
利用虚拟DOM处理,同时提供了工具函数h来编写虚拟DOM。
渲染器
渲染器用于将虚拟DOM渲染到浏览器中。即虚拟DOM => 渲染器 => 真实DOM
渲染器renderer接收两个参数,虚拟DOM和挂载元素。其实现思路为:
- 通过虚拟DOM创建元素
- 为元素添加属性和事件
- 处理children元素
- 挂载元素
组件
不同与DOM渲染,组件的处理可以理解为多个DOM的集合的处理。
关于组件的vnode的tag,此时会由string类型的标签名转变为构造函数Ctor或者JavaScript对象。
在renderer渲染处理时,会判断tag的类型。
- 当为字符串时,执行mountElemnt函数,即默认渲染器挂载元素。
- 当为对象或者函数时,执行mountComponent。此时则会执行函数或者对象的render来获取虚拟DOM,再利用renderer完成渲染。
编译器
编译器的作用就是将模板template编译成渲染函数render。
写在最后
这本书我目前也没看完,正好就当作写作的素材吧(绝不是我偷懒不想去找素材)。反正看一遍,总结一遍思维导图,再写一遍博客。就当开一个Vue3系列的解析了。这样就能把Vue3硬啃下来了。
关于封面的话,我决定使用绿色系的,毕竟Vue就是绿色,而且绿色健康嘛。
最后在感谢下这本书的作者霍春阳(HcySunYang)。我只是简单总结下,有兴趣的人可以买一本看看,肯定收益良多。
今天是周末,那就祝大家下周快乐吧。我是爱摸鱼的枫林,咱们下周再见!!!