上一节我们对 Web 系统的大致形态大概有了一个勾勒,多年来 HTML5 等技术正蓬勃发展,网页这一“文档”载体的表现力已经非常丰富,前端的工作已不仅仅是写文档与排版那么简单。

Web 不仅用以表达文档,不代表过去关于文档与排版的知识已经不需要关注。在这个体系中,最最基础的,依然还是网页构成的三个要素:HTML / CSS / JavaScript,俗称“前端三剑客”。接下来的故事,也是在此基础之上发生的,下面我们分别展开介绍。

2.1 HTML 与 CSS

HTML 与 CSS 是浏览器展示 UI 的基石,两者谁也离不开谁,这里我们一起介绍。

2.1.1 超文本标记语言 HTML

HTML 全称 HyperText Markup Language 超文本标记语言,顾名思义,这一门语言是专门为表达超文本而生的。浏览器展示一个网页,也是从超文本文档开始。HTML 语言以元素和元素嵌套的语法,表达组成文档的各个元素。

一个基本的 HTML 元素的代码结构如图:
image.png
来源:HTML基础 - MDN

描述元素与元素的嵌套结构的 HTML 代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>My title</title>
  6. </head>
  7. <body>
  8. <h1>A heading</h1>
  9. <a href="https://0xffff.one">Link text</a>
  10. </body>
  11. </html>

浏览器获得 HTML 后,会自动做解析,在内部构建对应的文档树(DOM Tree,DOM 为 Document Object Model 的缩写),在数据结构层面形成如下的树形结构:

image.png
来源:文档对象模型-维基百科

接下来浏览器的渲染引擎根据 文档树 中各个节点的元素的描述,加载对应的资源,然后加载样式、排版绘制页面。
image.png
使用网页调试工具查看加载的资源

这里我们知道了,HTML 的使命,在于向浏览器描述文档各个元素组成的树形结构(即 DOM Tree)。实际上 DOM 元素是多种多样的,关于它们的细节,可以在 HTML 基础 - MDN 中学习。你还可以在 FreeCodeCamp 开启闯关模式,通过精心设计的关卡一步步了解各个 DOM 元素的功能与特性。

2.1.2 层叠样式表 CSS

上一节我们知道了,HTML 描述了一个网页里元素与元素之间组成的 DOM Tree,而对于网页来说,DOM 仅仅是一个骨架,并没有定义各个元素的样式。

想要控制网页的排版与样式(诸如字体大小、颜色、元素布局、动画效果等等),我们通过引入样式表来实现。样式表是一种基于规则的语言,这门语言所表达的,就是指定特定元素的样式的一组规则。一个元素的样式可能有多条规则指定,浏览器按照特定规则选择其中一条规则并执行,多条规则互相覆盖,类似一种层层叠叠的模式。所以又称作“层叠样式表”(Cascading Style Sheets),简称 CSS。

早期网页定义样式表的方式有 CSS 与 XSLT,随着互联网发展,在历史潮流中胜出的是 CSS 的层叠样式表模式,一直沿用至今。 参考:Comparing XSL and CSS - XML.com

浏览器在内部实现上,会 将 CSS 解析成为 CSSOM(CSS 对象模型),然后将 DOM 和 CSSOM 合并生成渲染树(Render Tree),接下来渲染引擎根据渲染树布局各个元素的几何位置,再将各个元素节点绘制到屏幕上,形成我们最终看到的网页效果。

image.png
DOM 与 CSSOM 合并构造 Render Tree 的过程,来源

关于 CSS 的具体细节、入门与学习,推荐以下资源:

  1. MDN 的 CSS 入门学习材料
  2. CSS 布局进阶指南 - ycxu
  3. 书籍:《CSS权威指南(第三版)》

建议新手先从 MDN 的 CSS 入门学习材料 开始学起,建立了基本概念后,可再考虑《CSS 权威指南》等深入解析的书籍。

2.2 JavaScript 基础

早期的 JavaScript 只能用来做一些网页简单交互、表单验证等文档操作。随着 Web 的迅速发展壮大,JavaScript 语言与周边生态越来越完善,成为当下最活跃的编程语言之一,而且不仅仅只在 Web 领域发挥作用。这也意味着,学习 JavaScript 这门语言的投入产出比是非常可观的(无论是学习还是商业的角度)。

