一、前端知识深度-全栈
1. 移动端 H5 click 有 300ms 延迟,如何解决
- 背景:double tap to zoom
初期解决方案 FastClick
- 使用javascript
// FastClick 使用非常简单 window.addEventListener("load", function () { FastClick.attach(document.body) }, false)
- 原理
- 监听 touchend 事件(touchstart touchend 会优先 click 触发)
- 使用 自定义 DOM 事件 模拟一个 click 事件
- 把默认的 click 事件(300ms之后触发) 禁止掉
- 使用
现代浏览器的改进
2. 网络请求中,token 和 cookie 有什么区别
答案:
cookie:HTTP 标准;跨域限制;配合 session 使用
token:无标准;无跨域限制;用于 JWT
重点:
- cookie 的知识点很多,对于 HTTP 也很重要
- session 存在的价值
- token 和 cookie 要对比理解,否则容易混淆
连环问:
- session 和 JWT 哪个更好?
答案:
- 如有严格管理用户信息的要求(保密、快速封禁)推荐 session
- 如没有特殊要求,则使用 JWT(如创业初期的网站)
session 优点
- 原理简单,易于学习
- 用户信息存储在服务端,可快速封禁某个用户
session 缺点
- 占用服务端内存,硬件成本高
- 多进程,多服务器时,不好同步,需要使用第三方缓存,如 redis
- 默认有跨域限制
JWT 优点
- 不占用服务端内存
- 多进程、多服务器 不受影响
- 没有跨域限制
JWT 缺点
- 用户信息存储在客户端,无法快速封禁某用户
- 万一服务器秘钥被泄漏,则用户信息全部丢失
- token 体积一般大于 cookie,会增加请求的数据量
- session 和 JWT 哪个更好?
cookie
- http 无状态,每次请求都要带 cookie,以帮助识别身份
- 服务端也可以向客户端 set-cookie,cookie 大小限制 4kb
- 默认有跨域限制:不可跨域共享、传递 cookie
- 解决方案:withCredentials
cookie 本地存储
- HTML5 之前 cookie 常被用于本地存储
- HTML5 之后推荐使用 localStorage 和 sessionStorage
现在浏览器开始禁止第三方 cookie
- 和跨域限制不同。这里是:禁止网页引入的第三方 JS 设置 cookie
- 打击第三方广告,保护用户隐私
- 新增属性 SameSite: Strict/Lax/None; 值可自己选择
cookie 和 session
- cookie 用于登录验证,存储用户标识(如 userId)。
- session 在服务端,存储用户详细信息,和 cookie 信息一一对应。
- cookie + session 是常见登录验证解决方案
token vs cookie
- cookie 是 HTTP 规范,而 token 是自定义传递。
- cookie 会默认被浏览器存储,而 token 需要自己存储。
- token 默认没有跨域限制
JWT(JSON Web Token)
- 前端发起登录,后端验证成功之后,返回一个加密的 token
- 前端自行存储这个 token (其中包含了用户信息,加密了)
- 以后访问服务端接口,都带着这个 token ,作为用户信息。
连环问:如何实现 SSO 单点登录 答案:
主域名相同,则可共享 cookie
主域名不同,则需要使用 SSO
基于 cookie
- cookie 默认不可跨域共享,但有些情况下可设置为共享
- 主域名相同,如www.baidu.com image.baidu.com
- 设置 cookie domain 为主域名,即可共享 cookie
SSO
- 主域名完全不同,则 cookie 无法共享
- 可使用 SSO 技术方案
- OAuth 2.0 第三方 授权登录
3. HTTP 协议和 UDP 协议有什么区别?
答案:
HTTP 是应用层,TCP UDP是传输层
TCP 有连接,有断开,稳定传输
UDP 无连接,无断开,不稳定传输,但效率高
网络协议
- HTTP 协议在应用层
- TCP UDP 协议在传输层
- 严格来说,应该拿 TCP 和 UDP 进行比较
TCP 协议
- 有连接(三次握手)
- 有断开(四次挥手)
- 稳定传输
UDP 协议
- 无连接,无断开
- 不稳定传输,但效率高
- 如视频会议、语音通话
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 并行请求
- 服务端推送
- HTTP 1.0
4. 什么是 HTTPS 中间人攻击? 如何预防?
- HTTP 明文传输
- HTTPS 加密传输 HTTP + TLS/SSL 参考链接
- 对称加密
- 秘钥相同
- 非对称加密
- 公钥加密
- 私钥解密
- HTTPS 加密过程
- 中间人攻击
- 对称加密
5. <script>
defer 和 async 有什么区别?
答案:
没有:HTML 暂停解析,下载 JS ,执行 JS , 再继续解析 HTML
defer: HTML 继续解析,并行下载 JS , HTML 解析完再执行 JS
async: HTML 继续解析,并行下载 JS , 执行 JS ,再解析 HTML
连环问:prefetch 和 dns-prefetch 有什么区别? 答案:
prefetch 是资源预获取(和 preload 相关)
dns-prefetch 是 DNS 预查询(和 preconnect 相关)
都是使用标签
<link rel="prefetch">
preload 和 prefetch
- preload 资源在当前页面使用,会优先加载
- prefetch 资源在未来页面使用,空闲时加载
dns-prefetch 和 preconnect
- dns-prefetch 即 DNS 预查询
- preconnect 即 DNS 预连接
6. 你知道哪些前端攻击? 该如何预防?
- XSS
- Cross Site Script 跨站脚本攻击
- 手段:黑客将 js 代码插入到网页内容中,渲染时执行 JS 代码
- 预防:特殊字符替换(前端或者后端)
- Vue React 默认规避 XSS 攻击
- 除了 Vue v-html React dangerouslySetInnerHTML
-CSRF - Cross Site Request Forgery 跨站请求伪造 - 手段:黑客诱导用户访问另一个网站的接口,伪造请求。 - 详细过程 - 用户登录了 A 网站,有了 cookie - 黑客诱导用户到 B 网站,并发起 A 网站的请求 - A 网站的 API 发现有 cookie,认为是用户自己操作的 - 预防手段 - 严格的跨域请求限制,如判断 referrer (请求资源) - 为 cookie 设置 SameSite,禁止跨域传递 cookie - 关键接口中使用短信验证码
点击劫持
- Click Jacking
- 手段:诱导界面上蒙一个透明的 iframe, 诱导用户点击。
- 预防:让 iframe 不能跨域加载
DDos
- Distribute denial-of-service 分布式拒绝服务
- 手段:分布式的、大规模的流量访问,使服务器瘫痪
- 预防:软件层不好做,需要硬件预防(如阿里云 WAF)
SQL注入
- 手段:黑客提交内容时,写入SQL语句,破坏数据库。
- 预防:处理输入的内容,替换特殊字符。
7. WebSocket 和 HTTP 有什么区别?
- WebSocket
- 支持端对端通讯
- 可以有 client 发起,也可以由 server 发起
- 用于:消息通知,直播间讨论区,聊天室,协同编辑
- WebSocket 的连接过程
- 先发起一个 HTTP 请求
- 成功之后再升级到 WebSocket 协议,再通讯
- WebSocket 和 HTTP 区别
- WebSocket 协议名是 ws://,可双端发起请求
- WebSocket 没有跨域限制
- 通过 send 和 onmessage 通讯(HTTP 通过 req 和 res)
- ws 可升级为 wssjavascript
import { createServer } from 'https'; import { readFileSync } from "fs"; import { WebSocketServer } from "ws"; const server = createServer({ cert: readFileSync('/path/to/cret.pem'), key: readFileSync('/path/to/key.pem') }) const wss = new WebSocketServer({ server })
- 扩展:实际项目推荐 socket.io API简介
- 也可以使用聊天室 这种场景
- 连环问:WebSocket 和 HTTP 长轮询的区别?
- HTTP 长轮询:客户端发起请求,服务端阻塞,不会立即返回。
- WebSocket:客户端可发起请求,服务端也可发起请求。
- 注意:
- HTTP 长轮询,需要处理 timeout,即 timeout 之后重新发请求。
8. 描述从输入 url 到页面展示的完整过程。
答案:
网络请求:DNS 解析,HTTP 请求
解析:DOM 树,CSSDOM 树,Render Tree
渲染:计算、绘制,同时执行 JS
网络请求
- DNS 查询(得到IP),建立 TCP 连接(三次握手)
- 浏览器发起 HTTP 请求
- 收到请求响应,得到 HTML 源代码
- 继续网络请求
- 解析 HTML 过程中,遇到 静态资源还会继续发起网络请求
- JS CSS 图片 视频等
- 注意:静态资源可能有强缓存,此时不必请求
- 继续网络请求
解析:字符串 -> 结构化数据
- HTML 构建 DOM 树
- CSS 构建 CSSDOM 树(style tree)
- 两者结合,形成 render tree
- 解析过程很复杂
- CSS 可能来自
<style><link>
- JS 可能内嵌、或外链,还有 defer async 逻辑
- img 可能内嵌(base64),可能外链
- CSS 可能来自
- 优化解析
- CSS 放在
<head> 中,不要异步加载 CSS
- JS 放在
<body>
最下面(或合理使用 defer async) <img>
提前定义 width height
- CSS 放在
- 解析过程很复杂
渲染:Render Tree 绘制到页面
- 计算各个 DOM 的尺寸、定位、最后绘制到页面
- 遇到 JS 可能会执行(参考 defer async)
- 异步 CSS、图片加载,可能会触发重新渲染。
连环问:重绘 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
- 根节点
- 动态网页,随时都会重绘、重排
9. 如何实现网页多标签通讯?
答案:
WebSocket 需要服务端,成本较高
localStorage 简单易用,推荐
SharedWorker 调试不方便,不兼容 IE11
使用 WebSocket
- 无跨域限制
- 需要服务端支持,成本高
通过 localStorage 通讯
同域的 A 和 B 两个页面
A 页面设置 localStorage
B 页面可监听 localStorage 值的修改
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>msg-localStorage-detail</title> </head> <body> <p>localStorage message - detail page</p> <button id="btn"> 修改标题 </button> <script> const btn = document.getElementById('btn'); btn.addEventListener('click', () => { const info = { id: 100, name: '标题' + new Date() }; localStorage.setItem('changeInfo', JSON.stringify(info)) }) </script> </body> </html>
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>msg-localStorage-list</title> </head> <body> <p>localStorage message - list page</p> <button id="btn"> 修改标题 </button> <script> window.addEventListener('storage', event => { console.log('key', event.key); console.log('value', event.newValue) }) </script> </body> </html>
通过 SharedWorker 通讯 [代码调试需要开启浏览器隐私模式]
SharedWorker 是 WebWorker 的一种
WebWorker 可开启子进程执行 JS,但不能操作 DOM
SharedWorker 可单独开启一个进程,用于同域页面通讯
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>msg-sharedworker-detail</title> </head> <body> <p>msg-sharedworker-detail page</p> <button id="btn">修改标题</button> <script> const worker = new SharedWorker('./worker.js') const btn = document.getElementById('btn'); btn.addEventListener('click', () => { console.log('clicked'); worker.port.postMessage('detail go...') }) </script> </body> </html>
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>msg-sharedworker-list</title> </head> <body> <p>msg-sharedworker-list page</p> <button id="btn">修改标题</button> <script> const worker = new SharedWorker('./worker.js') worker.port.onmessage = e => console.info('list', e.data) </script> </body> </html>
js/** * @description for sharedWorker */ 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') }
连环问:网页和 iframe 如何通讯? 答案:
使用 postMessage 通讯
注意跨域的限制和判断
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>iframe-index</title> </head> <body> <p>iframe-index</p> <button id="btn">发送消息</button> <iframe id="iframe" src="./iframe-child.html"></iframe> <script> const btn = document.getElementById('btn'); btn.addEventListener('click', () => { console.log('index clicked'); window.iframe.contentWindow.postMessage('hello', '*') }) window.addEventListener('message', event => { console.log('origin', event.origin); console.log('index received', event.data) }) </script> </body> </html>
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>iframe-child</title> </head> <body> <p>iframe-child</p> <button id="btn">发送消息</button> <script> const btn = document.getElementById('btn'); btn.addEventListener('click', () => { console.log('child clicked'); window.parent.postMessage('world', '*') }) window.addEventListener('message', event => { console.log('origin', event.origin); console.log('child received', event.data) }) </script> </body> </html>
10. 请描述 koa2 洋葱圈模型
koa2
一个简约、流行的 node.js 框架
通过中间件阻止代码
多个中间件以 "洋葱圈模型" 执行
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);
重点:JS 异步编程