前端发展历史

# 前端发展历史

  • 1990 年,第一个 Web 浏览器诞生了。
  • 1994 年,网景公司发布第一个商业浏览器 Navigator。
  • 1995 年,网景工程师 Brendan Eich 用 10 天时间设计了 JavaScript,同年微软发布了 IE 浏览器,进而掀起了浏览器大战。
  • 2002 年,IE 在浏览器大战中赢得胜利,IE6 占有率超过 96% 。
  • 2004 年,Google 发布了 Gmail,用户可以在不刷新页面的情况下进行复杂的交互,之后,Ajax 逐渐成为网页开发的技术标准,也不断地被应用于各种网站。

Ajax 这个技术让我们可以异步的获取数据并且刷新页面,从此前端不再受限于后端的模板,这也宣告了 Web2.0 时代正式到来。至此,前端工程师也正式作为一个独立工种出现。

  • 2009 年 AngularJS 和 Node.js 的诞生,也宣告前端工业革命的到来。工业时代

# 前端三大框架

在前端 MVVM 模式下,不同框架的目标都是一致的,就是利用数据驱动页面,但是怎么处理数据的变化,各个框架走出了不同的路线。

  • Angular 1 就是最老套的脏检查。所谓的脏检查,指的是 Angular 1 在对数据变化的检查上,遵循每次用户交互时都检查一次数据是否变化,有变化就去更新 DOM 这一方法。
  • Vue 1 的解决方案,就是使用响应式,初始化的时候,Watcher 监听了数据的每个属性,这样数据发生变化的时候,我们就能精确地知道数据的哪个 key 变了,去针对性修改对应的 DOM 即可
  • React 框架,在页面初始化的时候,在浏览器 DOM 之上,搞了一个叫虚拟 DOM 的东西,也就是用一个 JavaScript 对象来描述整个 DOM 树。我们可以很方便的通过虚拟 DOM 计算出变化的数据,去进行精确的修改。

浏览器操作 DOM 一直都是性能杀手,而虚拟 DOM 的 Diff 的逻辑,又能够确保尽可能少的操作 DOM,这也是虚拟 DOM 驱动的框架性能一直比较优秀的原因之一。

# Vue 与 React 的对比

Vue 和 React 在数据发生变化后,在通知页面更新的方式上有明显的不同,通俗的来说,就是:在 Vue 框架下,如果数据变了,那框架会主动告诉你修改了哪些数据;而 React 的数据变化后,我们只能通过新老数据的计算 Diff 来得知数据的变化。

这两个解决方案都解决了数据变化后,如何通知页面更新的问题,并且迅速地获得了很高的占有率,但是他们都碰到了性能的瓶颈:

  • 对于 Vue 来说,它的一个核心就是“响应式”,也就是数据变化后,会主动通知我们。响应式数据新建 Watcher 监听,本身就比较损耗性能,项目大了之后每个数据都有一个 watcher 会影响性能。
  • 对于 React 的虚拟 DOM 的 Diff 计算逻辑来说,如果虚拟 DOM 树过于庞大,使得计算时间大于 16.6ms,那么就可能会造成性能的卡顿。

React 为了突破性能瓶颈,借鉴了操作系统时间分片的概念,引入了 Fiber 架构。通俗来说,就是把整个虚拟 DOM 树微观化,变成链表,然后我们利用浏览器的空闲时间计算 Diff。一旦浏览器有需求,我们可以把没计算完的任务放在一旁,把主进程控制权还给浏览器,等待浏览器下次空闲。

Vue 2 大胆引入虚拟 DOM 来解决响应式数据过多的问题。这个解决方案使用虚拟 DOM 解决了响应式数据过多的内存占用问题,又良好地规避了 React 中虚拟 DOM 的问题, 还通过虚拟 DOM 给 Vue 带来了跨端的能力。

  • 响应式数据是主动推送变化,虚拟 DOM 是被动计算数据的 Diff,一个推一个拉,它们看起来是两个方向的技术,但被 Vue 2 很好地融合在一起,采用的方式就是组件级别的划分。
  • 对于 Vue 2 来说,组件之间的变化,可以通过响应式来通知更新。组件内部的数据变化,则通过虚拟 DOM 去更新页面。这样就把响应式的监听器,控制在了组件级别,而虚拟 DOM 的量级,也控制在了组件的大小。

除了响应式和虚拟 DOM 这个维度,Vue 和 React 还有一些理念和路线的不同,在模板的书写上,也走出了 template 和 JSX 两个路线。

  • React 的世界里只有 JSX,最终 JSX 都会在 Compiler 那一层,也就是工程化那里编译成 JS 来执行,所以 React 最终拥有了全部 JS 的动态性,这也导致了 React 的 API 一直很少,只有 state、hooks、Component 几个概念,主要都是 JavaScript 本身的语法和特性。

  • 而 Vue 的世界默认是 template,也就是语法是限定死的,比如 v-if 和 v-for 等语法。有了这些写法的规矩后,我们可以在上线前做很多优化。Vue 3 很优秀的一个点,就是在虚拟 DOM 的静态标记上做到了极致,让静态的部分越过虚拟 DOM 的计算,真正做到了按需更新,很好的提高了性能。