暂且抛开细枝末节的语法不看,一门编程语言,更多时候是一种描述问题、思考问题如何解决的思维方式。我们把需解决的问题在该编程语言的语境中描述清晰,形成程序,当这个程序在某个环境下落地运行,就可以驱动问题的解决。学习编程,需要把握的是其中的思维模式(语言特性),在反复地训练中,在肌肉记忆里建立起在具体语言的语法细节的直觉,这一切属于语言基础的层面。程序如何在现有的系统中落地运行,具体需要操作什么接口,实现什么能力,属于运行时的层面。

人的精力有限,不可能光靠一个人的努力就能覆盖这个体系之下所有的细节,站在前人的肩膀上是必要的。对于一些通用的问题,前人在实践过程中形成通用解决方案,抽出了许多相关的公共代码与类库。针对某些特定问题,我们可以直接引入前人做好的包再做二次开发。这样的引用逐渐形成体系,也就形成了我们常说的语言周边生态的概念。编程学习中,如何站在前人的肩膀上,避免重复造轮子,也是需要关注的问题。

针对 JavaScript 语言,接下来将从语言基础运行时周边生态的角度,展开具体的学习指引。知识本质是一个网状结构,网络中点与点之间有着千丝万缕的联系,是无法完全割裂的。这也就意味着,各个角度推荐的学习材料中,也会穿插不少其他角度的内容。于读者而言,希望这里的文字,可以帮助你对 JavaScript 这门语言建立起概念,让你对进一步如何探索心中有数。想要达成真正的学习,最重要的还是要动起来,全身心地去感受,在不断思考与动手中一点点进步。

2.2.1 语言基础

从语言特性角度来看,JavaScript 与其他编程语言类似,它支持命令式编程、面向对象程序设计、函数式编程等特性,在语法结构上与 C 语言类似(如 if 条件语句、switch 语句、while, do-while, for 循环等),对有 C 语言基础的同学上手起来会比较自然。

另外,JavaScript 本身发展速度快,历史原因遗留了不少的糟粕之处,网上教程十分杂乱,容易把人带偏。对于这种情况,比较建议从一些核心的特性看起。

对于刚入坑的同学,推荐以下教程:

  1. MDN 的 JavaScript 入门教程

MDN 提供的入门教程,主要关注如何入手 JavaScript,结合 DOM 和 CSS 为页面增加交互逻辑,在语言基础与运行时角度都有涉及。

  1. 《现代 JavaScript 教程》

从最新 JavaScript 标准出发的入门教程,专注于 JavaScript 语言本身,适合针对 JS 语言本身的把握,运行时环境角度涉及较少。

有了一定的基础后,进阶的学习可以参考以下书籍:

  1. 《JavaScript 高级程序设计》
  2. 《你不知道的 JavaScript》
  3. 《JavaScript 忍者秘籍》
  4. 《ES6 入门教程》
  5. 《JavaScript语言精粹》

2.2.2 运行时

如开头所说,运行时是程序落地的一个重要成分。通俗的话来表述来说,学语言就像学开车,语法基础相当于你的开车技能,运行时相当于你对车子的了解。语法只有与运行时结合起来,才拥有驱动现实流程的意义。

JavaScript 的运行时(运行环境)主要分两大类,浏览器环境操作系统。前者是 JavaScript 作用于网页文档的机制;后者是 JavaScript 的能力得到进一步扩展的结果,借助 Node.jsQuickJS 等运行时,JavaScript 也具备了调用操作系统 API 的能力。由于浏览器本身也是操作系统之上的应用软件,所以在某种意义上来说,浏览器环境提供的能力也与操作系统关系密切,一切的根本,也在于操作系统的知识。

针对浏览器环境,这里也列举一些需要关注的运行时能力,完整的 API 接口可以参考 MDN 的文档

  1. DOM API
  2. 浏览器事件
  3. 文件系统
  4. 网络相关API:Fetch APIXMLHttpRequestWebSockets
  5. Canvas 与 WebGL API
  6. 媒体影音类 API,如 HTMLMediaElementWebRTC
  7. 地理 API
  8. 事件循环机制

浏览器环境相关的知识学习,写的较系统的书籍推荐:

针对操作系统本身的环境,我们大致需关注这几块:

  1. 标准输入输出 standard input/output
  2. 文件系统
  3. 计算机网络
  4. 进程 / 线程等

操作系统与网络相关学习资源推荐:

  1. 《深入理解计算机系统》(CSAPP)
  2. 《图解 HTTP》 / 《图解 TCP/IP》
  3. 《计算机网络》 - 谢希仁

