从前端到全栈
一、如何解决移动端 H5 点击有 300ms 延迟
1. 背景
- double tap to zoom
2. 初期解决方案 FastClick
window.addEventListener("load", function() {
FastClick.attach(document.body)
}, false)3. FastClick 原理
- 监听 touchend 事件( touchstart touchend 会先于 click 触发 )
- 使用 自定义 DOM 事件 模拟一个 click 事件
- 把默认的 click 事件( 300ms 之后触发)禁止掉
4. 现代浏览器的改进

二、token 和 cookie 有什么区别
1. cookie
- HTTP 无状态,每次请求都要带 cookie ,以帮助识别身份
- 服务端也可以向客户端 set-cookie ,cookie 大小限制 4kb
- 默认有跨域限制:不可跨域共享、传递 cookie
2. cookie 本地存储
- HTML5 之前 cookie 常被用于本地存储
- HTML5 之后推荐使用 localStorage 和 sessionStorage
3. 现代浏览器开始禁止第三方 cookie
和跨域限制不同。这里指:禁止网页引入的第三方 JS 设置 cookies
打击第三方广告,保护用户隐私
新增属性 SameSite: Strict / Lax / None; 值可自己选择

4. cookie 和 session
cookie 用于登录校验,存储用户标识(如 userId)
session 在服务端,存储用户详细信息,和 cookie 信息一一对应
cookie + session 是常用的登录验证解决方案

5. token 和 cookie
- cookie 是 HTTP 规范,而 token 是自定义传递
- cookie 会默认被浏览器存储,而 token 需自己存储
- token 默认没有跨域限制
6. JWT ( JSON Web Token )
前端发起登录,后端验证成功之后,返回一个加密的 token
前端自行存储这个 token(其中包括了用户信息,加密了)
以后访问服务端接口,都带着这个 token ,作为用户信息

7. 答案
- cookie:HTTP 标准;跨域限制;配合 session 使用
- token:无标准,无跨域限制;用于 JWT
8. 划重点
- cookie 的知识点很多,对于 HTTP 也很重要
- Session 存在的价值
- token 和 cookie 要对比理解,否则容易混淆
9. ( 连环问 ) Session 和 JWT 哪个更好
- Session 优点
- 原理简单,易于学习
- 用户信息存储在服务端,可快速封禁某个用户
- Session 缺点
- 占用服务器内存,硬件成本高
- 多进程,多服务器时,不好同步 -- 需使用第三方缓存,如 redis
- 默认有跨域限制
- JWT 优点
- 不占用服务端内存
- 多进程、多服务器,不受影响
- 没有跨域限制
- JWT 缺点
- 用户信息存储在客户端,无法快速封禁某用户
- 万一服务端秘钥被泄露,则用户信息全部丢失
- token 体积一般大于 cookie ,会增加请求的数据量
- 答案
- 如有严格管理用户信息的需求(保密、快速封禁)推荐 Session
- 如没有特殊要求,则使用 JWT (如创业初期的网站)
10. ( 连环问 ) 如何实现 SSO 单点登录
基于 cookie
- cookie 默认不可跨域共享,但有些情况下可设置为共享
- 主域名相同,如
www.baidu.comimage.baidu.com - 设置 cookie domain 为主域名,即可共享 cookie
SSO
主域名完全不同,则 cookie 无法共享
可使用 SSO 技术方案

OAuth 2.0

三、HTTP 协议和 UDP 协议区别
1. 网络协议
HTTP 协议在应用层
TCP UDP 协议在传输层
严格来说,应该拿 TCP 和 UDP 进行比较

