我们的头衔是 Web 前端开发工程师,也就是 Web 开发的子集。其实不论前端后端,最终都会掌握前后端的开发技能,只是熟练度、深度、细节上有所侧重。掌握后端开发知识、计算机网络知识对工作中的问题排查、协作交流帮助非常大。下面从一个超常见的面试题开始了解:
面试题:从输入 URL 到展示页面经过了哪些步骤?
简单版例子:
- 用户输入了
www.baidu.com
- 浏览器要向百度发送一个网页请求,要先知道百度的 IP 地址,于是向 DNS 服务器询问,DNS 服务器返回了 IP:
180.101.49.11:443
- 请求数据包被 HTTP、SSL、TCP、IP、以太网协议包装后准备传输。
- 因为双方不属于同一个子网络,传输需要通过网卡转发,从本机的网卡 MAC 地址发送到百度服务器的网关 MAC 地址(通过 ARP 协议得到)
- 服务器与客户端建立 TCP 连接,收到请求,返回一个 HTTP 响应,内容是 HTML 页面
- 浏览器收到响应,从上到下解析 HTML 页面,遇到不同的标签做不同处理。例如
script
标签会执行其中的 JS 代码,style
标签会被加入样式表等等,其中又会根据标签发出数个请求,请求对应的资源文件。 - 浏览器解析处理完成后,展示页面。
可以看到整个过程有两个大环节:一.网络安全准确完整的传输数据;二.浏览器解析处理展示各类资源。一就像菜鸟驿站,不论你的快递是啥,用协议包一层贴上地址联系方式,准确的把快递投放到目的地。二则是将代码运行成应用让用户使用。
想更多了解一的过程,可以参考阮一峰 – 互联网协议入门(一)、阮一峰 – 互联网协议入门(二)、网络是怎样连接的
想更多了解二的过程,可以参考你不知道的浏览器页面渲染机制、浏览器环境
上面步骤中衍生的面试题:
-
步骤 2 中,DNS 做了些什么:
DNS(Domain Name System)做的事情非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。
- 在没有缓存的情况下,DNS 查询顺序是 根域名 -> 顶级域名 -> 次级域名 -> 三级域名,例如
www.baidu.com
,查询顺序是 .root -> .com -> .baidu -> .www。其中根域名平时是省略的。 - 更多知识可以参考:DNS 原理入门
-
CDN 是什么,它做了什么事情
- CDN(Content Delivery Network)全称内容分发网络,最大的好处是让用户就近获取资源,提高访问效率。
- CDN 会接管步骤 2 中的 DNS 查询,按就近原则和负载均衡,向用户返回当前效率最高的节点(通常是最近的),从而提高传输效率。
-
步骤 3 中 HTTP 是什么,它的 1.0 1.1 2.0 版本有什么区别
- HTTP 协议是一种无状态的协议,浏览器中 JS 调用 XHR 时会向服务器发送 HTTP 请求。协议就是为了前后端可以相互理解、沟通。HTTP 报文由 header、body 组成,请求报文和响应报文内容上有区别
- HTTP 1.1 相比 HTTP 1.0 主要有以下优化:1.更多控制缓存策略的请求头;2.支持断点续传;3.更多状态码;4.传递了 hostname 字段;5.支持长链接
- HTTP 2.0 相比 HTTP 1.1 主要有以下优化:1.以二进制格式(Binary Format)解析报文;2.多路复用,一个连接上的多个请求相互独立不阻塞;3.头部压缩,避免重复传输 header;4.支持服务端推送,而不是只能客户端发起请求。
- 更多可以参考HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
-
步骤 3 中 HTTPS 具体做了什么
- HTTPS 其实是 HTTP + SSL/TLS,由 SSL 将 HTTP 报文内容处理包装然后传输。将传输内容加密,防止被劫持。
- SSL/TLS 通过非对称加密传输对称加密秘钥,再通过对称加密传输内容。非对称加密需要信任证书获取公钥。
-
步骤 5 中 TCP 建立连接的过程是什么
- 三次握手四次挥手,三次握手是为了确保双方能正常应答,四次挥手是为了确保双方正常断开连接。
- 有一种 DDoS 攻击就是利用三次握手,大量客户端发起第一次握手后再不应答,服务端为了维护连接消耗了巨大的资源,导致其他正常用户无法成功连接。
- 更多可以参考图解 TCP/IP(第 5 版)
-
步骤 6 中 JS 的执行顺序是怎样的,
script
标签的defer
async
属性的作用是什么- 浏览器解析 HTML 时,遇到没有特殊属性的
script
标签,会暂停解析,立即执行 JS 代码。如果 有src
属性则会等下载后执行完,再继续往下解析 HTML。此时如果下载或者执行时耗时太长,会影响到之后的页面渲染。为了解决这个问题,可以带上script
标签的defer
async
属性 - 加上
async
属性,则下载 JS 代码的时候不会阻塞解析,但是等下载完后,如果还没有解析完成,会暂停解析执行代码。 - 加上
defer
属性,则下载 JS 代码的时候不会阻塞解析,下载完后,如果还没有解析完成,也不会暂停解析 HTML,等到 HTML 处理完后再执行。多个带defer
的script
标签会按顺序执行。 - 也就是说
async
属性,可能阻塞解析也可能不阻塞,defer
属性一定不阻塞。
- 浏览器解析 HTML 时,遇到没有特殊属性的
后端在 Web 应用中通常负责什么?
在 Web 应用中,后端负责根据 Request,返回 Response。早年的 Web 应用开发没有前后端分离的概念,通过 JSP、ThinkPHP 等模板语言返回数据渲染好的页面,或者通过接口返回数据。由此可见前端应用部署后也是一个 Web 服务,不论静态构建还是服务端渲染,同样是根据 Request,返回 Response,返回的是各类资源。
以常见前后端分离的应用举例,用户访问网页时,前端返回 HTML、CSS、JS、IMG 等资源;浏览器解析后执行 JS 中的 XHR 请求,后端接受到请求,根据请求中的鉴权信息、参数等,将对应的数据返回给前端,前端通过请求回调的数据来渲染页面。
接口文档及前端请求
当前主流的后端应用都是提供接口,举例极简的用户登录和获取用户信息两个接口文档:
// 接口文档 - 用户登录
url: http://www.example.com/api/user/login
method: POST
params:
- name: username
type: string
required: true
- name: password
type: string
required: true
response:
- code: 200
msg: success
data:
- user-token: xxxxxyyyy
复制代码
前端通过 axios 发起请求:
axios
.post("http://www.example.com/api/user/login", {
username: "admin",
password: "123456",
})
.then((res) => {
console.log(res.data);
localStorage.setItem("user-token", res.data.userToken);
// { code: 200, msg: success, data: [{ user-token: xxxxxyyyy }] }
});
复制代码
这一步用户通过登录获取到了登录凭证 user-token
,我们将他保存在 localStorage
中。再设置 axios 的拦截器,每次请求自动带上登录凭证,以供服务端鉴权,axios 拦截器示例如下:
axios.interceptors.request.use((config) => {
config.headers["user-token"] = localStorage.getItem("user-token");
return config;
});
复制代码
此时我们发起其他请求,都会在 header
中带上 user-token
字段,使服务端能够对当前用户进行验证鉴权。此时我们再请求用户信息接口:
// 接口文档 - 获取用户信息
url: http://www.example.com/api/user/info
method: GET
response:
- code: 200
msg: success
data:
- username: admin
avatar: http://www.example.com/avatar.png
复制代码
axios.get("http://www.example.com/api/user/info").then((res) => {
console.log(res.data);
// { code: 200, msg: success, data: [{ username: admin, avatar: http://www.example.com/avatar.png }] }
});
复制代码
通过user-token
,接口就能判断出用户身份,每个用户返回不同的信息。我们看下怎样编写出这样的接口:
编写接口的过程
后端同学开发业务的时候喜欢笑称自己是 CRUD(增删改查) 工具人,说明大部分业务开发都得基于数据的增删改查完成。要实现一个登录(查)的接口,首先要实现注册(增)接口。同样还会有修改密码(改)、用户注销(删)的接口。
在编写接口前,应该对接口的功能有清晰的认识,能初步定下接口的接收参数和返回值,写出接口文档,以注册接口为例:
// 接口文档 - 注册
url: http://www.example.com/api/user/register
method: POST
params:
- name: username
type: string
required: true
- name: password
type: string
required: true
response:
- code: 200
msg: success
data:
user-token: xxxxxyyyy
复制代码
有了接口文档,可以看出注册接口接收用户名和密码,返回注册成功。为了储存用户数据,后端服务需要连接数据库并创建数据表,这里我们使用 NodeJS 环境、Koa 框架、MySQL 数据库、用 Sequelize 来操作数据库。首先创建 user 表:
// 注意 业务实践中应该将密码脱敏处理
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
复制代码
然后连接数据库:
const Sequelize = require("sequelize");
const sequelize = new Sequelize("database", "username", "password", {
host: "localhost",
dialect: "mysql",
pool: {
max: 5,
min: 0,
idle: 10000,
},
});
复制代码
编写注册接口
// 定义 User 模型
const User = sequelize.define("user", {
username: Sequelize.STRING,
password: Sequelize.STRING,
});
User.sync({ force: true }).then(() => {
// User表中创建一条数据
User.create({
username: "admin",
password: "123456",
});
ctx.body = {
code: 200,
message: "注册成功",
};
});
复制代码
想了解更多后端实战的同学,可以参考 基于 Node.js Koa2 实战开发的一套完整的博客项目网站
寥寥几笔,只能简单描述业务接口开发过程,实际上后端的知识网包括且不限于:操作系统、运维、网络、数据库等等等等(参考后端架构师技术图谱)。感兴趣的同学也只是建议在有限的精力下,努力做到其中的一精多通。
—小尾巴— 我叫葛圣明,擅长前端,有想法有问题欢迎私聊讨论 ^_^