参考
写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?
详解vue的diff算法
VueDiff算法的简单分析和一些个人思考
传统diff、react优化diff、vue优化diff
防抖:只要输入频率持续高于阈值,函数就不触发,当最后一次触发频率低于阈值时执行,将多次频繁执行合并为一次,函数固定时间执行次数与输入频率有关
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
节流:输入频率持续高于阈值时,函数转换为低频率执行,即将函数由高频执行降为低频,函数固定时间执行次数与输入频率无关
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
参考
什么是防抖和节流?有什么区别?如何实现?
前端面试查漏补缺–(一) 防抖和节流
1.Set为不含重复值的类数组结构
2.WeakSet为存储对象的类数组结构;且垃圾回收机制不考虑WeakSet成员
3.Map为Value-Value的Hash结构,键名不限于字符串
4.WeakMap键值只能为对象;且垃圾回收机制不考虑WeakMap键值
数据:
let data = {
a: {
c: {
g: {
m: {
r: true,
s: true
}
},
h: {
n: {
t: true
},
o: true
}
},
d:{
i: true
}
},
b: {
e: {
j: true,
k: {
p: true,
q: true
}
},
f: {
l: true
}
}
}
深度遍历:
const depthFirstTraversing = (data)=>{
let traversingRes = [];
for(let key in data){
// console.log(key);
traversingRes.push(key);
if(data[key] !== true){
traversingRes = traversingRes.concat(depthFirstTraversing(data[key]))
}
}
return traversingRes;
}
console.log(depthFirstTraversing(data));
广度遍历
const breadthFirstTraversing = (data)=>{
let traversingRes = [];
let leveNodes = [];
for(let key in data){
leveNodes.push({key, data: data[key]});
}
while(leveNodes.length > 0){
let item = leveNodes.shift();
traversingRes.push(item.key);
for(let childKey in item.data){
leveNodes.push({key: childKey, data: item.data[childKey]})
};
}
return traversingRes;
}
console.log(breadthFirstTraversing(data))
data同上题
利用DFS深拷贝对象
const deepCopyThroughDFS = (data)=>{
let copyRes = {};
for(let key in data){
// console.log(key);
if(data[key] !== true){
copyRes[key] = deepCopyThroughDFS(data[key]);
}else{
copyRes[key] = true;
}
}
return copyRes;
}
利用BFS深拷贝对象
const deepCopyThroughBFS = (data)=>{
let copyRes = {};
let leveNodes = [];
for(let key in data){
leveNodes.push({key, data: data[key], parentData: copyRes});
}
while(leveNodes.length > 0){
let item = leveNodes.shift();
if(item.data !== true){
item.parentData[item.key] = {};
for(let childKey in item.data){
leveNodes.push({key: childKey, data: item.data[childKey], parentData: item.parentData[item.key]})
};
}else{
item.parentData[item.key] = true;
}
}
return copyRes;
}
es6 class的实例方法,实例属性,原型链方法,静态方法(static)支持实现
es6 class的原型链属性,静态属性不支持实现,暂时用es5方式实现
参考
JS 基础之异步
js执行机制:
1.js任务分为同步任务和异步任务,异步任务进入任务队列,根据macrotasks和microtasks分别放入macroqueue和microqueue
2.macrotasks,每次执行栈执行的代码就是一个宏任务,主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
3.microtasks,当前 task 执行结束后立即执行的任务,主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
4.每个event loop执行一个macrotask后会清空当前产生的macrotask产生的microtasks,然后进入下个event loop
5.Promise构造函数是同步的立即执行函数, 当在 executor 中执行 resolve 或者 reject 的时候, 此时是异步操作
6.async通过返回Promise来执行实现await,await通过返回一个Promise对象来实现同步的效果
参考:
js event loop 事件循环
常见异步笔试题
浏览器的Tasks、microtasks、 queues 和 schedules
setTimeout、Promise、Async/Await 的区别
Promise 源码:实现一个简单的 Promise
Promise原理讲解 && 实现一个Promise对象 (遵循Promise/A+规范)
面试精选之Promise
已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
const flattenData = (data)=>{
var newData = [];
data.forEach((item)=>{
if(typeof item === 'object'){
newData = newData.concat(flattenData(item))
}else{
newData.push(item)
}
})
return newData;
}
const sortAndDeduplication = (data)=>{
const sort = (a,b)=>{
return a - b;
}
var newData = [];
newData = data.sort(sort).reduce((acc, cVlue)=>{
if(cVlue !== acc[acc.length - 1]){
acc.push(cVlue);
}
return acc
}, [])
return newData;
}
const flatAndSortAndDeduplication = (data)=>{
// let flatData = flattenData(data);
let sortAndDeduplicatedData = sortAndDeduplication(flattenData(data));
return sortAndDeduplicatedData
}
console.log(flatAndSortAndDeduplication(arr))
const _new = function(){
const [klass, ...args] = arguments;
var newInstance = {};
klass.apply(newInstance, args);
newInstance.__proto__ = klass.prototype
return newInstance;
}
let classA = function(a, b){
this.a = a;
this.b = b;
}
classA.prototype = {
geta: function(){
console.log(this.a)
},
setb: function(b){
this.b = b
}
}
// 实例化classA
let aa = _new(classA, 1, 2)
参考:
作为前端的你了解多少tcp的内容
TCP 请求头
网络分层TCP/IP 与HTTP
谈谈你对 TCP 三次握手和四次挥手的理解
参考:
介绍 HTTPS 握手过程
SSL/TLS协议运行机制的概述
HTTP和HTTPS协议,看一篇就够了
https握手
介绍 HTTPS 握手过程
介绍下 HTTPS 中间人攻击
HTTPS 握手过程中,客户端如何验证证书的合法性
参考:
你真的理解setState吗?
关键点:扁平化安装,若存在多个不兼容版本则需嵌套安装
参考:
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/22
详解npm的模块安装机制
原始类型:boolean、undefined、string、number、symbol
对象类型:null ,Array、function、Object
1.Object.prototype.toString.call([]) // “[object Array]”
借用Object的toString方法,基本可以判断所有的数据类型
2.[] instanceof Array // true
判断[]原型链上是否能找到Array类型,仅对对象类型的数据有效,对原始类型无效
3.Array.isArray([]) // true
判断是否为数组,与2方法比较,该方法可以判断出iframe数组
4.typeof [] // “object”
注意:该方法不能判断对象类型,对象类型输出均为object,只能判断原始类型
参考:
介绍下重绘和回流(Repaint & Reflow),以及如何进行优化
参考:
观察者模式和发布订阅模式的区别
介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景
参考:
Vuex、Flux、Redux、Redux-saga、Dva、MobX
1.Nodejs v11以下:
执行完第一个阶段的所有任务(包含macrotask与对应产生的microtasks),再执行完nextTick macrotasks队列里面的内容, 然后执行完微任务队列的内容
2.Nodejs v11及以上:
执行完每一个macrotask,再执行macrotask对应产生的microtasks,如此循环
参考:
浏览器和Node 事件循环的区别
参考:
前端中的模块化开发
1.const和let会创造block作用域,只要在同一个block内都可以拿到变量
2.顶部var变量挂在window下
示例:
如下创造了2个Block,分别对应变量e和f;1个Script(也可以理解为顶级Block),对应变量a和b,剩下的c和d作为全局变量
1.xss:跨站脚本攻击,分为存储型 XSS,反射型 XSS和DOM 型 XSS。向被攻击网站注入恶意代码并执行
2.csrf:跨站请求伪造,利用用户在被攻击网站已经保存的信息,在第三方网站向被攻击网站发送请求,冒充用户执行操作
3.预防
xss:过滤用户输入;转义html
csrf:同源检测;添加csrf token
4.
遭受xss攻击的网站,cookie和token均可以被劫持
遭受csrf攻击的网站,cookie可以被劫持,token只要不放在cookie就不会被劫持,或者只要不直接使用cookie token作为键值,被劫持的token也为无效token
一个mvvm包含:
1.编译器(compiler,基础模板引擎)
提取指令,data等,渲染真实dom
2.数据劫持(observer,利用defineProperty)
核心方法
defineReactive(obj,key,value){
// 在获取某个值的适合 想弹个框
let that = this;
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){ // 当取值时调用的方法
return value;
},
set(newValue){ // 当给data属性中设置值的适合 更改获取的属性的值
if(newValue!=value){
// 这里的this不是实例
that.observe(newValue);// 如果是设置的是对象继续劫持
value = newValue;
}
}
});
}
3.view观察者(watcher)
watcher在拥有data挂载的模板编译阶段实例化一次,确定回调更新函数,使view成为model更新的订阅者;watcher在内部的构造函数内将自身与dep关联并加入dep.subs队列
4.model发布主体(dep)
数据observer化时,在get方法内将关联watcher加入队列;在set方法发布订阅
5.数据代理(proxy)
将vm.data.foo代理到vm.foo
6.监听dom事件
在编译阶段查看是否涉及数据绑定事件监听,如v-model等,并设置回调函数更新vm.data
7.diff更新view(watcher执行订阅更新之后)
以上参考:
https://juejin.im/post/5af8eb55f265da0b814ba766
参照vue2.x(上例)的响应式设计模式,将数据劫持部分的Obejct.defineProperty替换为Proxy即可,其他部分,如compile,watcher,dep,事件监听等基本保持不变,简单实现代码如下:
class Watcher{
constructor(cb){
this.cb = cb;
}
update(){
this.cb()
}
}
class Dep{
constructor(){
this.subs = [];
}
publish(){
this.subs.forEach((item)=>{
item.update && item.update();
})
}
}
class MVVM{
constructor(data){
let that = this;
this.dep = new Dep();
this.data = new Proxy(data,{
get(obj, key, prox){
that.dep.target && that.dep.subs.push(that.dep.target);
return obj[key]
},
set(obj, key, value, prox){
obj[key] = value;
that.dep.publish();
return true;
}
})
this.compile();
}
compile(){
let divWatcher = new Watcher(()=>{
this.compileUtils().div();
})
this.dep.target = divWatcher;
this.compileUtils().div();
this.dep.target = null;
let inputWatcher = new Watcher(()=>{
this.compileUtils().input();
})
this.dep.target = inputWatcher;
this.compileUtils().input();
this.compileUtils().addListener();
this.dep.target = null;
}
compileUtils(){
let that = this;
return {
div(){
document.getElementById('foo').innerHTML = that.data.foo;
},
input(){
document.getElementById('bar').value = that.data.bar;
},
addListener(){
document.getElementById('bar').addEventListener('input', function(){
that.data.bar = this.value;
})
}
}
}
}
let mvvm = new MVVM({foo: 'foo233', bar: 'bar233'})
通过mvvm.data.foo
或者mvvm.data.bar
可以操作数据,可以观察到view做出了反应;在输入框改变输入值,也可以通过mvvm.data
观察到数据被触发改变
/**
* 更新
* @param {*} newState 新状态
*/
ReactClass.prototype.setState = function(newState) {
// 拿到ReactCompositeComponent的实例
// 在装载的时候保存
// 代码:this._reactInternalInstance = this
this._reactInternalInstance.receiveComponent(null, newState);
};
基本元素的 receiveComponent
基础元素的更新包括两方面
属性的更新,包括对特殊属性比如事件的处理
子节点的更新
子节点的更新比较复杂,是提升效率的关键,所以需要处理以下问题:
diff - 拿新的子节点树跟以前老的子节点树对比,找出他们之间的差别。
patch - 所有差别找出后,再一次性的去更新。
React 源码分析
React 源码剖析系列 - 不可思议的 react diff
[译] Virtual Dom 和 Diff 算法在 React 中是如何工作的?
了解react、vue的一大核心技术:虚拟DOM的实现原理
从Preact了解一个类React的框架是怎么实现的(一): 元素创建
从Preact了解一个类React的框架是怎么实现的(二): 元素diff
从Preact了解一个类React的框架是怎么实现的(三): 组件
ARV 渲染实现比较总结
1.reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
arr.reduce(callback(accumulator, currentValue[, index[, sourceArray]])[, initialValue])
2.filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
3.some() 方法测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个Boolean类型的值。
arr.some(callback(element[, index[, array]])[, thisArg])
4.forEach() 方法对数组的每个元素执行一次提供的函数。没有办法中止或者跳出 forEach() 循环,除了抛出一个异常.
arr.forEach(callback(element[, index[, array]])[, thisArg]);
5.map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
参考:
网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么
Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法
以下代码输出b函数
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
因为iife的函数表达式函数名为不可变量,在 strict 模式下会报错,非 strict 模式下静默失败
参考:
js的作用域问题?
下面的代码打印什么内容,为什么?
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
以上代码输出:
undefined -> 10 -> 20,因为IIFE作用域内a在后面声明时被提升了
如果去掉 var a = 20,则打印10->5->5,因为IIFE内部取到的是外部定义的变量a
1.强制缓存
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。Cache-Control优先级高于Expires,Expires是http1.0的产物,Cache-Control是http1.1的产物
2.协商缓存
协商缓存可以在response header中添加 Last-Modified和Etag实现。Etag优先级高于 Last-Modified,Etag精度较好,Last-Modified性能较好
3.强缓存优先级高于协商缓存
4.用户行为对浏览器缓存的影响
BFC 就是块级格式上下文,是页面盒模型布局中的一种 CSS 渲染模式,相当于一个独立的容器,里面的元素和外部的元素相互不影响
1.触发BFC的条件
参考:
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/60
1.Object.defineProperty不能检测数组下标变动,而Proxy可以代理整个数据
2.Object.defineProperty代理对象时需要遍历对象的每个属性(嵌套对象需要递归遍历),Proxy可以直接代理整个对象
参考:
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/90
1.定宽高:
2.不定宽高:
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x)
console.log(b.x)
以上代码输出:
undefined->{n:2}
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
以上代码输出:(push时array like对象length +1)
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
判断类数组的方法
实现以下类:
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food');
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food
关键点:
class LazyManClass{
constructor(name){
console.log(`Hi I am ${name}`)
this.tasksQueue = [];
setTimeout(()=>{
this.next();
})
}
eat(meal){
let that = this;
let fn = function(){
(function(meal){
console.log(`I am eating ${meal}`);
that.next();
})(meal);
}
this.tasksQueue.push(fn);
return this;
}
sleep(time){
let that = this;
let fn = function(){
(function(time){
setTimeout(function(){
console.log(`等待了 ${time}s`);
that.next();
}, time*1000)
})(time);
}
this.tasksQueue.push(fn);
return this;
}
sleepFirst(time){
let that = this;
let fn = function(){
(function(time){
setTimeout(function(){
console.log(`等待了 ${time}s`);
that.next();
}, time*1000)
})(time);
}
this.tasksQueue.unshift(fn);
return this;
}
next(){
let fn = this.tasksQueue.shift();
fn&&fn();
}
}
const LazyMan = (name)=>{
return new LazyManClass(name)
}
参考:
要求设计 LazyMan 类,实现以下功能
箭头函数没有自己的this,没有arguments,没有prototype,因此不能作为构造函数使用
1.关键点:
const carousel = function(id, speed=1000){
this.speed = speed;
this.root = document.getElementById(id);
this.startTime = new Date();
this.req = null;
this.begin();
}
carousel.prototype = {
begin(){
this.render();
let timer = setInterval(()=>{
// cancelAnimationFrame(this.req);
// clearInterval(timer);
let lists = this.root.getElementsByTagName('li');
let length = lists.length;
let first = lists[0];
this.root.appendChild(first.cloneNode(true));
this.root.removeChild(first);
this.root.style.left = '0px';
this.startTime = new Date();
// this.render();
}, this.speed)
},
render(){
this.root.style.left = -(new Date() - this.startTime)/this.speed*100 + 'px'
this.req = requestAnimationFrame(this.render.bind(this));
}
}
let carouselIns = new carousel("carousel")
1.概念:
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
2.兼容:
window.requestAnimFrame = (function() {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
window.setTimeout(callback, 1000 / 60)
}
)
})()
3.优势:
关键点
参考:
介绍下 BFC、IFC、GFC 和 FFC
使用compositionstart + compositionend判断,如过程不想触发input事件则加boolean值判断
// 实现一个函数
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
关键点:
fn.bind(this, ...args)
保存所有参数const originAdd = (x,y,z)=>{
return x+y+z
}
const currying = function(fn, length){
length = length || fn.length
return function(...args){
return args.length >= length ? fn.apply(this, args) : currying(fn.bind(this, ...args), length - args.length)
}
}
const add = currying(originAdd)
关键点
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
参考:
深入高阶函数应用之柯里化
请实现一个 add 函数,满足以下功能
详解JS函数柯里化
以下数据结构中,id 代表部门编号,name 是部门名称,parentId 是父部门编号,为 0 代表一级部门,现在要求实现一个 convert 方法,把原始 list 转换成树形结构,parentId 为多少就挂载在该 id 的属性 children 数组下,结构如下:
// 原始 list 如下
let list =[
{id:1,name:'部门A',parentId:0},
{id:2,name:'部门B',parentId:0},
{id:3,name:'部门C',parentId:1},
{id:4,name:'部门D',parentId:1},
{id:5,name:'部门E',parentId:2},
{id:6,name:'部门F',parentId:3},
{id:7,name:'部门G',parentId:2},
{id:8,name:'部门H',parentId:4}
];
const result = convert(list, ...);
实现如下:
const convert = (list)=>{
let rs = [], listMap = {}, parent;
for(let i=0;i<list.length;i++){
let temp = list[i];
listMap[temp.id] = temp;
}
list.forEach((temp)=>{
if(temp.parentId === 0){
rs.push(temp);
}else{
parent = listMap[temp.parentId];
!parent.children && (parent.children = []);
parent.children.push(temp)
}
})
return rs;
}
let list = [
{id:3,name:'部门C',parentId:1},
{id:4,name:'部门D',parentId:1},
{id:5,name:'部门E',parentId:2},
{id:6,name:'部门F',parentId:3},
{id:7,name:'部门G',parentId:2},
{id:8,name:'部门H',parentId:4},
{id:1,name:'部门A',parentId:0},
{id:2,name:'部门B',parentId:0},
];
const result = convert(list);
console.log(result)
// Promise.race
const _race = (p)=>{
return new Promise((resolve, reject)=>{
let status = false;
p.forEach((item)=>{
item.then(resolve, reject)
})
})
}
// Promise.all
all(list) {
return new Promise((resolve, reject) => {
let resValues = [];
let counts = 0;
for (let [i, p] of list) {
resolve(p).then(res => {
counts++;
resValues[i] = res;
if (counts === list.length) {
resolve(resValues)
}
}, err => {
reject(err)
})
}
})
}
// Promise.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
参考:
介绍下 HTTPS 中间人攻击
.native
修饰符的事件。最终EventTarget.addEventListener()
挂载事件.native
)会调用原型上的$on,$emit
(包括一些其他api $off,$once
等等)$on
方法放入观察者队列,子组件调用$emit
方法可发布订阅关键点
转字符串,二分,前后部分交换,递归
const reverseOrder = (num)=>{
let numStr = num.toString();
let length = numStr.length;
if(length == 1){
return numStr;
}else{
return length%2 === 0 ?
reverseOrder(numStr.slice(length/2,length)) + reverseOrder(numStr.slice(0,length/2)) :
reverseOrder(numStr.slice((length+1)/2,length)) + numStr[(length-1)/2] + reverseOrder(numStr.slice(0,(length-1)/2))
}
}
console.log(reverseOrder(12345678))
两个维度:
1.在生命周期钩子函数内将store.state.foo赋值给data,data无法刷新
解决:将store.state.foo绑定到计算属性
2.vuex无法持久化:
结合local/session storage,在set数据时写入缓存;在store.js读取缓存
1.格式
请求行(request line)、请求头部(header)、空行和请求数据
2.状态码
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |