最近写了太多技术文章,今天想写一点简单的东西。当一个新人问:如何开始学前端?很多知乎人都会发这样的脑图:
新人表示很淦,并点上右上角的“关闭”。
个人也有很讨厌找学习资源的时候,老手总是给一些“大而全”但对新人极度不友好的答案,我知道发图的人可能真的想为了新人好,但是这种图除了增加焦虑,没有大多作用。
但是前端发明了那么多的“名词”,不去了解又会一头雾水。这篇文章就给大家盘一盘前端开发那些“名词”的由来。
万维网
让我们把时间倒回 1989 年。一个英国佬 Tim Berners-Lee
发现他们实验室 CERN(European Organization for Nuclear Research)[1] 的资料越来越难管理了,资料很容易“丢失”,比如文件名忘了、或者维护这个文件的人走了,那么这些文件可能就永远“消失”在茫茫资料中了。
这个 CERN 实验室其实是研究物理的,并不是搞计算机的!但是当时各个地方的实验室都和这个实验室有合作,就难免要相互分享资料。当资料变得越发庞大的时候就很难管理了。另一个点是 CERN 的全名并不是英文名,而是法文:Conseil européen pour la recherche nucléaire
为此,Lee 提出 “Linked information systems” 的构想,并称为 World Wide Web,也即我们熟知的“万维网”。
两年后,在 1990 年,Lee 又发了第二个提案[2]。最后提出需要2个人在6个月内造出的万维网的想法。最后造出了第一个网站:http://info.cern.ch/。
从上面可以看到,一个简单的 Client-Server 架构已经出现了。把文件放到服务器上,在客户端上访问它。
构建页面骨架
对于学术论文,一般都是有专业格式的,如果只是纯文字展示,换谁也受不了呀。
所以,Lee 受 SGML(Standard Generalized Markup Language) 的启发创建了 **HTML(Hyper Text Markup Language)**。一般常用的标签有:h1~h6, div, header, body, main, footer, table, ul, li, button 等。
代码语言:javascript复制<header>头</header>
<main>身</main>
<footer>脚</footer>
如果你简单尝试写一个 .html 出来,会发现 <article>
和 <div>
的效果是一样的。但是,为了代码更语义化,也即可以让后面的人能看得懂,一般在头部会使用 <header>
,如果是文章则用 <article>
作代码块的区分。如果非要扛:我就喜欢用 div 行不行,那不用扛了,当然行!
美化页面
虽然标签可以做一些简单的样式,但是依然满足不了设计师的样式要求。
为了解决网页的样式问题,Lee 的同事 Håkon Wium Lie[3] 在 1994 年,起草并提出了 **CSS(Cascading Styling Sheet)**。一段简单的 CSS 就可以让页面丰富起来了:
代码语言:javascript复制body {
color: red;
}
很多人可能都知道 CSS 这个玩意,用得理所当然,但是你有没有想过,其实 XML 也可以用来表示样式的,比如在 Android 上就是这么做的。在那个时候, DSSSL[4] 和 FOSI[5] 也曾是浏览器样式的候选人,但是用这两玩意来写样式太麻烦了,所以最后才选择 CSS 作为浏览器的样式书写标准。
现在我们使用 CSS 已经是非常好用了,但在以前 CSS 的标准化之路是充满着坎坷的:
•当 CSS1.0 发布后,几乎没多少浏览器可以支持它。等支持 CSS1.0 的时候 CSS2.0 都已经被放出来了•CSS2.0 增加了更多的样式选择。CSS2.0 是在1997年提出来的,但是在升级为 3.0 的时候经历了大幅的打回、重改,重新提名。直到 2011年 CSS2.1 才作为标准发布出来•到了 CSS3.0,它不像 1.0 和 2.0 那样整个版本升级,而是将样式的升级模块化。现在,虽然我们用的还是 “CSS3.0”,但是其实某些模块已经可以算是 4.0
CSS UI 库
写了很多次 CSS 后,前端工程师们发现,一些好看的 CSS 样式可以拿出来共享呀,比如我写好了一个按钮的样式:
代码语言:javascript复制.btn {
...
}
然后,其它人只要复制这个 CSS 到他的项目里,然后在 HTML 用上 CSS 类名,就可以直接用上我写的效果啦。
代码语言:javascript复制<button class="btn">我的按钮</button>
在这段时间里,各种 UI 小组件的 CSS 样式满天飞,比如今天出个按钮的,明天就出了个输入框的。市面上还曾出现过很多类似《XX个好看的UI组件》和《XXXX年最好看的Y个UI组件》的文章。
不久后,开发者就发现另一 个问题:组件样式之间的冲突,比如,按钮的样式影响了自定义按钮的样式。另一个大问题是,单看一个组件的样式挺好看的,但是如果一个网页用了4 5 个别人写好的 CSS 样式,就会显得非常不协调,没有统一感。兼容性也很差。
Twitter 的 Mark Otto 和 Jacob Thornton 也想过这个问题,所以他们开发了 Bootstrap UI 库:
代码语言:javascript复制<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">@</span>
<input type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="basic-addon1">
</div>
把常用的按钮、字体、输入框的一些样式都写好了,还提供示例代码。这个 UI 库发布了之后,几乎所有开发者用过了,毕竟 CSS 终于可以不用自己写了。
当时我的也用 Bootstrap 写过课堂作业,那真是爽啊,一行 CSS 都没写过。
响应式布局
2011年,苹果发布了那款经典的 iPhone4S,标志着智能手机真正成为人们不可或缺的一部分,而手机上访问的需求也同时增加了不少。
当年在手机上看网页,用户都要先进入页面,手动放大,点击对应链接再进入下一个网页,操作非常复杂,而且界面很丑陋。
另一个方面,随着手机进入人们的生活,手机上的 App 也像雨后春笋一样疯狂冒出。这时,程序员就想:就把网页做成 App 的样子不就好看了嘛。
那怎么判断用户用的是手机还是电脑呢?很简单:通过屏幕宽度来判断嘛,而 CSS 的 media query(媒体查询) 正好可以用来解决这个问题:
代码语言:javascript复制/*屏幕宽度在 600px 以内时,背景色显示红色*/
@media only screen and (max-width: 600px) {
body {
background-color: red;
}
}
/*大于 600它时,背景色显示白色*/
@media only screen and (min-width: 600px) {
body {
background-color: white;
}
}
问题又来了:难道每个兼容样式都要写两遍?能不能只写一种样式就能兼容手机和电脑端呢?程序员们开始通过百分比,em,rem,优化布局等方式,使得屏幕变小后样式还是不会乱。
这种写 CSS 的思路就叫做 响应式 布局,兼容了手机和网页。
不过 响应式 也不是万能的。比如今天淘宝页面里的一些花里胡哨的样式就没法兼容手机端,所以现在的做法是做两套网页,电脑端做一套,手机端做一套。区别是:手机端的样式做得像 App 一些,而且功能不会太多,样式也不会很复杂,而且提供“从XX App”打开的按钮,向自家的 App 引流。电脑端更酷炫,功能更强大。毕竟现在应该没人在手机网页上购物吧?
如果你仔细看手机端的网页地址,都会以 m.xxxxx 开头,就表示网页只在手机上看的。
JavaScript 将内容动起来
注意:上面的 HTML、CSS 都不属于编程语言,HTML 是标记语言,而 CSS 是样式表。
现在我们有 HTML 和 CSS 已经可以让页面变好看了,但是页面内容都是定死的。为了能让页面“动”起来,浏览器必须要引入一种编程语言。那就是 JavaScript。
当年,本来有人想把 Java 用作浏览器的编程语言的,但是搞 Java 的 Sun 公司说:没空理你。所以,网景公司 NetSpace 只好自研编程语言。本来新语言想叫作 LiveScript,但是觉得这个名字又平平无其,不能一炮而红。当时 Java 正如日中天,所以,为了做个标题党、蹭个热度,在 12 月发布的时候改名为 JavaScript,并引入了一些 Java 的特性,然而,令人没想到的是,这些特性将会成为前端工程师的噩梦。
JavaScript 除了做一些简单的业务逻辑,比如判断是男是女:
代码语言:javascript复制if (you === 'male') {
console.log('男男')
}
还会操作 HTML,但是 HTML 不是一段文本么?怎么操作呢?实际上当浏览器拿到 .html 文件后,会自动解析 HTML 文本,将其转为为 **DOM(Document Object Modal)**,将普通的文本转换为一棵树的结构。
JS 只需操作 DOM 就可以修改 HTML 的布局和结构了。
代码语言:javascript复制document.getDocumentById('hello').textContent = '我是帅哥' // 内容变成“我是帅哥”
JavaScript 是一个设计极其糟糕的语言,比如 getMonth()
时,如果现在是1 月,返回的则是 0
,而 getHours()
又会准确返回当前的小时数。对于数组的操作又少得可怜,比如没有 unique
, findBy
这些 API。
为了给 JavaScript 的设计擦屁股,一些工具库营运而生:
•jQuery: 提供很多操作 DOM 的 API•moment, dayjs:提供很多操作 Date 对象的 API•lodash:更多像是个工具库
服务端渲染
虽然 JS 能动态修改内容了,但是,在以前发异步请求是一件很麻烦的事情。
Java 程序员想到了一个办法:JSP。反正要访问服务端,不如在你访问的时候,我直接从数据库里把数据读出来,生成一个 HTML 给你不就好了嘛。这种技术就叫做 JSP。
代码语言:javascript复制<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.io.*,java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h2>HTTP 头部请求实例</h2>
<table width="100%" border="1" align="center">
<tr bgcolor="#949494">
<th>Header Name</th><th>Header Value(s)</th>
</tr>
<%
Enumeration headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String paramName = (String)headerNames.nextElement();
out.print("<tr><td>" paramName "</td>n");
String paramValue = request.getHeader(paramName);
out.println("<td> " paramValue "</td></tr>n");
}
%>
</table>
</body>
</html>
被程序员一直喊为 “世界上最好的编程语言” PHP也沿用了这个思路。这类通过服务端动态生成 HTML 的方法就叫
服务端渲染。
但是这上面有两个问题:
1.如果 Java 里有报错的时候,页面会显示整个错误 Stack 给用户,体验非常不友好,而且一崩全崩
1.高度耦合的代码非常不利于维护,比如,第一眼看上面的代码能看出个啥子哦2.一个工程师除了要处理服务端的逻辑、也要考虑样式要怎么写、页面逻辑,职责不明确
虽然上面的 JSP 和 PHP 流行过一段时间,但是程序员们为了分工更明确,都选择了前端程序员管页面开发、而后端程序员管服务端的开发。这种开发模式也被称作 前后端分离。
而前后端的重要沟通桥梁就是异步请求,也即大家现在经常说的 Ajax请求,但是在很久以前,异步请求还是一个实现上特别困难的事情。在那个时候发完请求,页面就不得不重新刷新一遍,用户体验非常差。
JSONP
上面说到的问题在于:浏览器很难在不刷新页面的情况下,向服务器发异步请求来获取内容。
聪明的程序员就开始想:什么东西能发异步请求呢?然后他们发现如果直接创建一个 img 标签并写上 src 就会向服务器发一个 xxx.jpg 的异步请求了:
代码语言:javascript复制<img src="xxx.jpg">
那么如果要发个异步的 Get 请求,可以这样搞呀:
1.偷偷摸摸地创建一个看不见的 img 标签2.把要访问的 url 放到 src 里
代码语言:javascript复制function getData(url) {
var imgEl = document.createElement('img') // 创建 img 标签
imgEl.visibility = 'none' // 把 img 标签变成不可见
imgEl.src = url
document.appendChild(img) // 加在网页上,自动发送 Get 请求
}
但是上面这么又引出下面的问题:
1.不知道什么时候要清理新生成的 img2.请求发了就发了,响应后不知道怎么获取数据3.每次都要写 imgEl.visibility = 'none' // 把 img 标签变成不可见
这句话
为此,程序员再次想了很多办法。
首先,不再使用 img 标签,而使用 script 标签,就可以把第 3 步省略了。
第二步,在全局定义一个函数用于获取 users 信息:
代码语言:javascript复制function getUsers(users) {
console.log(users)
}
在写 url 的时候加一个参数上去:https://www.baidu.com/users?callback=getUsers
。服务端从参数里读取到 getUsers,向浏览器返回 JS 脚本:
getUsers(['Jack'. 'Mary'])
由于刚刚添加的标签是 script 标签,所以等服务器返回后,getUsers(['Jack'. 'Mary'])
就会被马上执行,最终就会将 users
打印出来。这种技术就叫做 JSONP,全称为 JSON with padding。
JSONP 另一个好处是可以实现跨域请求,因为 script 标签可以请求非同源策略的资源并获取返回的数据。但是这非常不安全,服务器很容易被一些恶意的 JS 代码给攻击了。
Ajax
从上面看出来 JSONP 能用但是很不规范,程序员们非常想要一套完善的异步请求机制。
2004 年,Google 在开发 Gmail 和 Map 两个应用的时候,完善了异步请求的机制,并制定了一些标准。
2006年时, W3C 起草了第一份 XMLHttpRequest 的草案,然后不断完善,一直到最后一份草案则在 2016年 被提出,XMLHttpRequest 才成为正式的标准。我们经常所说的 Ajax 请求其实就是使用这个对象来发请求的。下面就是用这个对象发送请求的代码:
代码语言:javascript复制// 生成请求对象
xmlhttp = new XMLHttpRequest();
// 监听请求的状态和返回
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) { // 200 = OK
console.log('成功');
} else {
console.log('失败');
}
}
};
// 打开通道
xmlhttp.open('GET', url, true)
// 发送请求
xmlhttp.send(null);
Ajax 全名是 Asynchronous JavaScript[6] and XML[7],它不是单单一门技术,而是多门技术的全集,所表求的是:在客户端创建异步应用的一系列技术。只不过核心其中一步就是发异步请求,而 XMLHttpRequest 正好可以帮助我们完成这项工作。
Node.js
2009 年,前端另一大飓风席卷了全球。Ryan Dahl 编写了第一个最初版本的 Node.js,使得 JavaScript 除了可以在浏览器里运行,也可以在拥有 Node.js 平台的地方运行,比如自己电脑的终端里。
JavaScript 终于不再是客户端语言,也可以做服务端的开发了。为了更方便做服务端的开发,TJ Holowaychuk 一个国外超级大佬,借鉴了 Ruby 社区的 Rack,开发了 Express.js,一个简易的 JS 服务器框架。
由于 Express.js 提供的功能太简单了,所以,很多开发者不断给这个框架开发各种各样的中间件,使用者可以用这些中间件增强自己服务器的功能,比如 body-parser, cookie-parser, passport 等。
后来,TJ 觉得 Express.js 写得还是不够精简,本来想重构的,但是重构成本太大了,干脆再造一个轮子吧,这个轮子也就是我们熟悉的 Koa.js。
为了让 JS 更好地完成服务端开发的工作,前端开发人员把后端开发的一些工具都造了一遍:
•连接数据库:mysql, mysql2, mongodb•缓存:session, redis•ORM: TypeORM, sequelize•定时任务:node-schedule, cron•...
Serverless
当很多人都开始用 Node.js 的时候,大家又发现一些问题:
1.写完代码,本地跑起来也挺好的,那怎么部署到服务器上呢?2.服务器要怎么买?HTTPS证书从哪里获取?Nginx是个啥?啊,好烦啊,我只想 npm run start
啊
作为前端程序员,平时搬砖就够累了,还要我配置服务器,一剑杀了我算了。
聪明的程序员发现,不管你写 Express.js 还是 Koa.js 不就是写响应函数么?
代码语言:javascript复制app.get('/users', (req, res) => {
res.send('我是帅哥')
})
那我把服务器、证书、域名这些东西都统统给你弄好,你就负责写相应函数和给钱不就很爽了么?这就是 Serverless 的由来。它的好处是不再操心服务器的配置、扩展等琐碎的事情,只需要写好响应函数就好了。而这种“响应函数”也被称为 云函数,Amazon 称此为 Lambda。
这时候有人发现,我自己写好的一些服务,比如收发邮件、数据库的存取也可以作为一种服务对外提供,前端工程师只需要给钱,然后请求我提供的 API 接口就可以享受我的服务啦。这也是很多云厂商另一种收入来源:卖服务。
比如,常见的:语言翻译服务、手机短信发送服务、鉴黄师、图形识别等。
模块化
当工具变得越来越多,Web 应用体积也变得越来越大了。一个大项目里可能有成百上千个 JavaScript 文件,它们之间相互依赖,可怕的是当前没有工具可以告诉你到底哪个文件是最先被执行的。
文件的管理就成了一个大问题,所以以应用需要划分模块。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
代码语言:javascript复制const xxx = require('xxx')
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
代码语言:javascript复制import xxx from 'xxx'
export default xxx
模块化思想的提出大大提高了 JS 程序员的幸福感,所有的 JS 文件都不再是同一层级,而可以分块管理了。对前端的工程化有着不可或缺的作用。
包管理工具
有的工程师发现,自己抽象出来的模块其实也可以放在社区让别人使用,比如发异步请求的 axios,工具库 lodash 等。这就需要一个中心仓库来存放这些库了,同时也需要一个包管理工具来管理包的发布、安装、升级等。
目前 npm 就是使用最多的包管理工具,当电脑里装了 Node.js 后,npm 也会一并装上。不过使用 npm 在国内下载时会很慢,一般推荐使用 yarn 这个包管理工具,速度更快。
工程化
模块拆分使得写代码时候爽了,但是如果把这些 JS 文件都引入到一个 HTML 上是不是太恐怖了?一个 HTML 里有 1000 个 script 标签,比内容还多也有点反人类了吧。CSS 文件同理。
为了解决这个问题,前端工程师提出了 bundle 这个概念——不管你的模块多乱,多分散,最终通过一个工具,直接转换为1 个 .js 文件。这样的工具就叫做打包工具。
在 2016 年,Grunt,一个 JavaScript Task Runner 被制作出来了,开发者可以编写自己的任务,然后流水线地执行。这已经有了工程化的雏形了。
但是 Grunt 的打包速度太慢了。工程师们受不了了,又造了一个 Glup 的打包工具,功能差不多,但是一个字快!
与此同时,另一只巨兽也在悄然进化——Webpack。
Webpack 以其强大的功能、高灵活配置度的特性直接抢夺了 Glup 的市场,很多人都纷纷用起了 Webpack。
随着 Webpack 的功能不断增强,开发者的要求也不断提高,市面上充斥着大量的 Loader 和插件:
•热加载•代码混淆•代码压缩•精简代码、TreeShaking•loader: file-loader, css-loader, vue-loader•...
Webpack 的另一个问题是,不同环境需要不同的 Webpack 打包配置,导致 Webpack 的配置越来越繁琐,前端工程师除了平常写代码之外,还要负责维护 webpack.config.js
的配置项目。
而 Parcel 的出现正好打破了这一局面,使用 Parcel 就像使用 iPhone 一样,不用太多配置可以马上跑出小网页。但是,治本不治根,在大型项目面前,还是没办法解决繁琐配置的问题。所以,Webpack 依旧是占领市场的巨头。
使用 Webpack 的另一个问题是本地开发打包很慢,Webpack 一般先打包构建再启动开发服务器。而 Evan You 则想到另一个方法:先启动开发服务器,当代码执行到模块加载时再请求对应模块的文件。加快本地开发时的打包编译速度,然后造出了 Vite。目前 Vite 还是个新生儿,可以关注一波,看看以它以后会迸发出什么好玩的东西。
CSS 预处理器
CSS 让人诟病的一点是不够简洁,很多东西不能复用。比如:
代码语言:javascript复制.container {
background: red; /*背景为红色*/
}
.container .title {
color: red; /*标题字体为红色*/
}
为什么不写成这样呢:
代码语言:javascript复制.container {
backtround: red; /*背景为红色*/
.title {
color: red; /*标题字体为红色*/
}
}
遗憾的是,浏览器只认识 CSS,不认识上面这样的写法。程序员又开始思考了:其实我不用浏览器认识第二种写法,我只要把第二种写法在打包的时候转换成 CSS 不就行了嘛。有了打包工具的加成,这件事我觉得能成!所以,第二种高级写法被称为 CSS 预处理器,即这些写法要先被处理成 CSS,然后再以普通 CSS 使用。
于是,在 2007 年,Sass 诞生了,借鉴了 Ruby 社区的 Sass 语法,但是前端程序员比较傲娇:凭啥要跟你叫一个名,就叫为 Scss,不过一般叫法还是叫 Sass,因为 Scss 不会读,哈哈。
后来在 2009 年,Less 被创造出来了,语法 Scss 差不多,没多大差别。
在 2010 年,又一个预处理器 Stylus 诞生了。
目前 Scss 和 Less 用的比较多,Stylus 名气比较小。新手不用担心,这三玩意语法都差不多,大同小异,会一个相当于3个都会了。
进击的 JavaScript
虽然 JavaScript 本来是一个设计非常糟糕的编程语言,但是 JS 它不是咸鱼,它也要努力成为世界上最好的编程语言!
JavaScript 总不能一成不变吧?但是也不能一下子就变天了吧?所以,需要有一个起草方案,审核方案,同意方案,变成规范的过程,这需要大量的人力和专家,那不如做一个 “JS爱好者协会”?European Computer Manufacturers Association (ECMA) 就是类似这样的组织,不过这个组织格局比“JS爱好者协会”的格局大多了,主要任务是是将 Computer System 标准化。
ECMA International 在 ECMA-262[8] 里规范了 JavaScript,可以认为 ECMAScript 就是标准的 JavaScript,所有浏览器都要支持标准的 JavaScript。这个 262 的标准规范从 1997 年开始提出了第 1 版。
从上面可以看到,往后几年就是第 N 代的 ES 标准对应标准的 JavaScript 就是 ECMAScript 201(N-1),比如我们最熟悉的 ES6 其实就是 ECMAScript 2015。而 ES6 对 JavaScript 一个大变革,后面的 ES7,ES8 新增的东西就很少了,所以现在 ES6 其实是 ES6 的一个泛指。
但是你有没有想过一个问题:虽然我用最新的语法做开发,但是用户用的可能还是老版本的浏览器呀,这要怎么办呢?
再次得益于自动化打包工具的兴起,我们可以在开发的时候用最新的语法,在打包生产代码时将新语法都转成旧语法就好了嘛。这种语法的转换听起来就很麻烦,不过,聪明的前端工程师已经帮各位大哥大嫂做好,那就是 Babel。
Babel 发展到了现在,除了做新旧语法的转换,还支持 JSX 语法的转换。
TypeScript
虽然 ES6 新增的语法和 API 已经大幅提升前端程序员的幸福感了,但是 JavaScript 依然是个弱类型的语言:
类型不规范,同事两行泪。当不正确使用类型时:
那能不能强行给 JavaScript 加上类型呢?微软说:可以!由微软牵头,开发了 TypeScript 编程语言和 TypeScript 的编译器,前者其实是 JavaScript 的超集,只是多加了很多料;后者则是负责将 TypeScript 编译成 JavaScript。
注意:这里的 TypeScript -> JavaScript 是不能用 Babel 实现的,因为这一步是编译,而不是新旧语法的替换。TypeScript 不是新语法,是一门正经的编程语言,只不过可以被编译成 JavaScript。
有了 TypeScript,开发者终于享受到了强类型约束的福利了:
代码语言:javascript复制const x: string = '123'
再再再一次得益于自动化打包工具人 Webpack,可以在输出生产代码时将 TypeScript 编译成 JavaScript,如果再加上 Babel,则能进一步转为某个时代的 ECMAScript。
单页应用
在打包工具不断厮杀的同时,单页应用框架也一并发展。
在以前,大部分都是一直在用 jQuery 直接操作 DOM 来更新页面。
每次操作 DOM 时就不得不写一些面条代码。但是这样很麻烦啊,操作 DOM 这一步能不能封装一下,数据更新时自动操作 DOM 去更新页面?
2010 年,Google 研发的 Angular.js 率先实现了 MVVM 想法,即开发者不再需要操作 DOM,可以直接拿数据渲染页面 Modal-View,而页面的变化,比如输入值改变,可以反过来改变数据内容。后来又研发了 Angular2,但是无论是 Angular.js 还是 Angular 本身都太复杂了,借鉴了非常多的后端设计,前端工程师上手难度非常巨大,最终并没有形成大潮流。
注意 Angular.js 和 Angular 是两个不同的东西!
2013 年,一个新的前端框架诞生了——Facebook 的 React.js。React 可以说是一个非常纯净的 JS 框架,没有 Angular 繁琐的内容,开发者只需要关注单向数据流就可以上手撸页面了。最后 React.js 在前端社区流行了起来。
但是 React.js 也有自己的问题:由于 React.js 内容太过于纯净了,本身没有太多的功能,导致开发 React 应用时出现非常多的解决方案,但没有一个方案是最优的,各有各的优缺点。这也导致了 React 社区总是骂战不断、帮派林立、出现各式各样的鄙视链的局面。
比如,在写样式时你可以直接 import 样式文件:
代码语言:javascript复制import 'xxx.css'
const xxx = <button className="xxx">xxx</button>
也可以用 CSS module
代码语言:javascript复制import styles from 'xxx.css'
const xxx = <button className={styles.xxx}>xxx</button>
也可以用 styled-component
代码语言:javascript复制const Button = styled.a`
color: white;
`
const xxx = <Button>xxx</Button>
但是偏偏有人说 styled-components 是高级人,别的都是垃圾,使用 CSS Module 的人就受不了了,说你天天引那么多库来干嘛?
另一个麻烦点是,单向数据流并不是所有人都喜欢的,人们开始怀念 Angular 的数据双向绑定了。
到了 2014 年,那个男人来了,他带着 Vue.js 来了!
Evan You 以前在 Google 和 Meteor 工作过。Vue.js 取了 Angular 和 React 的中间位置,以一种优雅、轻便的姿态登陆前端社区。保留了 Angular 的数据双向绑定,但是摒弃了 Angular 很多复杂的设计和 API,同时不像 React.js 那么纯净,开放很多方便的 API 给使用者爽爽。而且还基于 Webpack 开发了 vue-loader 用来解析 .vue 模板文件。这种 .vue 模板文件和 .html 非常相象,新人上手十分简单。
同时,得益于 Vue 简洁好看的中文官方文档,Vue.js 在中国迅速抢占了小公司的市场。
但是由于 Vue.js 太容易上手了,所以经常被 React.js 社区的一些人觉得写 Vue.js 的人都是新手。而 Vue.js 的人又觉得写 React.js 的人天天折腾这么多“最佳实践”,简直是在浪费生命,而且 JSX 的语法太丑了,不如我的 template 语法简洁好懂。直到现在,Vue 和 React 社区时不时就会爆发小规模的骂战。
另一个问题出现了:原来的 UI 库仅仅提供了简单的 CSS 和原生的 JS,这些 JS 放到单页应用里显得有点冗余了,有的还会报错,因为大多数都是 DOM 的操作。所以 UI 库必须要配合对应的 SPA 框架进行升级。在别的 UI 库升级的同时,饿了么针对 Vue.js 开发 Element UI,而蚂蚁金服则针对 React 开发了 Antd。随后,更多的 UI 库再次涌现,比如 iView、Ant Design for Vue, Ant Design for Angular 等。
总得来说,Angular, React.js, Vue.js 都开发了自己的一套单页应用框架,这套框架最后要做的就是 SPA(Single Page Application) 单页应用。即所有的逻辑都打包在 JavaScript 文件里了,对外,只会看到一个 .html,一个 .css 和一个 .js 文件。
等一下?一个 .html 文件?那不同页面怎么做跳转呢?这就是前端路由的由来了。
前端路由
不妨想想以前是怎么做路由的:用户页是 user.html,首页是 index.html,一个 url 对应着一个文件,也就说我们每次键入 url 时,实际上是访问某个 .html。
而浏览器里有一个监听浏览器地址改变的功能,单页应用的开发者就想了:我只要监听地址 url 的变化,再用 JS 渲染对应的页面组件,不就可以实现前端控制路由了么?这就是前端路由的基本思想。
上面的三大单页应用框架都有自己的前端路由框架:**@angular/router, react-router, vue-router**。
数据管理
单页应用框架另一个问题就是数据的管理,子组件访问的数据都只能靠父组件传过来,如果一个在很深的子组件想要最外层组件的数据时,就不得不把数据从头一路传到尾。
为了应对这种数据很难共享的问题,程序员就想:我把数据都存到一个公共的地方不就行了嘛?要的时候随便拿。
那公共地方是哪里呢?存全局变量?不行啊,会被别人覆盖啊,而且数据改了之后视图不能随之改变呀。所以,工程师又开发一些全局数据管理库:mobx, vuex, redux。
同构渲染
用多了单页框架之后,程序员们又发现问题了,单页最后生成的 HTML 是这样的:
代码语言:javascript复制<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
要知道搜索引擎每天都会用网络爬虫抓取成千上万个网页,分析 HTML 里的内容,以此来提高搜索准确性,这种做法做叫 SEO(Search engine optimization) 搜索引擎优化。但是你看上面这样的结构,搜索引擎根本不知道你这个网页是用来干嘛的。另一个问题是,如果用户网络环境很差,那只要 JS 还没被加载出来,网页永远是一片白色,这非常影响用户体验。这要怎么解决呢?
大家开始怀念当时 JSP、PHP 服务端渲染 HTML 的时候了,因为服务端渲染 HTML 可以马上返回 HTML 结构,页面会先展示一些内容,不至于白屏,而且有了大概的 HTML 结构,搜索引擎更容易做 SEO。
前端工程师想到了:对于一些静态内容,比如商品种类、导航栏内容,其实可以在生成 HTML 的时候就加上,不需要再通过 API 获取了。这样的技术就叫做 SSG(Static Site Generation)。那动态的内容,比如朋友圈列表怎么做呢?初始展示的数据可以先通过服务端先渲染,等用户与页面发生交互,比如点击按钮后再发请求获取数据。这就是 同构渲染。
与传统的服务端渲染不同,同构渲染的服务端也使用 JavaScript 来编写,这样一来前后端都使用上了 JavaScript 了。
同构渲染简单来说就是一份代码,服务端先通过服务端渲染(server-side rendering),生成html以及初始化数据,客户端拿到代码和初始化数据后,通过对 html 的 dom 进行 patch 和事件绑定对 Dom 进行客户端激活(client-side hydration)。
这个整体的过程叫同构渲染。既可以吸取服务端的优点,比如加速首屏渲染,又保留了 SPA 应用的特点,比如前端控制路由跳转,使得跳转时不需要再渲染新 HTML。
Single-SPA
当越来越多项目用上了 SPA 框架后,当公司要把多个项目合并成一个项目的时候,这就很麻烦了,不同项目可能用的框架都不一样,比如用户详情用的是 Vue.js,而首页用的是 React.js,怎么把这两者结合呢?
single-spa 和国内阿里开发的乾坤就是解决这种问题的。本质上相当于造一个更大的“单页的应用”,这个“单页应用”里又会有多个单页的应用。
这种将多个 SPA 整合成一个大 Project 的技术就是 微前端。
自动化测试
随着前端越来越工程化,自动化测试是比不可少的一件事。当造好一些轮子后,就需要接入自动化测试,不然每次修改都要做点点点的人工测试。
最简单的莫过于单元测试,目前单测常用的库有 *ava, jest, moch, sinon, chai 等。而前端又是一个非常依赖环境的工种,经常要用到不同的环境,比如 JSX 环境、浏览器环境,Vue 环境。又催生出很多提供 Mock 环境的库,比如 enzyme 就是用来提供 React 环境的。
同时,为了模拟一些特定的场景,前端还要 Mock 一些东西,比如 localStorage, indexedDB, cookie 等。这些都有不同的库和工具来实现,比如 mock-axios, mock-redux, mock-cookie 等。
上面的只是属于白盒测试的做法,前端能不能直接模拟人工做点点点的操作呢?可以的,这就叫端对端测试、或者叫 e2e 测试,或者叫集成测试。现在比较火的工具是 cypress 和 nightwatch。
不过对于业务经常频繁改动的项目,自动化测试并不是一件好事,改动频繁的业务带来的是变化无常的测试用例。写太多的测试代码,有时候变得事倍功半。
手机H5
随着智能手机发展得越来越快,微信等一些应用里都不得不内嵌一些前端的 H5 页面,比如微信公众号文章,其实就是一个小型网页。
另一个应用场景是,工程师们发现 App 里也可以嵌入 H5 来做简单的展示和交互,这样一来移动端就可以少开发一些内容了。这种内嵌H5页面的应用开发也称为混合开发,开发出来的 App 就是 Hybird App.
现在我们手机上的支付宝、微信、QQ 等都是采用了混合开发的模式,既能使用原生的代码保证流畅度,同时又可以内嵌H5提升开发速度(注意,速度不等于效率)。
低代码
手机上的 H5 写多了之后,前端工程师们又发现很多 H5 都千篇一律,很多都是模板一套,再改个颜色就OK了。
因此,聪明的程序员就想到了能不能用拖拽就生成网页呢?其实拖拽生成网页并不是什么新鲜事,也不难实现,早在 wordpress 的时代已经出现拖拽生成个人博客的工具了。只不过现在用到手机上了。
但是拖拽的问题在于不灵活,有时候非常死板。有些运营人员还是懂一点代码的。所以拖拽工具再度进化成了:可以给别人留个入口做一些简单的自定义事情。这种模板 简单代码做开发的就称为“低代码”开发,通过简单的配置就能自动生成页面。
需求生成代码
除了拖拽,有没有更高级的生成代码工具呢?有!比如,用文字描述场景来生成前端代码,这也是阿里正在做的事情,它们正在研究 P2C (PRD to Code)。个人觉得这是一件好事,对于简单需求来说可以直接生成比什么都快。
有人会担心:这会不会取代前端工程师呢?答案是不可能,也不科学。再厉害的人工智能最终也无法实现设计复杂、灵活多变、千奇百怪、疯狂迭代的产品需求。最终取代的是只会写简单 html、css 的低级工程师。
最后
不知不觉就写好了多东西,可见前端东西真的很多很杂。其实再下写去还能再细写下去,但是文章实在太长了,到此结束了。
这篇文章除了带大家了解前端常见的“名词”之外,还希望大家学前端时不要上来就我要精通XXX。你看前端的发展多么坎坷、都是通过解决一个一个问题才有今天的前端,学习也是同样的道理,先实现一个最 Low 的版本,再慢慢升级自己。
References
[1]
CERN(European Organization for Nuclear Research): https://en.wikipedia.org/wiki/CERN
[2]
第二个提案: http://cds.cern.ch/record/369245/files/dd-89-001.pdf
[3]
Håkon Wium Lie: https://en.wikipedia.org/wiki/Håkon_Wium_Lie
[4]
CSS4 (disambiguation): https://en.wikipedia.org/wiki/Document_Style_Semantics_and_Specification_Language
[5]
Formatting Output Specification Instance: https://en.wikipedia.org/wiki/Formatting_Output_Specification_Instance
[6]
JavaScript: https://en.wikipedia.org/wiki/JavaScript
[7]
XML: https://en.wikipedia.org/wiki/XML
[8]
ECMA-262: https://www.ecma-international.org/publications/standards/Ecma-262.htm