JavaScript 与操作系统相关的交互,主要在于 Node.js 封装的模块与接口,我们可以在需要的适合结合 Node.js 官网的 API 文档 学习具体的操作细节。

2.2.3 周边生态

JavaScript 发展多年,已积累了不少针对各个领域的类库,如针对 DOM 操作的 jQuery,工具库 lodash,3D渲染引擎 Three.js,React,Vue.js 等等。JavaScript 语言的开发模式,也从复制粘贴、script 标签手动引入代码的刀耕火种时期,逐步发展到以 npm 包管理器(node package manager)+ 打包工具 构成的庞大生态系统。

在 npm 生态中,每一个开发者都可在 npm 的 registry 注册与发布自己的模块,用户只需简单地运行 npm install 等命令,即可下载安装对应模块。浏览器原生不支持直接引用 npm 下的模块,这时候需利用 Webpack 等打包工具,我们可以很方便地把这些引入的第三方模块打包起来,实现浏览器端引入 npm 生态下的模块的需求。

关于 npm 生态的入门,我们可以从这几个问题出发去探索:

  1. npm 是什么?
  2. 如何在 npm 生态中寻找想要的模块?
  3. npm 的依赖是怎么管理的?
  4. npm 社区发布模块的约定?

针对这些问题的具体资料,可以参考《现代 Web 开发魔法全书》的 npm 生态系统 部分。

2.3 框架与类库

原始的 HTML / CSS / JavaScript 主要是为 表达文档 设计,而如今 Web 的重心渐渐偏向 GUI 应用开发 的范畴,针对文档表达的设计,已难以承载更复杂的需求。为适应 Web App 的需要,在上一小节我们提到的 npm 生态下,已涌现出众多 GUI 交互相关的框架与类库可供我们使用。各个框架/库的背后是各自不同的设计思想,群雄逐鹿,到现在前端 GUI 领域已由 ReactVue.jsAngular 三分天下。

虽说各个框架的设计思想不一,但它们都有着相似的目标:以组件为单位构建 UI。通过组件的抽象,避免不同功能的状态和逻辑相互耦合,方便维护与复用代码。

这些交互式 GUI 框架及其生态(路由分发、状态管理、通用组件等…)的学习,我们可直接从官网文档出发:

  1. React
  2. Vue.js
  3. Angular

上述的框架主要解决的是 DOM 结构与数据之间互相映射的问题,构建 UI 依然还需要做许多的工作,生态中也沉淀了不少相关的类库。比如说针对 UI 的样式,有单纯的 css 样式,也有针对上述三大 GUI 框架生态的组件库,例如 BootStrapBulma Tailwind CSSMaterial-UIAnt Design 等等;针对动画、游戏、绘图与数据可视化等领域也有不少的库,如 Animate.css, Hover.css, Velocity.js, React Motion, Three.js, Egret, Cocos, Echarts, Recharts, D3.js 等等等等…

篇幅有限,这里所能提到的,仅仅是冰山一角,更多领域与细节,待你入门后可以自己再进一步发掘(对入门者而言,上面提到的书也够玩很久了)。

2.4 工具链

现代的前端开发不仅是简单的页面与脚本的编写,还有代码复杂后的各种管理与兼容问题。以 Node.js 为核心,渐渐地形成了一系列打包编译构建工具。

  1. 包管理器:npm, yarn
  2. 打包构建:webpack, Parcel, Rollup.js, Vite, esbuild
  3. 语法转译,兼容:Babel, browserslist, postcss, node-sass
  4. 团队编码规范:ESLint, Prettier
  5. 类型扩展:TypeScript

通常现代前端开发的工具链由这三大部分组成(参考 React 文档):

  1. 包管理器
  2. 打包构建工具
  3. 编译器

日常开发较简单的页面,我们可以用零配置的 Parcel 一键实现打包,有更多的需求,再考虑自己折腾 webpack 配置。若你是基于框架开发 Web App,一般用框架提供的脚手架工具会更好,如 Create React App, Vue Cli, Angular Cli 等,社区会针对脚手架做更多的优化。

TODO: deno
TODO: dexteryy 的文章

2.5 衍生产物

第一节我们提到,Web 的表现力越来越丰富,UI 还原的能力与开发效率已大大超过纯客户端开发,而且 Web 天然具备跨平台优势,越来越多应用正考虑通过 Web 技术实现。

目前 Web 相比于原生客户端存在两大劣势:

  1. 缺少操作系统原生的 API
  2. 性能相比客户端原生处于劣势

