从浏览器地址栏输入 url 到显示页面发生了什么?
浏览器 & 网络
从浏览器地址栏输入 url 到显示页面的步骤(浏览器加载页面的过程)
当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列, 因为事件循环的机制, 渲染主线程取出消息队列里的渲染任务, 开始渲染
网络线程: 建立网络连接, 获取 HTML 文档
渲染主线程: 渲染页面
-
网络连接
- 浏览器查找域名的 IP 地址
- 浏览器和服务器三次握手, 建立连接, 进行 HTTP 请求
- 服务器处理请求, 返回一个 HTTP 响应
- 浏览器对 HTML 进行解析, 请求 HTML 中的资源
-
渲染过程
-
HTMl 解析
渲染主线程遇到 css 解析 css,遇到 js 解析 js, 当遇到外部 css 和外部 js 文件时, 会将其放到预解析线程中, 以提高解析效率
当解析到
link
时, 由于外部 css 文件还没有解析好, 主线程会继续解析后续的 html, 因为下载和解析外部 css 是放在预解析线程中执行的, 所以 css 不会阻塞 html 的解析.当解析到
script
时, 会停止解析 html, 转而等待 js文件下载完成,并将 js 代码解析执行完成再继续解析 html, 因为 js 代码执行过程中可能会修改当前的 dom 树, 所以必须暂停 html 的解析.解析完成后, 会得到 dom 树和 cssom 树(sou mu)
-
样式计算
渲染主线程会遍历 dom 树, 依次为每个节点计算最终样式, 也就是将 dom 树和 cssom 树结合, 得到一个带有样式的 dom 树
-
布局
遍历 dom 树每个节点的几何信息, 包含宽高和位置, 得到布局树(布局树不与 dom 树一一对应)(比如 display:none 和 伪类元素)
-
分层
浏览器会对布局树进行分层, 某一层的改变仅对该层进行处理, 提高效率
滚动条, 堆叠上下文, transform, opacity 都可能对分层产生影响
分层无法控制. 但可以通过 will-change 影响分层结果
-
绘制
浏览器对每一个分层产生单独的绘制指令, 绘制完成后提交给合成线程进行分块
-
分块
合成线程对每个图层进行分块, 划分为更小的区域
-
光栅化
合成线程将分块信息交给 GPU 进程, 完成光栅化, 并优先处理靠近视口区域的块, 光栅化产生的是一块快的位图
-
画(draw)
合成线程拿到位图后, 生成指引信息, 标识出每一个位图处于屏幕的位置以及旋转, 缩放等变形, (变形发生在合成线程, 与渲染主线成无关, 所以 transform 效率很高), 合成线程将指引信息交给 GPU 进程, 由 GPU 进程产生系统调用, 提交给 GPU 硬件完成成像.
-
reflow(回流) 和 repaint(重绘)
reflow 是当进行了影响布局的操作后, 重新计算布局树(当多次触发时, 为提高效率浏览器会合并这些操作, 当 js 全部执行完成后再统一计算, 也就是说 reflow 是异步执行的)
repaint 是当改动了可见样式后, 根据分层信息重新计算绘制指令, 因为布局也属于可见样式, 所以回流一定会引起重绘
什么是 TCP
协议栈, 用于识别连接请求
什么是三次握手
(为了防止已失效的连接请求报文段突然又传送到了服务端, 因而产生错误)
- 第一次握手: 客户端 A 将标志位 SYN 置为 1,随机产生一个值为 seq=J(J 的取值范围为=1234567)的数据包到服务器, 客户端 A 进入 SYN_SENT 状态, 等待服务端 B 确认;
- 第二次握手: 服务端 B 收到数据包后由标志位 SYN=1 知道客户端 A 请求建立连接, 服务端 B 将标志位 SYN 和 ACK 都置为 1, ACK=J+1, 随机产生一个值 seq=K, 并将该数据包发送给客户端 A 以确认连接请求, 服务端 B 进入 SYN_RCVD 状态;
- 第三次握手: 客户端 A 收到确认后, 检查 ack 是否为 J+1, ACK 是否为 1, 如果正确则将标志位 ACK 置为 1, ack=K+1, 并将该数据包发送给服务端 B, 服务端 B 检查 ack 是否为 K+1, ACK 是否为 1, 如果正确则连接建立成功, 客户端 A 和服务端 B 进入 ESTABLISHED 状态, 完成三次握手, 随后客户端 A 与服务端 B 之间可以开始传输数据了
人话:
- 客户端发送一个数据包(SYN: 1, seq: n)(n 是 1-7 的随机数)给服务器, 然后进入 SYN_SENT 状态;
- 服务端收到数据包后, 发送了一个数据包(SYN: 1, ACK: n+1, seq: m)(m 也是随机数)给客户端, 并进入 SYN_RCVD 状态;
- 客户端收到数据包后, 检查确认数据包信息, 并处理数据包(ACK: m+1), 再发送给服务端, 服务端检查确认后, 连接建立成功. 客户端和服务端都进入 ESTABLISHED 状态.
什么是四次挥手
- 浏览器发送一个 FIN, 用来关闭浏览器到 Server 的数据传送, 浏览器进入 FIN_WAIT_1 状态
- 服务端收到 FIN 后, 发送一个 ACK 给浏览器, 确认序号为收到序号+1(与 SYN 相同, 一个 FIN 占用一个序号), 服务端进入 CLOSE_WAIT 状态。
- 服务端发送一个 FIN, 用来关闭服务端到浏览器的数据传送, 服务端进入 LAST_ACK 状态。
- 浏览器收到 FIN 后, 浏览器进入 TIME_WAIT 状态, 接着发送一个 ACK 给服务端, 确认序号为收到序号+1, 服务端进入 CLOSED 状态, 完成四次挥手。
人话:
- 客户端发送一个 FIN, 用来关闭客户端到服务端的数据传送, 客户端进入 FIN_WAIT_1 状态
- 服务端收到 FIN 后, 发送一个 ACK 给客户端, 确认序号为收到序号+1, 服务端进入 CLOSE_WAIT 状态。
- 服务端发送一个 FIN, 用来关闭服务端到客户端的数据传送, 服务端进入 LAST_ACK 状态。
- 客户端收到 FIN 后, 进入 TIME_WAIT 状态, 接着发送一个 ACK 给服务端, 确认序号为收到序号+1, 服务端进入 CLOSED 状态, 完成四次挥手。
为什么建立连接是三次握手, 而关闭连接却是四次挥手呢
这是因为服务端在 LISTEN 状态下, 收到建立连接请求的 SYN 报文后, 把 ACK 和 SYN 放在一个报文里发送给客户端。而关闭连接时, 当收到对方的 FIN 报文时, 仅仅表示对方不再发送数据了但是还能接收数据, 己方也未必全部数据都发送给对方了, 所以己方可以立即 close, 也可以发送一些数据给对方后, 再发送 FIN 报文给对方来表示同意现在关闭连接, 因此, 己方 ACK 和 FIN 一般都会分开发送
cookies, localStorage 和 sessionStorage 和 indexDB 的区别
- 数据生命周期: cookie 可以设置过期时间, localStorage 和 indexDB 不手动清理会一直存在, sessionStorage 页面关闭就清理
- 数据存储大小: cookie 只有 4k, localStorage 和 sessionStorage 有 5M, indexDB 理论上无存储上限
- 与服务端通信: 只有 cookie 每次请求都会携带在 header 中, 对性能和安全性有一定负面影响
http 缓存
- 强缓存(表示在缓存期间不需要请求, state code 为 200) --> 浏览器在第一次请求的时候, 会直接下载资源, 然后缓存在本地, 第二次请求的时候, 直接使用缓存
通过两种响应头实现强缓存:
- Cache-control:
- no-store --> 不进行任何缓存
- no-cache --> 跳过强缓存 直接进入协商缓存
- public --> 浏览器、代理服务器都可缓存
- private --> 只能浏览器缓存
- Expires(过期时间)
- 协商缓存 --> 第一次请求缓存时保存缓存标识和时间, 重复请求向服务器发送缓存标识和最后缓存时间, 服务端进行校验, 如果有效则返回 304
两种实现方式: (ETag 优先级比 Last-Modified 高)
- Last-Modified(本地文件最后修改日期) 和 If-Modified-Since If-Modified-Since 会将 Last-Modified 的值发送给服务器验证资源是否有更新, 但如果在本地打开了缓存文件, Last-Modified 就会被修改, 为了解决这个问题有了 ETag
- ETag(文件标记) 和 If-None-Match If-None-Match 会将当前 ETag 发送给服务器, 询问该资源的 ETag 是否变动, 有变动的话就将新的资源发送回来, 否则返回 304, 直接用缓存
defer 和 async 的区别, 以及它们的加载和执行时机
| script 标签 | JS 执行顺序 | 是否阻塞解析 HTML | | ------------ | ---------------- | ------------------------------------------------------- | | script | 在 HTML 中的顺序 | 阻塞 | | script async | 网络请求返回顺序 | 不可控, 根据网络请求返回时间, 有可能阻塞,也有可能不阻塞 | | script defer | 在 HTML 中的顺序 | 不阻塞 |
事件模型
事件流分为三个阶段(W3C 中定义事件的发生经历三个阶段)
- 捕获阶段(capturing)
- 目标阶段(targetin)
- 冒泡阶段(bubbling)
- 当你使用事件捕获时, 父级元素先触发, 子级元素后触发; 捕获事件流从根节点开始执行, 一直往子节点查找执行, 直到查找执行到目标节点 阻止捕获(阻止事件的默认行为) --> preventDefault();
- 当你使用事件冒泡时, 子级元素先触发, 父级元素后触发; 冒泡事件流从目标节点开始执行, 一直往父节点冒泡查找执行, 直到查到到根节点 阻止冒泡 --> stopPropagation();
什么是进程和线程
- 进程: 程序运行需要有属于它的内存空间,可以把这块内存空间简单的理解为进程;
- 线程: 有了进程后,就可以运行程序的代码了, 而运行代码的就是线程
比如: 打开 QQ 应用程序开启了进程来运行程序(QQ), 有多个线程来进行资源调度和分配(多个线程来分配打开 QQ 所占用的运存), 达到运行程序(QQ)的作用.
EventLoop(事件循环/消息循环)
JS是一门单线程的语言, 它运行在浏览器的渲染主线程中,而渲染主线程承担很多工作,如果同步执行的话,就会导致主线程阻塞,那么消息队列中的其他任务执行不了, 页面也不能及时更新,造成卡死现象。
所以浏览器采用异步的方式来解决阻塞的问题。当遇到某些任务时,比如计时器、网络、事件监听,主线程将其交给其他线程去处理,自己继续执行后续代码。当其他线程把这个任务执行完成时,把这个任务的回调函数包装成任务,加入到消息队列中,等待主线程执行。
这样就解决了阻塞的问题, 保证了单线程的流畅运行。
具体是怎么做的 / 渲染主线程是如何处理任务的?
渲染主线程开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾就可以了.
消息队列
任务是没有优先级的, 但消息队列(延时队列, 交互队列, 微队列)有优先级, 以前我们说宏任务微任务, 就是取第一个宏任务执行, 并在这个过程中依次执行微任务队列里的任务, 但现在随着浏览器的越来越复杂,W3C 已经不再使用宏任务队列的说法, 现在微队列的优先级是最高的.
进入微队列的方式有 Promise.then(), catch(), finally(), async/await等等, 还有 Node.js 中的 process.nextTick
以下过时了, 可以简单了解
-
同步和异步任务
- 同步任务: 立即执行, 直接进入主线程(执行栈)中执行
- 异步任务: 异步执行(如 ajax 等接口调取操作、setTimeout 定时器等), 进入任务队列(event table)
- 宏任务(macrotask/task): 执行一个宏任务, 遇到微任务时会放入微任务事件队列中, 当前宏任务执行完成后再查看微任务的事件队列, 将其中的所有微任务依次执行完; 宏任务如: script, setTimeout/setInterval/setImmediate, postMessage, Node.js 中的 I/O
- 微任务(microtask/jobs): 执行时机: 主函数执行结束之后, 当前宏任务结束之前;
-
async/await: 是 promise 和 generator 的语法糖 async 用来声明一个异步方法, await 用来等待异步方法执行完成
- async: 执行 async 函数, 返回的是 Promise 对象
- await: 相当于 Promise 的 then, await 后面通常是一个 promise 对象, 如果不是, 就直接返回对应的值 await 会阻塞后面的代码
-
流程分析
内存泄露
- setTimeout 为什么会造成内存泄露?如何防止 setTimeout 内存泄露?清除定时器为什么就不会有内存泄露?
指任何对象在我们不再拥有或需要它之后仍然存在
-
垃圾回收机制: 垃圾回收机制会周期性的找出那些不再继续使用的变量, 然后释放其内存
-
哪些操作会造成内存泄漏?
- setTimeout 的第一个参数使用字符串而非函数的话, 会引发内存泄漏;
- 闭包使用不当, 会引发内存泄漏
ajax 、axios 、fetch
- ajax
- axios
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 发出 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防止 CSRF/XSRF
- fetch
- superagent request
- fetch 只对网络请求报错, 对 400, 500 都当做成功的请求, 需要封装去处理
- fetch 默认不会带 cookie , 需要添加配置项
- fetch 不支持 abort , 不支持超时控制, 使用 setTimeout 及 Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台运行, 造成了量的浪费
- fetch 没有办法原生监测请求的进度, 而 XHR 可以
Ajax 原理
简单来说是在用户和服务器之间加了一个中间层( AJAX 引擎), 通过 XmlHttpRequest 对象来向服务器发送异步请求, 从服务器获得数据, 然后用 js 来操作 DOM 更新页面, (即局部刷新/动态不刷新) 使用户操作与服务器响应异步化 。这其中最关键的一步就是从服务器获得请求数据。
- 优点 -->
- 异步模式提高用户体验
- 可以实现局部刷新
- 优化了浏览器与服务器之间的传输, 减少了不必要的数据往返, 减少了带宽占用(Ajax 在客户端运行, 承担了一部分本来由服务器承担的工作, 减少了大用户量下的服务器负载)
- 缺点 -->
- 安全问题:Ajax 暴露了与服务器交互的细节
- 不易调试
- 本身是针对 MVC 的编程,不符合现在前端 MVVM 的浪潮
- JQuery 整个项目太大, 单纯使用 ajax 却要引入整个 JQuery 非常的不合理
http response 报文结构
首行是状态行包括:HTTP 版本, 状态码, 状态描述, 后面跟一个 CRLF
首行之后是若干行响应头, 包括:通用头部, 响应头部, 实体头部
响应头部和响应实体之间用一个 CRLF 空行分隔
常见的请求头
| 常见请求头 | 含义 | | -------------- | ---- | | Accept| 期望接收的数据类型 | | content-Length | 请求体的数据字符长度,无请求体为0 | | connection | 浏览器和服务器的连接方式:keep-alive: 长连接 | | content-type | 请求体的数据类型 | | Cache-Control | 是否启动缓存 | | Accept-Encoding | 支持的扩展数据类型。比如支持压缩 | | host | 发送请求的域名,即服务器地址 |
常见的响应头(response headers)
| 常见的响应头 | 含义 | | ------------------------------------------------- | ------------------------------------------------------------ | | Access-Control-Allow-Origin | 服务器用来决定浏览器是否拦截这个响应. 允许哪些域名可以跨域请求该服务器数据。*表示任何一个域名都可以访问服务器。 | | Location | 服务端需要客户端访问的页面路径 | | | | | Content-Encoding: gzip | 服务端能够发送压缩编码类型 | | Content-Length: 888 | 响应体数据的长度 | | Content-Language: zh-cn | 服务端发送的语言类型 | | Content-Type:application/json;charset=UTF-8 | 服务端返回数据的类型及采用的编码方式 | | Last-Modified: Mon, 22 May 2017 09:41:07 GMT | 服务端对该资源最后修改的时间 | | Refresh: 1;url= | 服务端要求客户端1秒钟后,刷新,然后访问指定的页面路径 | | Content-Disposition: attachment; filename=aaa.zip | 服务端要求客户端以下载文件的方式打开该文件 | | Expires: -1//3种 | 服务端禁止客户端缓存页面数据 | | Cache-Control: no-cache | 服务端禁止客户端缓存页面数据 | | Pragma: no-cache | 服务端禁止客户端缓存页面数据 | | Connection: close(1.0)/(1.1)Keep-Alive | | | Date: Mon, 22 May 2017 18:41:07 GMT | 服务端响应客户端的时间 | | | | | | |
get/post 区别 安全性
本质上都是http 请求方式,都属于TCP连接
- 存储大小: get是2kb, post: 不限大小
- 应用: get一般是获取数据,比如查询, post一般是发送数据,比如新增和修改
- 从缓存的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。
- 从参数的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。
- 从编码的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
http 队头阻塞问题
当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态,也就是著名的队头阻塞问题。
http1.1 和 http2 的区别
- 多路复用: http1.1 每个请求都需要单独的建立和维护连接, 而且是按序执行的, 也就是同时只能处理一个请求; http2 是多路复用(multiplexing)的, 允许同时在同一连接中发送多个请求和响应。可以并发处理多个请求, 从而提高了性能;
- 头部压缩: http1.1 头部字段需要在每个请求和响应中重复发送, 导致较大的数据传输量; http2 使用了帧(frame)和流(stream)的概念, 对头部进行了压缩。而且只需要在第一个请求中发送, 后续请求中使用对应的标识符引用, 减少了重复的数据传输;
- http1.1 加密是可选的, 并通过 HTTPS 来实现; http2 在协议层面上对传输进行了加密, 并要求使用 HTTPS 来进行通信;
http 和 https 的区别
HTTPS
是超文本传输安全协议,即HTTP + SSL/TLS
TLS
加密算法:
- HTTP是
明文传输
,不安全的,HTTPS是加密传输
,安全的多 - HTTP标准端口是
80
,HTTPS标准端口是443
- HTTP不用认证证书
免费
,HTTPS需要认证证书`要钱 连接方式不同
,HTTP三次握手,HTTPS中TLS1.2版本7次,TLS1.3版本6次- HTTP在OSI网络模型中是在
应用层
,而HTTPS的TLS是在传输层
- HTTP是
无状态
的,HTTPS是有状态
的
网络攻击
- sql 注入 就是通过把 SQL 命令插入到 Web 表单递交或输入域名或⻚面请求的查询字符串, 最终达到欺骗服务器执行恶意的 SQL 命令 防御: 对用户的输入进行校验, 通过正则表达式或限制⻓度, 对单引号和双引号进行转换等; 加密或者 hash 掉密码和敏感的信息;
- CSRF 跨站请求伪造, 劫持用户登录认证信息 防御: 验证 token, 验证 Referer, 设置 SameSite
- XSS(cross-site scripting) 跨站脚本攻击, 植入攻击脚本 类型: 储存型, 反射型, DOM 型 防御: 输入检查(<>;), 设置 httpOnly, 开启 CSP
- XSS 和 CSRF 有什么区别 XSS 是获取信息, 不需要提前知道其他用户⻚面的代码和数据包。 CSRF 是代替用户完成指定的动作, 需要知道其他用户⻚面的代码和数据包: 登录受信任网站 A, 并在本地生成 Cookie, 在不登出 A 的情况下, 访问危险网站 B
跨域
同源策略(SOP/Same origin policy)是浏览器最基本的安全功能, 可以避免浏览器受到 XSS、CSFR 等攻击。 同源策略指的是协议,域名,端口三者相同,任一不同就会导致跨域
- 跨域的解决方案:
-
jsonp -->
<script>
标签可以通过 src 填上目标地址从而发出 GET 请求,实现跨域请求并拿到响应- 优点: 实现简单, 兼容性好
- 缺点: 仅支持 get 方法, 容易受到 XSS 攻击
- 实现:
const jsonp = ({ url, params, callbackName }) => { const generateURL = () => { let dataStr = ''; for(let key in params) { dataStr += `${key}=${params[key]}&`; } dataStr += `callback=${callbackName}`; return `${url}?${dataStr}`; }; return new Promise((resolve, reject) => { // 初始化回调函数名称 callbackName = callbackName || Math.random().toString.replace(',', ''); // 创建 script 元素并加入到当前文档中 let scriptEle = document.createElement('script'); scriptEle.src = generateURL(); document.body.appendChild(scriptEle); // 绑定到 window 上,为了后面调用 window[callbackName] = (data) => { resolve(data); // script 执行完了,成为无用元素,需要清除 document.body.removeChild(scriptEle); } }); } jsonp({ url: 'http://localhost:3000', params: { a: 1, b: 2 } }).then(data => { // 拿到数据进行处理 console.log(data); // 数据包 })
-
document.domain + iframe 跨域 (仅限主域相同, 子域不同的跨域应用场景) 两个页面都通过 js 强制设置 document.domain 为基础主域, 就实现了同域
// 父窗口:
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
// 子窗口
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
- location.hash + iframe
- window.name + iframe 跨域
- postMessage 跨域
// a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
// b.html
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>
-
跨域资源共享(CORS) --> xhr.withCredentials = true; 需要后端配合
let xhr = new XMLHttpRequest(); xhr.withCredentials = true;
-
nginx 反向代理
-
nodejs 中间件代理跨域
-
WebSocket 协议跨域 --> Socket.io
简单请求和预检请求
同源策略, 为了防止跨域请求恶意访问跨域响应的数据, 浏览器限制从脚本内发起的跨源 HTTP 请求, 除非使用 CORS 头文件
-
简单请求: 不会触发 CORS(浏览器限制) 预检请求, 这样的请求为简单请求, 需要满足以下条件:
- 请求方法为 GET、POST 或者 HEAD
- 请求头中不能加入其他信息
- 请求头的取值范围: Accept、Accept-Language、Content-Language、Content-Type(只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
-
预检请求: 非简单请求的 CORS 请求, 会在正式通信之前, 增加一次 HTTP 查询请求, 称为预检请求; 必须先使用 OPTIONS 方法发起一个预检请求到服务器, 以获知服务器是否允许该实际请求。预检请求的使用, 可以避免跨域请求对服务器的用户数据产生未预期的影响;
-
预检请求的头信息包含一些特殊字段:
- Access-Control-Request-Method: 必填字段, 用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法, 如 POST;
- Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串, 指定浏览器 CORS 请求会额外发送的头信息字段, 如 content-type,x-secsdk-csrf-token;
- access-control-allow-origin: 表示 "https://..." 可以请求数据, 也可以设置为* 符号, 表示统一任意跨源请求;
- access-control-max-age: 可选字段, 指定本次预检请求的有效期, 单位为秒
http 常用状态码
| 状态码 | 含义 | | ------ | ----------------------------------------------------------- | | 204 | 无内容 - 服务器成功处理了请求,但没有返回任何内容 | | 304 | 未修改 - 自从上次请求后,请求的网页未修改过(协商缓存返回值) | | 404 | 未找到 - 服务器找不到请求的网页 | | 504 | 网关超时 | | | | | 1xx | 协议处理的中间状态,还需要后续操作 | | 2xx | 成功状态 | | 3xx | 重定向状态 | | 4xx | 请求报文有误 | | 5xx | 服务器端发生错误 |
扩展阅读(~~学不动了, 下面的先不学了~~)
requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画, 并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数, 该回调函数会在浏览器下一次重绘之前执行...