2. TCP 协议
- 有连接(三次握手)
- 有断开(四次挥手)
- 稳定传输
3. UDP 协议
- 无连接,无断开
- 不稳定传输,但效率高
- 适用视频会议,语音通话
4. ( 连环问 ) HTTP 协议 1.0 1.1 2.0 有什么区别
- HTTP 1.0
- 最基础的 HTTP 协议
- 支持基本的 GET POST 方法
- HTTP 1.1
- 缓存策略 cache-control E-tag 等
- 支持长连接 Connection: keep-alive ,一次 TCP 连接多次请求
- 断点续传,状态码 206
- 支持新的方法 PUT DELETE 等,可用于 Restful API
- HTTP 2.0
- 可压缩 header ,减少体积
- 多路复用,一次 TCP 连接中可以多个 HTTP 并行请求
- 服务端推送
四、WebSocket 和 HTTP 有什么区别
1. WebSocket
- 支持端对端通讯
- 可以由 client 发起,也可以由 server 发起
- 用于:消息通知,直播间讨论区,聊天室,协同编辑
2. 代码
// 客户端 html
const ws = new WebSocket('ws://localhost:3001')
ws.onopen = () => {
console.log('opened')
ws.send('client opened')
}
ws.onmessage = ev => {
console.log('客户端收到了信息', ev.data)
}// 服务端 nodejs
const {WebSocketServer} = require('ws')
const wsServer = new WebSocketServer({port: 3001})
wsServer.on('connection', ws => {
console.log('connected')
ws.on('message', msg => {
console.log('收到了信息', msg.toString())
// 服务端像客户端发送信息
setTimeout(() => {
ws.send('服务端收到了信息' + msg.toString())
}, 2000)
})
})3. WebSocket 连接过程
- 先发起一个 HTTP 请求
- 成功之后再升级到 WebSocket 协议,再通讯
4. WebSocket 和 HTTP 区别
- WebSocket 协议名是 ws:// ,可 双端 发起请求
- WebSocket 没有跨域限制
- 通过 send 和 onmessage 通讯(HTTP 通过 req 和 res)
5. ws 可升级为 wss (像 https)
import {createServer} from 'https'
import {readFileSync} from 'fs'
import {WebSocketServer} from 'ws'
const server = createServer({
cert: readFileSync('/path/to/cert.pem'),
key: readFileSync('/path/to/key.pem')
})
const wss = new WebSocketServer({server})6. 扩展:实际项目推荐 socket.io ,API 更简洁
io.on('connection', socket => {
// emit an event to the socket
socket.emit('request', '...')
// emit an event to all connected sockets
io.emit('broadcast', '...')
// listen to the event
socket.on('reply', () => { /* ... */ })
})7. 扩展:创建简易聊天室
// nodejs
const {WebSocketServer} = require('ws')
const wsServer = new WebSocketServer({port: 3001})
const list = new Set()
wsServer.on('connection', curWs => {
console.log('connected')
// 这里不能一直 add , 实际中这里应该有清理缓存的机制
list.add(curWs)
curWs.on('message', msg => {
console.log('收到了信息', msg.toString())
// 传递给其他客户端
list.forEach(ws => {
if (ws === curWs) return
ws.send(msg.toString())
})
})
})8. ( 连环问 ) WebSocket 和 HTTP 长轮询的区别
HTTP 长轮询:客户端发起请求,服务端阻塞,不会立即返回
WebSocket:客户端可发起请求,服务端也可发起请求
HTTP 长轮询,需处理 timeout ,即 timeout 之后重新请求

五、从输入 URL 到网页显示的完整过程
1. 步骤
- 网络请求
- 解析
- 渲染
2. 网络请求
- DNS 查询(得到 IP),建立 TCP 连接(三次握手)
- 浏览器发起 HTTP 请求
- 收到请求响应,得到 HTML 源代码
- 继续请求静态资源
- 解析 HTML 过程中,遇到静态资源还会继续发起网络请求
- JS CSS 图片 视频等
- 注意:静态资源可能有强缓存,此时不必请求
2. 解析:字符串 -> 结构化数据
HTML 构建 DOM 树
CSS 构建 CSSOM 树 (style tree)
两者结合,形成 render tree

优化解析
- CSS 放在
<head>中,不要异步加载 CSS - JS 放在
<body>最下面(或合理使用 defer async) <img>提前定义 width height
- CSS 放在
3. 渲染:Render Tree 绘制到页面
- 计算各个 DOM 的尺寸、定位,最后绘制到页面
- 遇到 JS 可能会执行(参考 defer async)
- 异步 CSS 、图片加载,可能会触发重新渲染
4. 答案
- 网络请求:DNS 解析,建立 TCP 连接,HTTP 请求
- 解析:DOM 树,CSSOM 树,Render Tree
- 渲染:计算、绘制,同时执行 JS
5. ( 连环问 ) 重绘 repaint 重排 reflow 有什么区别
- 动态网页,随时都会重绘、重排
- 网页动画
- Modal Dialog 弹窗
- 增加 / 删除一个元素,显示 / 隐藏一个元素
- 重绘 repaint
- 元素外观改变,如颜色、背景色
- 但元素的尺寸、定位不变,不会影响其他元素的位置
- 重排 reflow
- 重新计算尺寸和布局,可能会影响其他元素的位置
- 如元素高度增加,可能会使相邻元素位置下移
- 区别
- 重排比重绘要影响更大,消耗也更大
- 尽量避免无意义的重排
- 集中修改样式,或直接切换 css class
- 修改前先设置 display: none ,脱离文档流
- 使用 BFC 特性,不影响其他元素位置
- 频繁触发(resize scroll)使用节流防抖
- 使用 createDocumentFragment 批量操作 DOM
- 优化动画,使用 CSS3 和 requestAnimationFrame
- 扩展:BFC
- Block Format Context 块级格式化上下文
- 内部元素无论如何改动,都不会影响其他元素的位置
- 触发 BFC 的条件
- 根节点
<html> - float: left / right;
- overflow: auto / scroll / hidden;
- display: inline-block / table / table-row / table-cell;
- display: flex / grid; 的直接子元素
- position: absolute / fixed;
- 根节点
六、如何实现网页多标签通讯
1. 使用 WebSocket
- 无跨域限制
- 需要服务端支持,成本高
2. 通过 localStorage 通讯
同域 的 A 和 B 两个页面
A 页面设置 localStorage
B 页面可监听 localStorage 值的修改
jswindow.addEventListener('storage', e => { console.log('key', e.key) console.log('value', e.newValue) })
3. 通过 SharedWorker 通讯
SharedWorker 是 WebWorker 的一种
WebWorker 可开启 子进程 执行 JS ,但不能操作 DOM
SharedWorker 可单独开启一个进程,用于 同域 页面通讯
js// worker.js const set = new Set() onconnect = event => { const port = event.ports[0] set.add(port) // 接收信息 port.onmessage = e => { // 广播消息 set.forEach(p => { if (p === port) return p.postMessage(e.data) }) } // 发送信息 port.postMessage('worker.js done') }js// page detail const worker = new SharedWorker('./worker.js') const btn = document.getElementById('btn') btn.addEventListener('click', () => { console.log('clicked') worker.port.postMessage('detail go...') })js// page list const worker = new SharedWorker('./worker.js') worker.port.onmessage = e => console.log('list', e.data)
4. 答案
- WebSocket 需要服务端,成本高,但可以跨域
- localStorage 简单易用,推荐
- ShareWorker 调试不方便,不兼容 IE11
5. ( 连环问 ) 网页和 iframe 如何通讯
使用 PostMessage 通讯
注意跨域的限制和判断
js// index.html const btn = document.getElementById('btn') btn.addEventListener('click', () => { console.log('index clicked') window.iframe.contentWindow.postMessage('hello', '*') }) window.addEventListener('message', ev => { console.log('origin', ev.origin) // 来源域名 console.log('index received', ev.data) })js// child.html const btn = document.getElementById('btn') btn.addEventListener('click', () => { console.log('child clicked') window.parent.postMessage('world', '*') }) window.addEventListener('message', ev => { console.log('origin', ev.origin) // 来源域名 console.log('child received', ev.data) })
七、script 标签的 defer 和 async 有什么区别
1. 区别