# Vue3新特性

# 响应式系统

Vue 2 的响应式机制是基于 Object.defineProperty() 这个 API 实现的,此外,Vue 还使用了 Proxy,这两者看起来都像是对数据的读写进行拦截,但是 defineProperty 是拦截具体某个属性,Proxy 才是真正的“代理”。

Object.defineProperty(obj, 'title', {
  get() {},
  set() {},
})

当项目里“读取 obj.title”和“修改 obj.title”的时候被 defineProperty 拦截,但 defineProperty 对不存在的属性无法拦截,所以 Vue 2 中所有数据必须要在 data 里声明。

而且,如果 title 是一个数组的时候,对数组的操作,并不会改变 obj.title 的指向,虽然我们可以通过拦截.push 等操作实现部分功能,但是对数组的长度的修改等操作还是无法实现拦截,所以还需要额外的 $set 等 API。

new Proxy(obj, {
  get() { },
  set() { },
})

需要注意的是,虽然 Proxy 拦截 obj 这个数据,但 obj 具体是什么属性,Proxy 则不关心,统一都拦截了。而且 Proxy 还可以监听更多的数据格式,比如 Set、Map,这是 Vue 2 做不到的。

当然,Proxy 存在一些兼容性问题,这也是为什么 Vue 3 不兼容 IE11 以下的浏览器的原因,还好现在 IE 用的人不多了。

# 自定义渲染器

Vue 2 内部所有的模块都是揉在一起的,这样做会导致不好扩展的问题,刚才我也提到了这一点。Vue 3 是怎么解决这个问题的呢?那就是拆包,使用最近流行的 monorepo 管理方式,响应式、编译和运行时全部独立了

在 Vue 3 的组织架构中,响应式独立了出来。而 Vue 2 的响应式只服务于 Vue,Vue 3 的响应式就和 Vue 解耦了,你甚至可以在 Node.js 和 React 中使用响应式。

渲染的逻辑也拆成了平台无关渲染逻辑和浏览器渲染 API 两部分

# 全部模块使用 TypeScript 重构

类型系统带来了更方便的提示,并且让我们的代码能够更健壮。

Vue 2 那个时代基本只有两个技术选型,Facebook 家的 Flow.js 和微软家的 TypeScript。Vue 2 选 Flow.js 没问题,但是现在 Flow.js 被抛弃了。Vue 3 选择了 TypeScript,TypeScript 官方也对使用 TypeScript 开发 Vue 3 项目的团队也更加友好。

# Composition API 组合语法

使用 Composition API 后,代码看起来很烦琐,没有 Vue 2 中 Options API 的写法简单好懂,但 Options API 的写法也有几个很严重的问题:

  • 由于所有数据都挂载在 this 之上,因而 Options API 的写法对 TypeScript 的类型推导很不友好,并且这样也不好做 Tree-shaking 清理代码。
  • 新增功能基本都得修改 data、method 等配置,并且代码上 300 行之后,会经常上下反复横跳,开发很痛苦。
  • 代码不好复用,Vue 2 的组件很难抽离通用逻辑,只能使用 mixin,还会带来命名冲突的问题。

我们使用 Composition API 后,虽然看起来烦琐了一些,但是带来了诸多好处:

  • 所有 API 都是 import 引入的(现在我们的例子还没有工程化,后续会加入)。用到的功能都 import 进来,对 Tree-shaking 很友好,我的例子里没用到功能,打包的时候会被清理掉 ,减小包的大小。
  • 不再上下反复横跳,我们可以把一个功能模块的 methods、data 都放在一起书写,维护更轻松。
  • 代码方便复用,可以把一个功能所有的 methods、data 封装在一个独立的函数里,复用代码非常容易。
  • Composotion API 新增的 return 等语句,在实际项目中使用 <script setup> 特性可以清除, 我们后续项目中都会用到这样的操作。

# 新的组件

Vue 3 还内置了 Fragment、Teleport 和 Suspense 三个新组件。这个倒不难,项目中用到的时候会详细剖析,现在你只需要这仨是啥就行,以及它们的用途即可:

  • Fragment: Vue 3 组件不再要求有一个唯一的根节点,清除了很多无用的占位 div。
  • Teleport: 允许组件渲染在别的元素内,主要开发弹窗组件的时候特别有用。
  • Suspense: 异步组件,更方便开发有异步请求的组件。

# 新一代工程化工具 Vite

Vite 主要提升的是开发的体验,Webpack 等工程化工具的原理,就是根据你的 import 依赖逻辑,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。

你可能不知道,现代浏览器已经默认支持了 ES6 的 import 语法,Vite 就是基于这个原理来实现的。具体来说,在调试环境下,我们不需要全部预打包,只是把你首页依赖的文件,依次通过网络请求去获取,整个开发体验得到巨大提升,做到了复杂项目的秒级调试和热更新。

Vite 的工作原理,一开始就可以准备联调,然后根据首页的依赖模块,再去按需加载,这样启动调试所需要的资源会大大减少。

上次更新: 2022/4/24 21:42:08