Web 快速入门:一次点击背后的系统
本页目录
虽然我是原生应用的坚定粉,但是随着微信小程序的出现,还是得了解一下 Web 应用的开发。本文结合 CMU 15417 Web Application Development 这门课,大致梳理一下入门 Web 开发的必备知识及思维方式上需要转变的地方。
更新记录
- 2016.09.27 更新『优秀学习资源』和一些链接
- 2016.09.26 更新『Web 发展历程』与『Web 技术演进』
- 2016.09.25 完成『Web 之难』与『一次点击背后』
Web 之难
对于大多数同学来说,如果没有业余做一些小项目,其实是很难理解 Web 到底是怎么回事儿的,至少我当年就因为『觉得』 Web 很难所以『抗拒』Web 和网络相关的开发。虽然课堂上学过计算机网络,甚至也去实验室捣鼓过路由器交换机,但是真到自己开发的时候,真的是一脸懵逼。现在回过头来看,发现主要有以下原因:
- 教学内容僵硬,硬件部分只讲硬件,软件部分只讲软件,没有联系起来
- 实验设计不接地气,为了实验而实验,没有结合具体的场景来辅助理解相关概念
当然,最重要的原因还是在自己的『好奇心』不足,没有追根溯源把事儿弄明白。底层原理和上层应用的有机结合本来就是非常需要花功夫的,以目前国内高校的现状来看,甚至是『吃力不讨好』的。如果体制内没有变革的动力,那不妨通过外部努力进行自下而上的改变。
看到越来越多非常走心的教程以开源的方式出现在网上,我特别开心。这种开放和分享的精神在中文(互联网)世界里生根发芽,信息流动的过程本身就是一种平等。只要有网络和一颗想要学习的心,不需要缴纳高昂的学费,同样可以学到本领。程序员有程序员改变世界的方式,这就是我开博客写文章的初心了。
Web 之难,主要是简单操作背后所隐藏着的复杂体系,需要有比较完善的计算机及网络相关的知识积累,既需要大局观,也需要懂细节,这对于初学者来说可能就像无法逾越的大山,很多东西只知道怎么做,却不知道为什么要这样做。与此同时,遇到问题也没有解决的思路,就很容易有挫败感。(心疼当年的自己一秒)
希望本文能给在困惑中的同学一点帮助,虽然不能面面俱到,但至少能给出一些有用的提示和方法,出了问题不至于束手无策。
一次点击背后
之前在 caoz 的公众号中看到过,他喜欢出的一道面试题是:
从浏览器地址栏输入网址,到网页彻底打开,中间都发生了什么?
这道题看起来很简单,但其实需要答题者对 Web 的工作过程有整体认识。与此同时,面试官也可以根据具体情况选择一到两个细节进行深入的讨论。只有明白了整个系统运行的机制,之后的调试、测试、优化才不是无源之水。而弄懂这些问题本身,也能体现一个人学习的习惯和对技术的追求,这才是面试最需要体现的东西。
我一直认为,即使是做一个小的模块,也应该把自己放到更大的场景中去思考,这个模块在系统中所处的位置,与其他模块的关系与交互方式等等都是需要去思考的,这样才能写出最有价值的代码,并从中找到之后改进的方向。如果让我去面试,具体的技术细节我反而不会去拘泥太多,毕竟聪明的人很快就能学会,我在意的是候选人能不能真正以网络和计算机的方式去思考问题,这才是最重要的。(原文可以在caoz 的梦呓中查看)
仔细读题,我们应该想到的细节问题是:
- 为什么我们要输入网址而不是其他什么东西?
- 浏览器做了什么事情?
- 网址最后去了哪里?
- 服务器是怎么收到网址的?
- 服务器做了什么事情?
- 不同的页面效果是怎么出来的?
化简一下,就是『客户端』、『网络』和『服务端』。每个部分都有各自相关的技术体系,学习的时候一定要弄清楚自己处于哪一块,比如上面几个问题涉及的相关知识点是:
- 为什么我们要输入网址而不是其他什么东西?
- DNS 服务器的工作原理。具体每个部分的缓存机制,如何依次更新?
- CDN 的工作原理。如何在硬件条件限制的情况下提供最佳服务质量?
- 浏览器做了什么事情?
- Host 文件的作用。应用扩展与劫持。
- 网址最后去了哪里?
- 不同层的网络协议(HTTP, TCP, UDP)
- 路由规则,传播路径,tracert
- 服务器是怎么收到网址的?
- 端口监听
- 静态资源
- 动态请求处理
- 服务器做了什么事情?
- 防火墙
- 缓存
- 数据库
- 负载均衡
- 集群
- 不同的页面效果是怎么出来的?
- javascript
- css/html
一个网址请求背后就有这么多的东西,基本上每一个都可以开一门一学期的课程,初学者忽然面对这样概念饱和的轰炸,真的是九死一生。而这么多知识点,真的要样样精通,不太可能,不过如果有一个完整的概念,学习或工作中涉及具体的某个领域,再根据需要深挖,就能逐步触类旁通,找到技术的感觉。
了解系统原理最直接的好处是:出了问题知道怎么排查。下面保留的是 2016 年博客访问变慢时的经历,托管平台、字体源与排查结论都属于当时环境:
- 梳理架构及流程。我的博客是静态博客,评论系统采用第三方服务,页面部署在 Github 和 Coding 双平台。因为是第三方托管,所以服务器的部分我不需要操心,而客户端的话我在手机和电脑都测试过,也并没有表现出设备相关的特性,于是优先检查网络请求与资源加载,但这还不足以排除服务端和浏览器的问题。
- 因为是静态网站,静态本身已经帮我缩小了可能出现问题的范围,无非是几个可能:页面内容传输、图片传输、字体渲染、图标显示和评论系统加载,逐个进行排查。
- 切换网络后观察到 Github 和 Coding 上的主要资源加载正常(即页面内容和图片的传输正常),发现字体渲染卡住了,检查后是因为所用的字体库不稳定(为了照顾国内使用了 360 的源),所以切换到了中科大的源。
- 问题解决。另外,几天后,360 官方也给出通告,停止了该源的维护,所以我也经历了一次『春江水暖刀先知』了
平时遇到的问题大多是模糊的,只有对系统运行的机制有基本的理解,才能快速找到问题的关键所在,而这就是个人价值的体现。
另一篇类似的文章是技术普及帖:你刚才在淘宝上买了一件东西
Web 的发展历程
通过前面一道面试题,我们大致了解了 Web 的整体框架逻辑,这里我们从历史出发,分门别类介绍一下 Web 相关的技术
这段历史可以沿着几个变化理解:文档通过超链接连接起来;服务器开始动态生成页面;CSS 帮助分离内容与样式;JavaScript 与浏览器接口让页面能够响应交互;异步请求让局部更新不再依赖整页刷新。它们相互叠加,而不是每出现一种技术就完全替代上一种。
HTML 与 CSS
HTML 描述文档结构与语义,CSS 描述呈现方式。XML 是另一种标记语言体系,不能把它直接列为 HTML 的版本。浏览器对标准的实现可能有差异,实际开发要检查目标浏览器的支持情况。
最原始的页面基本都是非常『简陋』的,就是把需要显示的内容显示出来。而随着时代的发展,大家对于外观的要求越来越高,慢慢就出现了样式和内容分离的趋势,也就是 CSS(Cascading Style Sheets) 的出现。
纵观历史,就会发现这样的解耦其实无处不在。为什么每一次解耦都能够带来巨大的进步呢?因为解耦实际上是沟通方式的抽象化,用更加高层的抽象能够表示更多的细节,分工更细,自然挖掘得更深。
DOM - Document Object Model
提到 HTML,就不得不提 DOM 这概念,甚至可以这么说,万变不离其宗,我们看到的五花八门的各种前端技术,实际上都是基于 DOM 这个东西的。DOM 可以认为是一个 HTML 的对象模型,一个表示 HTML 文档的节点树。树中有文档、元素、文本、注释等节点;元素属性通常通过元素接口访问,并不是普通的子节点,我们通过操作不同的节点,就可以展现出不同的页面。
尤其是在动态页面大行其道的今天(但静态仍然有静态的好处,比如本博客就是静态博客),如何看待 DOM,如何操作 DOM 是每个前端框架都必须要回答的问题,比方说 React 提出的 Virtual DOM,就是希望优化传统对 DOM 操作可能带来的各种问题。
Javascript
如何去操作 DOM?如何在页面中展现动态效果呢?这就离不开基于浏览器的脚本语言 - JavaScript 了。它负责页面逻辑,DOM 等浏览器接口则提供操作文档的能力。语言和宿主接口是两层不同的东西。
而随着语言本身的不断进化,也出现了各种各样的相关语言和工具,虽然说最终都会转化成 JavaScript,但如果开发过程中有特定的需求(比方说希望进行静态类型检查),还是有用武之地的。
简单描述 JavaScript 就是:
- 类 C 语法
- 垃圾回收
- 基于对象(函数是对象)
- 对象有具名属性
- 函数可以是属性
- 基于原型
- 引擎可结合解释执行与即时编译
- 动态类型
- 可在浏览器、Node.js 等不同宿主中运行
好了,相信再多写点大家也看不下去了,总之写的时候多思考下就好。
jQuery
有一定开发经验的同学都知道,没有各种各样的库的支持,开发起来真的是捉襟见肘。在原稿写作时,jQuery 已经是影响很大的前端库。在它的帮助下,我们得以以更加清晰的方式来进行开发。jQuery 提供了:
- DOM 操作支持(元素选择,遍历等等)
- 事件支持
- AJAX 支持
- 工具支持
- 插件支持
理解这些库曾经解决的问题,有助于判断哪些能力来自语言、浏览器接口,哪些来自框架封装。
AJAX
前面提到的 AJAX 并不是一种语言,而是一种技术,通过异步 HTTP 请求实现局部更新,让更具交互性的 Web 应用成为可能。它不要求每次刷新整个页面,但是否减少服务器负载,还取决于请求频率和后端工作量。
具体的原理也很简单,使用 JavaScript 通过 XMLHttpRequest 或 Fetch API 获取数据,再按应用逻辑更新 DOM。响应可以是 JSON、文本等,并不一定直接转换为 DOM 树。
HTTP 与服务器
除了前面提到的技术,Web 另一部分非常重要的内容就是服务器。这里以 HTTP 服务器为例,简要进行说明。原稿中的服务器市场份额截图已去掉,因为它只反映当时某种统计口径,不影响理解请求与响应。
客户端通过 URI 标识资源并发起请求。普通 HTTP 常用 80 端口,HTTPS 常用 443 端口。HTTP/1.1 和 HTTP/2 通常运行在 TCP 上,HTTP/3 使用基于 UDP 的 QUIC,不能一概说 HTTP 都基于 TCP。参见 HTTP 的演进。
具体的服务器实现因为 HTTP 规范,所以比较复杂,但基本结构是简单的,就是读取请求 - 处理请求 - 返回响应,如下所示:
while (true) {
request = readHttpRequest(...);
response = processHTTPRequest(request);
sendHttpResponse(..., response);
}
服务器需要照顾的东西非常多,比如:
- 服务器名称
- 电子邮件
- 文档位置
- IP 地址和端口
- 超时
- 最大请求长度
- 缓存
- 目录显示
- 验证与授权
- 日志与监控
- 错误处理
- 系统管理
- 性能
- 安全
- ....
不一而足,这里就暂时不展开了。
Cookies 与 Sessions
前面忘了说,HTTP 其实是无状态的,即协议本身不要求服务器在请求之间保存应用会话状态,但是很多时候页面上的操作是需要依赖上下文的,比方说登录注册购物车。这时候就是 Cookies 与 Sessions 登场的时候了。
Cookie 可以按寿命区分:会话 Cookie 没有显式过期时间,由浏览器界定会话结束;持久 Cookie 通过 Max-Age 或 Expires 设置寿命,但也可能被用户或浏览器提前清除。浏览器恢复会话时可能恢复会话 Cookie,不能依赖“关闭窗口就必定删除”。
第一方、第三方则是另一种与站点上下文有关的区分,不是寿命分类。跨站发送还受到 SameSite 和浏览器策略等约束。参见 HTTP Cookie。
而 Sessions 则可以认为是一个短期的状态暂存,常通过 Cookie 传递会话标识,由服务端保存状态。我们保存一个 session id,通过这个 id 来取出服务器上对应的 (name, object),来进行相关操作。
网络地址
回过头来说说网络传输层面,针对不同层级,我们都需要一个方式来区别不同的设备和应用,于是有了:
- MAC 地址 - 底层 - 硬件地址
- IP 地址 - 可路由的地址
- DNS 主机名 - 高层 - 逻辑地址
确定了服务器之后,使用不同的端口来和不同的应用沟通,比如
- telnet - 23
- smtp - 25
- rdp - 3389
在开发和部署中,也有一些惯例,我们在开发的时候最好也遵守这种约定:
- 部署
- HTTP - 80
- HTTPS - 443
- MySQL - 3306
- 开发
- HTTP - 8080(常见替代端口)
- HTTPS - 8443(常见替代端口)
Web 技术演进
CGI
虽然现在可能很多人都不知道 CGI(Common Gateway Interface)是啥了,但是从早期 Web 中出现起,CGI 使得在网页上动态显示内容成为可能。这里的动态并不是指页面在动,而是可以通过 Web 请求动态生成内容。这是怎么做到的呢?说来也简单,CGI 可以认为是 Web 服务器用来执行外部程序的接口,通过环境变量和标准输入等方式把请求交给外部程序,再读取程序输出,而具体调用的函数可以由我们自己编写。当年最流行的语言是 Perl, Shell, 和 C,只要符合 CGI 接口标准,就可以把内容发送给 Web 服务器,用户也就能看到了。
借助 CGI 这一层,我们把 Web 请求和处理请求的程序解耦了。当年的网站大概是这样的:

PHP
能显示动态内容是一个巨大的进步,不过很快人们就发现 CGI 没有办法满足需求了,传统 CGI 常为每个请求启动一个进程,带来开销;FastCGI 等方式则可以复用进程。而且需要我们的程序自己输出 HTML 字符串,哪怕只需要一个简单的页面,也不得不输出成吨的 HTML 代码。
程序员就想,那我把不变的部分抽出来,把动态内容留个占位符最后填充进去不就省事儿了吗?于是,我们就有了模板。折腾的人多了,自然就要弄出一个新东西,于是 PHP 从早期个人网页工具逐步发展起来(毕竟是『最好的』语言)。
ASP、JSP 也支持把动态逻辑与页面结合,但它们各自涉及不同的运行环境,不能仅用“换了语言的模板引擎”概括。后来为了把内容和样式分离,1996 年的时候 W3C 发布了 CSS 1.0 规范。这下好了,Web 终于成了一个可以处理复杂事务的平台了。
J2EE / .Net
随着 Web 应用越来越大,大家又不满足了。分布式要考虑,安全要考虑,事务要考虑,部署方式要考虑。各大公司一看,这是一个可以赚钱的事儿,于是双雄争霸,Java 派以 Servlet, JSP 和 EJB 合体出战,是为 J2EE。而微软甩出 ASP.Net 和 Visual Studio,用拖拽组件方式创建 Web 页面,极大降低了门槛。
框架的兴起
不同的技术方案在发展到一定程度,再次面临了同一个问题,就是如何多人协作,如何组织代码。回答这两个方式有多种答案,但是最流行的答案是 MVC 的方法,即 Model/View/Controller 的套路。这种分类方式按照不同的角色进行代码复用和组织,Web 应用可以以更加灵活的方式进行配置。
有的框架大而全,如果没有特别多个性化需求,几乎可能在很短的时间完成开发工作,但缺点是易学难精,一旦出现问题和稍微复杂且小众的需求,就需要大量的开发工作。
有的框架小而灵活,不过需要使用者对于自己的需求和技术整体有比较清晰的认知,才能挑选出正确的组件完成工作。
不同语言基本都有自己的框架,具体怎么选择就看团队和业务的要求了,性能比较也要结合测试环境、查询类型、并发与延迟目标,不能凭一张历史榜单决定选型。
随着前端的复杂度越来越高,各种各样前端的框架也应运而生,比如 BackboneJS, AngularJS, EmberJS(然而我并不熟悉,就此略过)
一些框架提供或集成 ORM,把对象操作映射为数据库查询。它可以减少重复代码,但不免除理解 SQL、索引、事务和查询成本的责任。
更多内容可以查看Spring,Django,Rails,Express这些框架技术的出现都是为了解决什么问题,现在这些框架都应用在哪些方面?
RESTful
从原来的静态页面开始,到各种框架,Web 本身几乎已经淹没于各种技术名词中。直到 REST 的提出和 RESTful API 的广泛使用,它强调资源、统一接口、无状态交互等约束,不能仅理解为用 URL 组织功能。
不过 REST 和 MVC 并不是互斥的关系,经过合理的设计,其实可以真正发挥 Web 开放、简洁且平等的力量。
优秀学习资源
总结
前面走马观花写了这么多,可能仍有许多地方没有讲清楚。但理解客户端、网络与服务端如何协作,能让后续学习不再只是一串框架名词。具体动手时,可以先从一个小程序开始,再逐步扩大问题范围。
延伸练习:Node.js 命令行程序实例教程。
参考链接
相关文章
- 从源代码到可复现修改:编译、Make 与 Git2026-11-06
- Kubernetes 学习笔记2017-06-05
- Q Con 2017 PPT 学习笔记2017-05-21
- Docker 完全指南2017-05-01