2. 答案
- 无:HTML 暂停解析,下载 JS ,执行 JS ,再继续解析 HTML
- defer:HTML 继续解析,并行下载 JS ,HTML 解析完再执行 JS
- async:HTML 继续解析,并行下载 JS ,执行 JS ,在解析 HTML
3. ( 连环问 ) prefetch 和 dns-prefetch 区别
preload 和 prefetch
preload 资源在当前页面使用,会 优先 加载
prefetch 资源在未来页面使用,空闲时 加载
html<head> <!-- preload--> <link rel="preload" href="style.css" as="style"> <link rel="preload" href="main.js" as="script"> <!-- prefetch--> <link rel="prefetch" href="other.js" as="script"> <!-- 引用 css--> <link rel="stylesheet" href="style.css"> </head> <body> <!-- 引用 js--> <script src="main.js" defer></script> </body>
dns-prefetch 和 preconnet
dns-prefetch 即 DNS 预查询
preconnect 即 DNS 预连接
html<head> <link rel="dns-prefetch" href="https://xxx.com"> <link rel="preconnect" href="https://xxx.com" crossorigin> </head>
答案
- prefetch 是资源预获取(和 preload 相关)
- dns-prefetch 是 DNS 预查询(和 preconnect 相关)
八、什么是 HTTPS 中间人攻击
1. HTTPS 加密传输
HTTP 明文传输
HTTPS 加密传输 HTTP + TLS / SSL

2. 中间人攻击
黑客伪造证书

九、前端攻击手段有哪些
1. XSS
- Cross Site Script 跨站脚本攻击
- 手段:黑客将 JS 代码插入到网页内容中,渲染时执行 JS 代码
- 预防:特殊字符替换(前端或者后端)
2. CSRF
- Cross Site Request Forgery 跨站请求伪造
- CSRF 详细过程
- 用户登录了 A 网站,有了 cookie
- 黑客诱导用户到 B 网站,并发起 A 网站的请求
- A 网站的 API 发现有 cookie ,认为是用户自己操作的
- CSRF 预防手段
- 严格的跨域限制,如判断 referrer (请求来源)
- 为 cookie 设置 SameSite ,禁止跨域传递 cookie
- 关键接口使用短信验证码
3. 点击劫持
Click Jacking
手段:诱导界面上蒙一个透明的 iframe ,诱导用户点击
预防:让 iframe 不能跨域加载

4. DDos
- Distribute denial-of-service 分布式拒绝服务
- 手段:分布式的、大规模的流量访问,使服务器瘫痪
- 预防:软件层不好做,需硬件预防(如阿里云 WAF)
5. SQL 注入
- 手段:黑客提交内容时写入 SQL 语句,破坏数据库
- 预防:处理输入的内容,替换特殊字符
十、描述 koa2 洋葱圈模型
1. Koa2
一个简约、流行的 nodejs 框架
通过 中间件 组织代码
jsconst Koa = require('koa'); const app = new Koa(); // logger app.use(async (ctx, next) => { await next(); // 先执行下一步 x-response-time ,执行完再继续执行 const rt = ctx.response.get('X-Response-Time'); console.log(`${ctx.method} ${ctx.url} - ${rt}`); }); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next(); // 先执行下一步 response ,执行完再继续执行 const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`); }); // response app.use(async ctx => { ctx.body = 'Hello World'; }); app.listen(3000);多个中间件以 “ 洋葱圈模型 ” 执行