缺少原生 API 能力的问题,可通过定制运行时环境的方式补齐;性能的问题,如今的 PC、手机处于性能过剩状态,加上 Chromium blink 内核与 V8 JavaScript 引擎的加持,JavaScript 构建应用的性能损耗已能被接受。基于 Web 技术构建客户端也变得可行,渐渐地 Web 也可以作为一个承载业务 UI 的容器而存在。

接下来也会从桌面软件与手机应用的角度介绍 Web 技术在客户端开发领域的应用,还有这几年流行的 PWA 与小程序究竟是什么一回事。

2.5.1 桌面软件

Web 技术开发客户端应用的实现,目前以 Chromium 与 Node.js 结合的方案为主。Chromium 负责 UI 渲染的部分,Node.js 则负责与操作系统的文件系统、进程、各种 native API 的对接,两者互相配合,满足客户端应用的需求。

目前按此思路实现的框架有 ElectronNW.js,两者在实现上有所区别:NW.js 将 Chromium 与 Node.js 的上下文合并,可以在浏览器上下文互相调用;Electron 利用 Node.js 的 node_bindings 功能实现与 Chromium 的整合,Chromium 和 Node.js 彼此相互独立,框架也可以随 Chromium 和 Node.js 的更新而更新。

两者均在生产环境有成熟的应用(目前 Electron 占主导),举一些例子:

更多链接参考:

2.5.2 手机应用

目前基于 Web 技术构建手机应用的方案主要有两类:一类是 WebView 为核心的 Hybrid App;一类是在 JavaScript 编写业务逻辑,Native 层提供组件渲染的机制,JavaScript 核心通过 bridge 与 Native 通信实现 UI 渲染,如 React Native。

Hybrid App 以 WebView 为中心承载业务逻辑,另外提供了 Native 的通信机制,Web 无法实现的部分通过 Native 端实现,这其中存在 Web 与 Native 互相调用的额外开销,影响体验。

React Native 是 React 生态的一部分,它将 Native 的组件也以 React 组件的形式做抽象。Native 的各个原生组件也可以在 JavaScript 层面以 React 组件的形式存在,供 React 统一渲染。在这个体系下,React 的开发模式、npm 生态中的各种类库也可应用其中,Web 开发者可以复用现有的 React 技能,减小熟悉新平台的学习成本。目前在 Facebook、QQ、百度、京东等 App 有应用(来源)。

近年来 Google 推出了新的 UI 框架 Flutter,基于 Flutter 可以用同一套代码编译成 Android、iOS、桌面、Web 应用。虽然不是 Web 技术,但它的设计与实现思想都与 Web 关系密切,也是一个充满潜力的方向。

2.5.3 Progressive Web Apps

在 HTML 5 与前端 GUI 框架生态的不断完善下, Web App 在体验上已有重大的突破,但还存在一些问题:Web App 重度依赖网络,启动时面临下载的网络延迟;在形式上依赖浏览器作为入口,与操作系统融合还存在着较多欠缺之处。这些问题在网络稳定、支持多任务的桌面端并不明显;但在移动端脆弱多变的网络连接、全屏交互的交互下,这些问题直接影响了用户的体验。

针对这样的问题,浏览器开发者们提出了 Progressive Web Apps(简称 PWA) 的概念,通过一系列的浏览器新特性,在保持向下兼容的同时,解决上述两点体验问题。

一个 PWA 应用的特点(摘自 渐进式 web 应用介绍 - MDN):

  • Discoverable, 内容可以通过搜索引擎发现。
  • Installable, 可以出现在设备的主屏幕。
  • Linkable, 你可以简单地通过一个URL来分享它。
  • Network independent, 它可以在离线状态或者是在网速很差的情况下运行。
  • Progressive, 它在老版本的浏览器仍旧可以使用,在新版本的浏览器上可以使用全部功能。
  • Re-engageable, 无论何时有新的内容它都可以发送通知。
  • Responsive, 它在任何具有屏幕和浏览器的设备上可以正常使用——包括手机,平板电脑,笔记本,电视,冰箱等。
  • Safe, 在你和应用之间的连接是安全的,可以阻止第三方访问你的敏感数据。

PWA 涉及的技术:

  1. Service Worker
  2. Web App Manifest
  3. Push API (后台消息推送)
  4. Notifications API
  5. Add to Home screen (添加到桌面)

PWA 做的比较好的:Twitter,Facebook,AliExpress,Telegram,星巴克等。

