从DOM到虚拟DOM——前端DOM发展史、性能与产能双赢背后的思考
原文:https://juejin.cn/post/6900887137239957512
写在前面的一些话
从我上大学开始正式认识前端这个岗位到现在已经整整过了六年,这六年于我而言就像是观赏了一场盛大的游园活动。无数各式各样的框架、模式、开发思路如同花车一般从眼前掠过又走远。留下了一地残乱的余烬和破碎的思考。
当我抬头的时候,发现这已经是余留MVVM的时代。vue和react大行其道,刚刚毕业的同学们仿佛已经默认不会这俩框架之一就找不到工作。在面试的时候他们面对相关的提问也能娓娓道来,让他们阐述源码中的一些原理也同样能胜任。
但是,源码的原理永远是一层浮于表象的阐述,你未曾一点点认真的追踪每一个步骤,也未曾在框架设计的时候渗入自己的思考和提案,更不曾去了解框架被设计和迭代时的需求和背景。如此一来,你了解的永远只能是 HOW,而不是背后真正重要的 WHY。
这几天听阿里的P8止水大佬说过,在大部分情况下,p7以上的技术实力并无太大的区别,能以技术实力走上代码巅峰的人寥寥无几,更多的代码攀登者将以思考的方式去进步,因此他们去拓宽他们视野,去了解代码演进的历程,去预判未来的趋势。而这,绝不是仅仅明白 HOW 就能到的。
现在的我,只是一个两年半经验的小前端。但工作经验并不会对技术的喜爱和思考产生一丝一毫的障碍。计算机是现代社会进程中真正的魔法,而我们,则是掌控这魔法的魔法师,参破魔法背后的奥秘,在代码的巴别塔上留下我们的痕迹,应是吾辈所求。
从DOM到虚拟DOM,我们应该提出哪些问题?
在如今面向面试学习的大氛围下,我们最常用被问到的关于虚拟DOM的问题是:
“什么是虚拟DOM?”
“为什么要用虚拟DOM?”
第一个问题其实比较好解答,个人觉得比较好的回答是:“虚拟DOM本质上是一组JS到DOM的映射,他在表现形式上呈现为一个包含了所有DOM所需信息的JS对象。”其实一般而言,是什么之类的问题会比较好解答。因为你只需要描述他,而为什么之类的问题就往往难以下口,因为其往往内部蕴含这很多很多延伸的信息。
于是乎在我听到看到过对于第二个问题的解答中,最多的是:“选择使用虚拟DOM的原因是因为直接操作DOM节点的代价太昂贵,而操作JS的成本就要小的多,直接操作DOM节点会引起浏览器的回流重绘,JS则可以发挥他的优势自由选择操作时机和方式。”
但真的仅仅是如此吗?
让我们拉开我们的视野,不再死盯着虚拟DOM这一个单词,而是回望整个前端高速迭代的这几年,重新思考一下以下这些问题。
-
在没有虚拟DOM前,我们经历了什么? -
是什么让程序员们萌生了使用虚拟DOM的想法? -
虚拟DOM的优势到底是什么,是什么让它成为了时代的选择? -
是什么推动了前端职能和功能的高速发展?
身为前端,我们经历了什么?
当我参加工作时,其实vue与react已经成为主流,虽说不像如今这般火爆,但也是妥妥的加分项目。页面的需求和职能已经相当复杂。让我们时光倒退,从头来看这前端人的发展历程。
静态页面与切图——低需求下的低要求
从前(听说的),有这么一个年代,前端同学们最麻烦的工作是切图,最枯燥的任务是搓DOM,所以又被成为切图仔...
那时候只有一些初级版本的js框架,包括但不限于prototype,开发者靠着大量手搓DOM和原生JS实现一些简单的交互,高端的操作可能会事扒脚本,扒组件以实现自己的功能。
这个时期的前端开发者很纯粹,那时尚未开始前后端分离的进程,开发者要做的就是将页面合理的呈现出来,以及实现不多的交互需求。
JQ时代——日益增长的需求与低下开发效率的矛盾
一件事物一旦出现,历史的浪潮就会裹挟着它不断的向前进步。很快的,人们对于页面的要求不再是单纯的能用就行——他们需要更加炫酷的交互,这为前端工程师们带来了难题,究其原因,还是因为那时原生的jsapi实在是又缺又难用。于是,JQ等一大批js框架便应运而生了。
同时代的框架包括但不限于JQ,Dojo,prototype等等,这些对于年轻的前端工程师们来说无疑已经是近乎古董的代名词。可是在当时那个年代,后浏览器战争时代带来的后遗症——兼容性问题已经是折磨当时工程师们的主要凶手(当然现在也是,但在越来越丰富的插件辅助下已经缓解许多)。JQ凭借着其优异的兼容性和性能,在一代js框架中占据了主流。
从当今的眼光来看,JQ依然是不可多得的好框架,得益于Sizzle选择器引擎的研发成功,其性能有了质的突破。它将编写前端代码从后端操纵类的思维中脱离出来,便携式的获取一个或一组DOM节点,并且提供了链式调用。前端开发者们不再需要费劲心思的去想着怎么去合理的操作DOM,他们只要关心业务逻辑的实现即可。
JQ时代是需求和效率第一次大型冲突的产物,从此,解放生产力成为了前端工程师心中亘古不变的追求。
后JQ时代——进一步提升效率的模板语法
JQ和其高度发达的生态环境催生了JQ插件的高速发展,当时的前端开发者们想要实现一个功能,第一个反应就去搜插件,所以在当时的HTML文件内经常密密麻麻的插入了十几甚至几十的<script>
标签,这不仅让页面变的臃肿不堪,更带来了全局变量污染的问题和性能问题(例如经典的白屏问题)。
受累于不同素质的开发者(插件素质层次不齐)以及日益庞大的插件库所带来的全局变量污染问题,前端开发者们从后端同胞的代码中得到了启发——他们也想要模块化。
在那个年代,javaScript尚没有import
和export
这样的标准化支持,于是他们便自己建立了一个标准,那就是CommonJs。当然,之后还有分裂出AMD,CMD等等。这为后来Node.js的蓬勃发展埋下了伏笔。模块化相关知识与本文主旨关系不大,所以不展开细说。
同时JQ也没能很好的解决循环插入DOM节点的需求,拼接数据组成的DOM字符串让开发者们叫苦不迭。于是乎模版语法也运营而生了。用一个经典的公式来概括,那就是:
HTML = template(data)
可以看出,template其实就是一种将数据转为DOM的规则——而且能自定义!而template的原理其实很简单,大致分为以下几步:
-
接受数据,解析他们
-
填入模板
-
innerHtml
在解决了循环插入DOM节点的需求后前端工程师们发现了模板语法更大的优点——你要做的只是数据到DOM节点的映射,不用去点对点的操作DOM,不用关心数据如何变化,你只需要控制规则。兴许是从那一刻开始,前端开发者们寻到了一个崭新而又正确的发展方向——数据驱动。
但模版语法也有他的缺点。说白了,他只是一个数据的解析器,你不可能指望他去解决多么复杂炫酷的问题。而且在早期的模板语法中,他的更新会将所有dom节点注销,然后生成完整的新节点再插入页面中。这样大批量的操作dom必然会导致性能问题——操作dom的开销实在是太大了,何况是一整个页面的的dom呢?
于是乎开发者们在写的爽和用的爽之间陷入了两难,但用的不爽是绝对不行的(你懂的),于是乎也不知是哪一位大佬突然在某天拍案而起,操作dom开销那么大,我操作js不就行了!
三大框架时代——虚拟DOM的狂飙
操作dom开销那么大,我操作js不就行了?
这个疯狂却又合理想法一旦产生就再也无法扑灭,因为这太适合js了。js在诞生之初就可以操作DOM的本能,而js本身的能力又那么强大,模板语法的缺点它完全可以消化和包容,包括但不限于差量更新 ,批量更新等一系列梦寐以求的可以提高渲染性能的手段终于可以提上日程了。
于是,新世界的大门就此打开。
至此,我们来明确一点,虚拟DOM,正是作为数据和真实DOM之间的缓冲诞生。
有了虚拟DOM,前端世界迸发了更大的活力,Angular,React,Vue这三种基于虚拟DOM的框架在大浪淘沙之后终于存活了下来,成为了新世界的主流。
虚拟DOM的优势到底是什么,是什么让它成为了时代的选择?
正如之前说的,虚拟DOM是作为数据和真实DOM之间的缓冲层诞生的。数据通过某种模板/语法糖/函数将数据转换为虚拟DOM,从而实现在js层面的控制。这个转换,在vue里是<template>
,在react中是JSX
。
react中<div>虚拟DOM</div>
的虚拟DOM形式
这让“差量更新”这一至关重要的功能得以应用,新的虚拟DOM树会和旧虚拟DOM树以diff
算法进行比较,形成一个“补丁”,最后用batch
方法将这个补丁打到需要更新的节点上。差量更新既能让开发者感受到舒适的开发体验,又保持了优异的性能。可谓是写的爽和用的爽的双赢典范。
在“差量更新”这一至关重要的功能得以应用之外,还有一个重要的功能是“批量更新”,即用户在短时间内dom进行高频操作的时候会取最后一次的操作结果,以此避免大量的大成本的性能消耗。这里就涉及batch
方法的内容了,他会缓冲每次生成的补丁集,然后把它们放入一个队列中,算出一个渲染结果后再将结果交给渲染函数,以此实现批量更新。
有那么多优势的情况下,人们下意识的认为虚拟DOM的性能优于模板语法,可这真的对吗?
我们不妨回头看看两者的流程区别,模板语法的流程是 数据->模板->真实dom,虚拟DOM的流程是数据->模版/算法/语法糖->虚拟dom->一系列js操作->真实dom。
你突然发现,虚拟DOM的流程明明长了那么多,为什么大家会说它的性能更好?
其实这个问题的答案应该分环境来讲:在数据量少的情况下,两者性能相差无几。数据量多的情况下,若是数据改变大,接近于全页面更新,模版语法性能更好。在局部更新为主的环境下,虚拟DOM的性能更好。而这,恰好是最频繁的线上情况。
在现代的模板语法中,模板语法同样具有将数据转化为虚拟DOM的能力。所以大家别下意识的认定虚拟DOM的性能一定优于模板语法。这种说法是有谬误的,应该说在特定情况下,应用虚拟DOM有着比单独应用模板语法更优越的性能。
更进一步来说,虚拟DOM提供了一个同一化的能够操作dom对象的入口,在如今越来越提倡多端代码一套共用的今天,虚拟DOM可以是IOS界面,可以是安卓界面,可以是小程序,可以是其他....它需要的只是不同的转译,这就实现了真正的多端通用。
至此,我们来总结一下虚拟DOM的优势到底是什么。
1.它让用户用的很爽,因为它解决了页面性能优化的关键性痛点。
2.他让开发者开发的很爽,爽意味着迭代的效率,意味着社区维护的积极性。
3.他能解决多端复用统一性的问题,这可以减少成本,而减少成本,意味着占有市场。
是什么推动了前端职能和功能的高速发展?
从之前的阐述中,我们不经惊叹前端技术栈的日新月异。从一个静态页面切图仔到如今的大前端大全栈微前端,仿佛前端的大厦在短短的十几年里尘埃落定,熠熠生辉。
那么是什么推动了前端职能和功能的高速发展呢?
这个问题我想认真了解前端发展史的同学一定会若有所思。我们不如换个更落地的问题。
什么样的技术/项目,会成为受欢迎的项目,甚至引领环境的发展呢?
从我的角度来说。所有能称之为成功的技术/项目都有一个特点——性能和产能的双赢
JQ解放了开发者的生产力同时拥有一般原生api无法比拟的性能,于是它是一个时代的标志。
虚拟DOM,精准解决了多交互情况下性能不佳的痛点,又让开发者从DOM的迷锁中解脱,只去关注数据和数据的变化,更为多端统一打开了大门
Node.js,让js也能写后端,划时代的理念
...
作为一名开发者,技术的提升会随着精力的消逝而慢慢暂缓,就像我在开头的话中说的,P7以上的大部分人可能他们的技术实力没有太大的差别,差别在于他们对于技术的发展方向,对项目的未来展望有着不同的见解和理解。当我们走到了一定的工作年限,不免会承担一定的领导职责。于是此时,性能和产能将会是我们最需要关注和关心的项目指标。此时,前瞻性,系统性的思维就必不可少。
这篇文章其实没有阐述太多的技术细节,我想向大家传达的是一种理解技术的思路。这种技术,这个项目为什么会火?在它的背后有没有什么实现的动机?它的前身或者前辈们曾踏足的道路是如何的?若是展望未来,你会有什么想法?它的存在解决了什么痛点?当我们以超脱技术的视野去俯瞰技术本身,我们可能能比单纯的技术实现收获的更多。
最后,前端这个职业从微尘发展至今,我们有理由相信它的未来在星辰大海,再次与诸君共勉。