最近写了太多技术文章,今天想写一点简单的东西。当一个新人问:如何开始学前端?很多知乎人都会发这样的脑图:
新人表示很淦,并点上右上角的“关闭”。
个人也有很讨厌找学习资源的时候,老手总是给一些“大而全”但对新人极度不友好的答案,我知道发图的人可能真的想为了新人好,但是这种图除了增加焦虑,没有大多作用。
但是前端发明了那么多的“名词”,不去了解又会一头雾水。这篇文章就给大家盘一盘前端开发那些“名词”的由来。
万维网
让我们把时间倒回 1989 年。一个英国佬 Tim Berners-Lee
发现他们实验室 CERN(European Organization for Nuclear Research) 的资料越来越难管理了,资料很容易“丢失”,比如文件名忘了、或者维护这个文件的人走了,那么这些文件可能就永远“消失”在茫茫资料中了。
这个 CERN 实验室其实是研究物理的,并不是搞计算机的!但是当时各个地方的实验室都和这个实验室有合作,就难免要相互分享资料。当资料变得越发庞大的时候就很难管理了。另一个点是 CERN 的全名并不是英文名,而是法文:Conseil européen pour la recherche nucléaire
为此,Lee 提出 “Linked information systems” 的构想,并称为 World Wide Web,也即我们熟知的“万维网”。
两年后,在 1990 年,Lee 又发了第二个提案。最后提出需要2个人在6个月内造出的万维网的想法。最后造出了第一个网站: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 等。
<header>头</header> <main>身</main> <footer>脚</footer> 复制代码
如果你简单尝试写一个 .html 出来,会发现 和
的效果是一样的。但是,为了代码更语义化,也即可以让后面的人能看得懂,一般在头部会使用 ,如果是文章则用 作代码块的区分。如果非要扛:我就喜欢用 div 行不行,那不用扛了,当然行!
美化页面
虽然标签可以做一些简单的样式,但是依然满足不了设计师的样式要求。
为了解决网页的样式问题,Lee 的同事 Håkon Wium Lie 在 1994 年,起草并提出了 CSS(Cascading Styling Sheet)。一段简单的 CSS 就可以让页面丰富起来了:
body { color: red; } 复制代码
很多人可能都知道 CSS 这个玩意,用得理所当然,但是你有没有想过,其实 XML 也可以用来表示样式的,比如在 Android 上就是这么做的。在那个时候, DSSSL 和 FOSI 也曾是浏览器样式的候选人,但是用这两玩意来写样式太麻烦了,所以最后才选择 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 样式可以拿出来共享呀,比如我写好了一个按钮的样式:
.btn { ... } 复制代码
然后,其它人只要复制这个 CSS 到他的项目里,然后在 HTML 用上 CSS 类名,就可以直接用上我写的效果啦。
<button class="btn">我的按钮</button> 复制代码
在这段时间里,各种 UI 小组件的 CSS 样式满天飞,比如今天出个按钮的,明天就出了个输入框的。市面上还曾出现过很多类似《XX个好看的UI组件》和《XXXX年最好看的Y个UI组件》的文章。
不久后,开发者就发现另一 个问题:组件样式之间的冲突,比如,按钮的样式影响了自定义按钮的样式。另一个大问题是,单看一个组件的样式挺好看的,但是如果一个网页用了4 5 个别人写好的 CSS 样式,就会显得非常不协调,没有统一感。兼容性也很差。
Twitter 的 Mark Otto 和 Jacob Thornton 也想过这个问题,所以他们开发了 Bootstrap UI 库:
<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(媒体查询) 正好可以用来解决这个问题:
/*屏幕宽度在 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 除了做一些简单的业务逻辑,比如判断是男是女:
if (you === 'male') { console.log('男男') } 复制代码
还会操作 HTML,但是 HTML 不是一段文本么?怎么操作呢?实际上当浏览器拿到 .html 文件后,会自动解析 HTML 文本,将其转为为 DOM(Document Object Modal),将普通的文本转换为一棵树的结构。
JS 只需操作 DOM 就可以修改 HTML 的布局和结构了。
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。
<%@ 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 的方法就叫 SSR(Server Side Rendering) 服务端渲染。
但是这上面有两个问题:
- 如果 Java 里有报错的时候,页面会显示整个错误 Stack 给用户,体验非常不友好,而且一崩全崩
- 高度耦合的代码非常不利于维护,比如,第一眼看上面的代码能看出个啥子哦
- 一个工程师除了要处理服务端的逻辑、也要考虑样式要怎么写、页面逻辑,职责不明确
虽然上面的 JSP 和 PHP 流行过一段时间,但是程序员们为了分工更明确,都选择了前端程序员管页面开发、而后端程序员管服务端的开发。这种开发模式也被称作 前后端分离。
而前后端的重要沟通桥梁就是异步请求,也即大家现在经常说的 Ajax请求,但是在很久以前,异步请求还是一个实现上特别困难的事情。在那个时候发完请求,页面就不得不重新刷新一遍,用户体验非常差。
JSONP
上面说到的问题在于:浏览器很难在不刷新页面的情况下,向服务器发异步请求来获取内容。
聪明的程序员就开始想:什么东西能发异步请求呢?然后他们发现如果直接创建一个 img 标签并写上 src 就会向服务器发一个 xxx.jpg 的异步请求了:
<img src="xxx.jpg"> 复制代码
那么如果要发个异步的 Get 请求,可以这样搞呀:
- 偷偷摸摸地创建一个看不见的 img 标签
- 把要访问的 url 放到 src 里
function getData(url) { var imgEl = document.createElement('img') // 创建 img 标签 imgEl.visibility = 'none' // 把 img 标签变成不可见 imgEl.src = url document.appendChild(img) // 加在网页上,自动发送 Get 请求 } 复制代码
但是上面这么又引出下面的问题:
- 不知道什么时候要清理新生成的 img
- 请求发了就发了,响应后不知道怎么获取数据
- 每次都要写
imgEl.visibility = 'none' // 把 img 标签变成不可见
这句话
为此,程序员再次想了很多办法。
首先,不再使用 img 标签,而使用 script 标签,就可以把第 3 步省略了。
第二步,在全局定义一个函数用于获取 users 信息:
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 请求其实就是使用这个对象来发请求的。下面就是用这个对象发送请求的代码:
// 生成请求对象 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 and XML,它不是单单一门技术,而是多门技术的全集,所表求的是:在客户端创建异步应用的一系列技术。只不过核心其中一步就是发异步请求,而 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 的时候,大家又发现一些问题:
- 写完代码,本地跑起来也挺好的,那怎么部署到服务器上呢?
- 服务器要怎么买?HTTPS证书从哪里获取?Nginx是个啥?啊,好烦啊,我只想
npm run start
啊
作为前端程序员,平时搬砖就够累了,还要我配置服务器,一剑杀了我算了。
聪明的程序员发现,不管你写 Express.js 还是 Koa.js 不就是写响应函数么?
app.get('/users', (req, res) => { res.send('我是帅哥') }) 复制代码
那我把服务器、证书、域名这些东西都统统给你弄好,你就负责写相应函数和给钱不就很爽了么?这就是 Serverless 的由来。它的好处是不再操心服务器的配置、扩展等琐碎的事情,只需要写好响应函数就好了。而这种“响应函数”也被称为 云函数,Amazon 称此为 Lambda。
这时候有人发现,我自己写好的一些服务,比如收发邮件、数据库的存取也可以作为一种服务对外提供,前端工程师只需要给钱,然后请求我提供的 API 接口就可以享受我的服务啦。这也是很多云厂商另一种收入来源:卖服务。
比如,常见的:语言翻译服务、手机短信发送服务、鉴黄师、图形识别等。
模块化
当工具变得越来越多,Web 应用体积也变得越来越大了。一个大项目里可能有成百上千个 JavaScript 文件,它们之间相互依赖,可怕的是当前没有工具可以告诉你到底哪个文件是最先被执行的。
文件的管理就成了一个大问题,所以以应用需要划分模块。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
const xxx = require('xxx') 复制代码
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
import xxx from 'xxx' export default xxx 复制代码
模块化思想的提出大大提高了 JS 程序员的幸福感,所有的 JS 文件都不再是同一层级,而可以分块管理了。对前端的工程化有着不可或缺的作用。