由于 Google 的推送服务在中国大陆被屏蔽,我们无法使用推送的功能(大大削弱了 PWA 的实用性),也导致了 PWA 在中国大陆的推广受阻。目前大陆的 Web 开发中,我们通常把 PWA 中的 Service Worker 能力作为一个加载速度优化的手段来跟进。

相关链接:

2.5.4 小程序

手机用户日常会有许多较轻量或临时的需求(例如线下购物、点餐等),为了这些需求额外下载 App 成本过高,所以这样的需求一般会在 Web App 中解决,不知不觉微信、支付宝等超级 App 扫码打开的 WebView 成为了一个重要的流量入口。

然而像上节介绍 PWA 时提到的网络与 Native 整合的痛点,也同样制约着这里的体验。并且 Web App 的页面切换等操作也相对生硬,涉及到 input 框等控件的交互优化也不尽完善,有各种各样大大小小的兼容坑。综合之下,微信在 2016 年提出了“小程序”的方案来优化这些问题,后续QQ、支付宝、百度、抖音、头条等 App 也模仿微信的设计,推出了各自的“小程序”方案。

小程序诞生的考虑(来源:小程序简介):

  1. 快速加载
  2. 跨平台一致性
  3. 用户体验 - Native 组件与 WebView 结合
  4. 管控方便
  5. 微信 Native API 的整合

可以看到,“小程序”是一种基于 Web 技术定制的 Hybrid App。它与 PWA 的愿景类似,但 PWA 着眼于开放标准,考虑许多通用性、隐私与安全的问题,推进的速度要慢一些。如果说 PWA 是理想化的形态,小程序更多的是当下的现实形态,还是那句话,在通向理想的路上,总会有许多需要向现实妥协的地方,写小程序的过程也一样会伴随着踩很多不同的 WebView 容器兼容的坑。

为了实现强有力的管控,小程序的核心正是将UI渲染层与业务逻辑层分离的双线程架构。小程序的每一个页面都是一个单独的 WebView,这个 WebView 只提供渲染的逻辑。小程序核心通过 xml 与模版定义组件树,原生提供 MVVM 渲染的机制,避免开发者直接操作 WebView;然后通过一个 JSCore 引擎作为逻辑层,在逻辑层设计了对应的生命周期方法和客户端的 Native API,承载业务逻辑。逻辑层与渲染层之间只有纯粹的数据通信,避免开发者乱搞导致失控。
image.png
小程序的双线程架构,来源微信小程序官方文档

为了优化体验,小程序的渲染层部分 UI 组件以原生组件的方式提供,如 input 输入框、map 地图、canvas 画布等等,渲染时原生组件直接覆盖在 WebView 上方,补齐 Web 在 UI 交互层面上的部分缺憾,并想办法在 WebView 之上再盖一层,实现 WebView 的页面元素显示在原生的层级之上。另外它还通过 hack 浏览器内核的方式,让原生组件渲染在 WebView 的 DOM 节点中,从而实现的同层渲染。

由此我们可以发现,所谓小程序,实际上其实是一种戴上了镣铐后的变形 Hybrid Web 应用,掌握了 Web 开发的核心,还有上述提到的小程序基本架构,熟悉小程序开发也是自然而然的事情。

小程序在使用者的角度勉强实现了统一,相对稳定的环境,较低的开发门槛,也伴随着众多的交互相对不复杂的业务都在其上承载,这也伴随着更多的用户价值和商业价值集中在此地。

有关小程序的更多资料与学习,请戳 微信小程序官方文档,其他厂商的小程序,也是一样的套路。

2.6 前端学习建议

如今市面上充斥着各式各样的学习资料,质量参差不齐,面对这样的现状,我们可以从这几点出发:

一些值得阅读的文章与网站推荐:

  1. 2019 年 Web 开发领域概览:现代 Web 开发的现状与未来(JSDC 2019 演讲全文)
  2. Web 开发知识体系梳理:Spellbook of Modern Web Dev(现代 Web 开发魔法全书)
  3. 2021 年 Web 开发现状:迈入现代 Web 开发(GMTC 2021 演讲《字节跳动的现代 Web 开发实践》全文)
  4. MDN 参考文档:MDN Web 文档
  5. Google 的 Web 开发文档:Web | Google Developersweb.dev
  6. Roadmap.sh 的前端部分:https://roadmap.sh/frontend

前端发展十分迅速,这里的一些信息有可能会过时,欢迎各位补充。