前端面试题整理-持续更新CSS,JS,React,http等

717 阅读38分钟

undefined , null

null是对象原型链的终点,null == undefined

  • undefined不是保留字,它只是全局对象的一个属性,在低版本 IE 中能被重写。

  • undefined 在 ES5 中已经是全局对象的一个只读(read-only)属性了,它不能被重写。但是在局部作用域中,还是可以被重写的。

  • void 0 === undefined,用 void 0 代替 undefined 能节省不少字节的大小,事实上,不少 JavaScript 压缩工具在压缩过程中,正是将 undefined 用 void 0 代替掉了。

CSS

position

position: static | relative | absolute | sticky | fixed

  • static: 该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。此时 top, right, bottom, left 和 z-index 属性无效

  • relative: 相对定位的元素是在文档中的正常位置偏移给定的值,但是不影响其他元素的偏移

  • absolute: 相对定位的元素并未脱离文档流,而绝对定位的元素则脱离了文档流。在布置文档流中其它元素时,绝对定位元素不占据空间。绝对定位元素相对于最近的非 static 祖先元素定位

  • sticky: 粘性定位可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位

    • 粘性定位常用于定位字母列表的头部元素。标示 B 部分开始的头部元素在滚动 A 部分时,始终处于 A 的下方。而在开始滚动 B 部分时,B 的头部会固定在屏幕顶部,直到所有 B 的项均完成滚动后,才被 C 的头部替代
  • fixed: 固定定位与绝对定位相似,但元素的包含块为 viewport 视口。该定位方式常用于创建在滚动屏幕时仍固定在相同位置的元素

animation transition

animation: name | duration | timing-function | delay | iteration-count | direction | fill-mode | play-state

transition: property name | duration | timing function | delay

transition从:hover延伸出来;animation从flash延伸出来

  • 不同点

    • 触发条件不同。transition通常和hover等事件配合使用,由事件触发,强调过渡。animation则和gif动态图差不多,立即播放,多个关键帧,实现自由动画。
    • 循环。 animation可以设定循环次数。
    • 精确性。 animation可以设定每一帧的样式和时间。tranistion 只能设定头尾。 animation中可以设置每一帧需要单独变化的样式属性,可以通过@keyframe控制当前帧属性,更灵活, transition中所有样式属性都要一起变化
    • 与javascript的交互。animation与js的交互不是很紧密。tranistion和js的结合更强大。js设定要变化的样式,transition负责动画效果。

圣杯布局,双飞翼布局(淘宝UED)

  • 三列布局,中间宽度自适应,两边定宽
  • 中间栏要在浏览器中优先展示渲染
  • 允许任意列的高度最高

解决:中间栏div内容不被遮挡问题

区别:三栏全部float浮动;圣杯布局,为了中间div内容不被遮挡,将中间div设置了左右padding-left和padding-right后,将左右两个div用相对布局position: relative并分别配合right和left属性,以便左右两栏div移动后不遮挡中间div;双飞翼布局,为了中间div内容不被遮挡,直接在中间div内部创建子div用于放置内容,在该子div里用margin-left和margin-right为左右两栏div留出位置。简单说起来就是”双飞翼布局比圣杯布局多创建了一个div,但不用相对布局了“

因为浏览器渲染引擎在构建和渲染渲染树是异步的(谁先构建好谁先显示),方便SEO

回流,重绘

浏览器使用流式布局模型。浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree。有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一。回流必将引起重绘,重绘不一定会引起回流

  1. 当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流,如何引发回流

    • 页面首次渲染,浏览器窗口改变大小,元素尺寸或位置发生改变,元素内容变化(文字数量或图片大小等等),元素字体大小变化,DOM操作,激活CSS伪类(例如::hover),查询某些属性或调用某些方法

    • scrollTo(),getComputedStyle(),scrollIntoView()、scrollIntoViewIfNeeded(),getBoundingClientRect()

  2. 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘

如何避免

  1. 避免使用table布局
  2. 尽可能在DOM树的最末端改变class
  3. 避免设置多层内联样式
  4. 将动画效果应用到position属性为absolute或fixed的元素上
  5. 避免使用CSS表达式(例如:calc())
  6. 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性
  7. 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
  8. 先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
  9. 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
  10. 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流

居中

  1. 水平居中
<!--行内元素:文本text、图像img、按钮超链接等 -->
.center {
    text-align: center;
}
<div class="center">水平居中</div>

<!--块级定宽元素 -->
.center {
    width: 200px;
    margin: auto;
}
<div class="center">水平居中</div>

<!--块级不定宽元素,table -->
.center {
    display: table;
    margin: auto;
}
<div class="center">水平居中</div>

<!--块级不定宽元素,inline-block -->
.center {
    text-align: center;

    >div {
        display: inline-block;
    }
}
<div class="center"><div>inline-block<div></div>

<!--块级不定宽元素,flex -->
.center {
    text-align: flex;
    justify-content: center;
}
<div class="center"><div>flex<div></div>
  1. 垂直居中
<!--单行文本 line-height=height或者padding-top=padding-bottom-->
.colCenter {
    height: 24px;
    line-height: 24px;
}
<div class="colCenter">垂直居中</div>

<!--多行文本,利用table -->
.colCenter {
    width: 200px;
    height: 200px;
    display: table;

    >div {
        display: table-cell;
        vertical-align: middle;
    }
}
<div class="colCenter"><div>垂直居中<div></div>

<!--块级元素,flex -->
.colCenter {
    width: 200px;
    height: 200px;
    display: flex;
    align-items: center;
}
<div class="colCenter"><div>垂直居中<div></div>

除此之外,还可以利用padding和margin等计算居中

块格式化上下文(Block Formatting Context,BFC)

是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。触发条件如下:

  • 根元素<html>
  • 浮动元素(元素的 float 不是 none)
  • 行内块元素(元素的 display 为 inline-block)
  • 表格单元格(元素的 display为 table-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值
  • 匿名表格单元格元素(元素的 display为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是HTML table、row、tbody、thead、tfoot的默认属性)或 inline-table)
  • overflow 值不为 visible 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layout、content或 paint 的元素
  • 弹性元素(display为 flex 或 inline-flex元素的直接子元素
  • 网格元素(display为 grid 或 inline-grid 元素的直接子元素)
  • 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)
  • column-span 为 all 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)
  • display的值为inline-block、table-cell、table-caption
  • position的值为absolute或fixed

作用:清除浮动,浮动塌陷,外边距塌陷,元素重叠

  • 自适应两栏布局
  • 可以阻止元素被浮动元素覆盖
  • 可以包含浮动元素——清除内部浮动
  • 分属于不同的BFC时可以阻止margin重叠

canvas和svg的区别,适用场景

  • Canvas提供的功能更原始,适合像素处理,动态渲染和大数据量绘制;Canvas基于像素,提供2D绘制函数,是一种HTML元素类型,依赖于HTML,只能通过脚本绘制图形

    • 定制型更强,可以绘制绘制自己想要的东西
    • 非dom结构形式,用JavaScript进行绘制,涉及到动画性能较高
    • 事件分发由canvas处理,绘制的内容的事件需要自己做处理
    • 依赖于像素,无法高效保真,画布较大时候性能较低
    • 不支持事件处理器
    • 适合游戏应用
  • SVG功能更完善,适合静态图片展示,高保真文档查看和打印的应用场景,不依赖于像素,无限放大后不会失真;SVG为矢量,提供一系列图形元素(Rect, Path, Circle, Line …),还有完整的动画,事件机制,本身就能独立使用,也可以嵌入到HTML中

    • 矢量图,不依赖于像素,无限放大后不会失真。
    • 以dom的形式表示,事件绑定由浏览器直接分发到节点上
    • dom形式,涉及到动画时候需要更新dom,性能较低。
    • 支持事件处理器
    • 不适合游戏应用

JS

数据类型:number,string,null,boolean,undefined,object(Array,Function,RegExp,Date)

类型判断:

  • typeof: 1.基本数据类型除了null返回object,其他都正确;2.引用数据类型,除了function返回function,其他都返回object

  • instanceof: A instanceof B,A是否为B的实例,instanceof 检测的是原型;例如 [].proto——》Array.prototype.proto——》Object.prototype.proto——》null

  • constructor: null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断;例如:'a'.constructor === String

  • toString: toString() 是 Object 的原型方法,对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。用法:Object.prototype.toString.call('')

  • 内置Symbol接口

原型,原型链:

原型是指prototype对象,表示类型之间的关系。原型可以储存属性和方法,都会被他的实例继承。每个实例对象都有__proto__,来指向他的构造函数的原型对象,该对象也有自己的原型对象,通过__proto__层层向上,最后指向null.null没有原型。

  • js分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性

  • 构造函数的__proto__都指向Object.prototype, 因为原型对象本身是普通对象

function F(){};
F.prototype.__proto__ = Object.prototype;
Array.prtotype.__proto__ = Object.prototype;
Object.prototype.__proto__ = null;

F.__proto__ = Function.prototype;

var f = new F();
f.__proto__ = F.prototype;

call,apply,bind

改变函数执行时的上下文,改变函数运行时的this指向

  • bind 是创建一个新的函数,call,apply会执行函数
  • 参数不同

map/set,weakMap/weakSet

Set和Map都可以用来生成新的 Map。Set类似数组,没有重复的值;Map类似对象

// 去重,Set接受数组或者类数组为参数
var arr = new Set([1,2,3,2,4])
[...arr]  // [1,2,3,4]

// WeakSet 中的对象都是弱引用,垃圾回收机制不考虑 WeakSet 对该对象的引用,WeakSet不可遍历
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]} 注意,是a数组的成员成为 WeakSet 的成员,而不是a数组本身。这意味着,数组的成员只能是对象。

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);
map.set('name','jin');

// WeakMap结构与Map结构类似,也是用于生成键值对的集合
// WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名;WeakMap的键名所指向的对象,不计入垃圾回收机制,WeakMap 弱引用的只是键名,而不是键值

// 基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。另一个用处是部署私有属性

堆栈:

栈内存一般储存基础数据类型,遵循后进先出的原则,大小固定并且有序;堆内存一般储存引用数据类型,JavaScript不允许直接访问堆内存中的位置,因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从栈中获取了该对象的地址引用(或者地址指针),然后再从堆内存中读取我们需要的数据

  • 栈:存储基础数据类型;按值访问;存储的值大小固定;由系统自动分配内存空间;空间小,运行效率高;先进后出,后进先出;栈中的DOM,ajax,setTimeout会依次进入到队列中,当栈中代码执行完毕后,再将队列中的事件放到执行栈中依次执行

  • 堆: 存储引用数据类型;按引用访问;存储的值大小不定,可动态调整;主要用来存放对象;空间大,但是运行效率相对较低;无序存储,可根据引用直接获取

执行上下文

  • 全局可执行上下文,函数可执行上下文,Eval可执行上下文

执行栈(先进后出)

  • 当程序进入执行环境,将执行上下文推入执行栈中(入栈)
  • 当程序执行完成之后,执行上下文就会被销毁,并从栈顶被推出(出栈)

eventloop事件循环

"主线程"(执行栈)和任务队列<先进先出>(主要是各种I/O操作)的通信,被称为事件循环

  1. node环境 还是 浏览器环境
  2. 任务队列(task queue):分为3,4
  3. 宏任务task(macro-task):script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI render
  4. 微任务jobs(micro-task):process.nextTick,Promise,Async/Await(实际就是promise),MutationObserver(html5新特性)
  5. 事件循环机制:主线程 ->所有微任务 ->宏任务(先进先执行,如果里面有微任务,则下一步先执行微任务,否则继续执行宏任务)

node 11之后的特性已经向浏览器看齐了。两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的

垃圾回收机制

  1. 引用计数:0引用时会被回收,IE 6, 7 使用引用计数方式对 DOM 对象进行垃圾回收。该方式常常造成对象被循环引用时内存发生泄漏
  2. 标记-清除算法:这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”,因为“有零引用的对象”总是不可获得的,垃圾回收器将定期从根开始,找所有从根开始引用的对象,对堆内存从头到尾进行线性的遍历,进行标记,而没有被标记的对象就被清除

浅拷贝:遍历对象赋值,只能用作基本数据类型的拷贝

  • =赋值

  • Object.assign()

深拷贝:递归浅拷贝

  • JSON,但是会破坏结构

  • $.extend

  • lodash

function deepCopy(a,b){
    let c = b || {};
    for(let i in a){
        if(typeof a[i] == 'object'){
            c[i] = a[i].constructor === Array ? [] : {};
            deepCopy(a[i],c[i]);
        }else{
            c[i] = a[i];
        }
    }
    
    return c;
}
var parent = {
    name: 'king',
    animals: {
        dogs: 2,
        cat: 1
    },
    area: ['A','B']
}
var obj = deepCopy(parent,{});

json序列化和反序列化的缺点

new

Person (){

}
var obj = new Object();
obj.__proto__ = Person.prototype;
Person.call(obj)

构造函数和class(语法糖)的区别

  • 类的内部所有定义的方法,都是不可枚举的

  • 类和模块的内部,默认就是严格模式(this 实际指向的是undefined),非严格模式指向window,所以不需要使用use strict指定运行模式

  • 类不存在变量提升(hoist)

  • 类的方法内部如果含有this,它默认指向类的实例

  • 类的静态方法不会被实例继承,静态方法里的this指的是类,而不是他的实例

  • 类的继承extends,在constructor中使用super,让this指向当前类,等同于Parent.prototype.constructor.call(this)

class Parent {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    say(){
        console.log('hello')
    }
}

// 等价于
function Parent(name,age){
    this.name = name;
    this.age = age;
}
Parent.prototype.say = function (){
    console.log('hello');
}

var child = new Parent('king',12);

类的数据类型就是函数,类本身就指向构造函数;使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致;Object.assign方法可以很方便地一次向类添加多个方法;prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的;

function和箭头函数

  • 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象

  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误

  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替

  • 箭头函数不能用作 Generator 函数

  • function可以使this动态使用(比如监听按钮事件,this就会动态指向被点击的按钮对象),如果函数体内有大量的读写操作,尽量不要使用箭头函数

手动实现字符串单词统计,’sdsdfdewrwescvv’,统计各个出现次数

var str = 'sdsdfdewrwescvv';
let obj = {};
for (i = 0; i<str.length; i++){
    if(obj[str[i]]){
        obj[str[i]]++;
    }else{
        obj[str[i]] = 1;
    }
}
console.log(obj);

promise,generator,async/await 对比

promise手动实现

1. 设置三种状态,一但开始不能暂停,且状态只能被修改一次,pending,rejected,resolved(fulfilled)

2. 两个参数,resolve(),reject(),函数执行之后改变状态,执行的时候如果传递参数,就是then的参数

3. 回调函数,then(resolved,rejected(可选)),返回新promise实例,以便继续链式调用,注意是异步操作,使用settimeout实现

4. catch异常回调,Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

5.finally(无论成败,都进来执行回调函数),all,race(哪个结果返回快,就先返回,无论成败)

用async/await实现promise.all

1. promise.all的参数数组里面的promise,如果自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法

2. 参数数组里面的实例只要有一个变为rejected,或者都为fulfilled,就会调用回调函数

3. async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数

在await之后增加手动判断,抛出异常

es 6 module和cmd中的模块require有什么区别

  • Require是CommonJS的语法
  • export后面要跟能够解构的对象,export 命令规定的是对外接口,必须与模块内部变量建立一一对应的关系
  • require是动态加载的,import是编译的时候加载的

继承

  • 原型链继承
function Parent(){
    this.color = ['red','yellow']
}
function child() {}
child.prototype = new Parent();

var eg1 = new child();
eg1.color.push('white');
var eg2 = new child();
console.log(eg2)  // ["red", "yellow", "white"]

原型链方案存在的缺点:多个实例对引用类型的操作会被篡改
  • 构造函数继承
function Person(){
    this.color = ['red','yellow']
}
Person.prototype.say=function(){
    console.log('hi')
}
function child(){
    Person.call(this);
}
var eg1 = new child();
eg1.color.push('white');
console.log(eg1.say)  // undefined
var eg2 = new child();
console.log(eg2)  // ["red", "yellow"]

比前一种更耗内存,影响性能,每次都要执行建立Person实例
不能继承原型方法
  • 组合继承
把前两种组合一下

function Person(){
    this.age = 11;
    this.color = ['red','yellow']
}
Person.prototype.say=function(){
    console.log('hi')
}
function child(){
    Person.call(this);
}
child.prototype = new Parent();

child.prototype.constructor = child;
var eg1 = new child();

eg1同时继承child 和person

缺点: 子类实例中会存在两份相同的属性/方法
  • 原型式继承
即后来的Object.create内置函数,其原理为
function Person(o){
    var child = function(){};
    child.prototype = o;
    return new child();
}

但实际上new具体做了什么操作

var son = new Person();

当这段代码运行的时候,内部实际上执行的是:

// 创建一个空对象
var other = new Object();
// 将空对象的原型赋值为构造函数的原型
other.__proto__ = Person.prototype;
// 改变this指向
Person.call(other);

缓存 SessionStorage,localStorage,Cookie

存储方式

  • cookie会数据始终在同源http请求中携带,在浏览器和服务器之间来回传递
  • sessionStroage和localStroage仅保存在本地

存储大小

  • 单个cookie不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie
  • sessionStroage和localStroage可以达到5M

过期时间

  • 只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
  • sessionStorage,仅在当前浏览器窗口关闭之前有效
  • localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据

作用域

  • cookie和localStorage: 在所有同源窗口中都是共享的.只要浏览器不关闭,数据仍然存在
  • sessionStorage:打开多个相同的URL的Tabs页面,会创建各自的sessionStorage;关闭对应浏览器tab,会清除对应的sessionStorage

节流和防抖

  • 节流: 每隔一段时间,只执行一次函数;滚动页面加载,用时间戳或者定时器实现

  • 防抖: 连续的事件,只需触发一次回调;输入框实时搜索,setTimeout

// 清除之前的定时器,利用闭包重新创建一个
function debounce(func, wait) {
    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}

设计模式,观察者,发布订阅模式

  • 主要是消息的处理机制不同,一个同步一个异步,而异步的实现手段是使用MQ,类似于Kafka

跨域

  • 同源:协议+域名+端口都一样;当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”
  • 如果是协议和端口造成的跨域问题“前台”是无能为力的
  • 跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了
  1. jsonp: 利用 script 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要服务器做支持才可以;兼容性好,仅支持get方法且不安全,容易遭XSS攻击。实现方式可以返回一个promise对象,需要创建一个script标签,把API地址赋值给src

  2. cors: 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS;简单请求GET,POST,HEAD,且Content-Type为text/plain,multipart/form-data,application/x-www-form-urlencoded;复杂请求会在正式通信之前增加一次预检option请求,通过该请求来知道服务端是否允许跨域请求

  3. postMessage:允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

  4. websocket:用socket.io比原生websocket API好用,server安装ws,调用时new WebSocket('ws://localhost:3000')

  5. node中间件代理(两次跨域)

  6. nginx反向代理:搭建一个中转nginx服务器,用于转发请求,只需修改nginx配置,将前后端地址用ngnix转发到同一个下,支持所有浏览器,支持session,不影响服务器性能;每次修改nginx.conf之后需要执行ngnix -s reload生效;listen为代理地址,location.proxy_pass为前端真实访问地址

  7. document.domain+iframe;location.hash+iframe;window.name+iframe

CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案;用得比较多的跨域方案是cors和nginx反向代理;JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据;不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制

http

概念:超文本传输​​协议(HTTP)是一个用于传输超媒体文档(例如 HTML)的应用层协议。它是为 Web 浏览器与 Web 服务器之间的通信而设计的,但也可以用于其他目的。HTTP 遵循经典的客户端-服务端模型,客户端打开一个连接以发出请求,然后等待它收到服务器端响应。HTTP 是无状态协议,这意味着服务器不会在两个请求之间保留任何数据(状态)。该协议虽然通常基于 TCP/IP 层,但可以在任何可靠的传输层上使用;也就是说,不像 UDP,它是一个不会静默丢失消息的协议。RUDP——作为 UDP 的可靠化升级版本——是一种合适的替代选择

http特性:

1.HTTP 是无连接无状态的

2.HTTP 一般构建于 TCP/IP 协议之上,默认端口号是 80

3.HTTP 可以分为两个部分,即请求和响应。

HTTP各版本特性及区别

1.http0.9– 单行协议:请求由单行指令构成,以唯一可用方法GET开头,其后跟目标资源的路径(一旦连接到服务器,协议、服务器、端口号这些都不是必须的);响应也极其简单的:只包含响应文档本身。

2.http1.0:协议版本信息现在会随着每个请求发送(HTTP/1.0被追加到了GET行);状态码会在响应开始时发送;引入了HTTP头的概念;Content-Type让请求具备了传输除纯文本HTML文件以外其他类型文档的能力

3.http1.1:连接可以复用,节省了多次打开TCP连接加载网页文档资源的时间;增加管线化技术,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟;支持响应分块;引入额外的缓存控制机制;引入内容协商机制,包括语言,编码,类型等,并允许客户端和服务器之间约定以最合适的内容进行交换;感谢Host头,能够使不同域名配置在同一个IP地址的服务器上

4.http2.0:HTTP/2是二进制协议而不是文本协议。不再可读,也不可无障碍的手动创建,改善的优化技术现在可被实施;这是一个复用协议。并行的请求能在同一个链接中处理,移除了HTTP/1.x中顺序和阻塞的约束;压缩了headers。因为headers在一系列请求中常常是相似的,其移除了重复和传输重复数据的成本;其允许服务器在客户端缓存中填充数据,通过一个叫服务器推送的机制来提前请求

5.http3.0: 将弃用TCP协议,改为使用基于UDP协议的QUIC协议实现,该协议旨在使网页传输更快

TCP,UDP

  • 基于连接与无连接
  • 对系统资源的要求(TCP较多,UDP少)
  • UDP程序结构较简单
  • 流模式与数据报模式
  • TCP保证数据正确性,UDP可能丢包;
  • TCP保证数据顺序,UDP不保证。

http请求:

  • HTTP 请求由 3 个部分构成,分别是:状态行,请求头(Request Header),请求正文。

  • HTTP 响应由 3 个部分构成,分别是:状态行,响应头(Response Header),响应正文。

  • HTTP 响应中包含一个状态码,用来表示服务器对客户端响应的结果。 状态码一般由3位构成:

    1xx : 表示请求已经接受了,继续处理。 2xx : 表示请求已经处理掉了。 3xx : 重定向。 4xx : 一般表示客户端有错误,请求无法实现。 5xx : 一般为服务器端的错误。

  1. get:GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.
  2. post:POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用
  3. head: HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.
  4. put:PUT方法用请求有效载荷替换目标资源的所有当前表示。
  5. delete:DELETE方法删除指定的资源。
  6. connect:CONNECT方法建立一个到由目标资源标识的服务器的隧道
  7. options:OPTIONS方法用于描述目标资源的通信选项
  8. trace:TRACE方法沿着到目标资源的路径执行一个消息环回测试。
  9. patch:PATCH方法用于对资源应用部分修改

常见的状态码:

  • 200 OK 客户端请求成功。

  • 301 Moved Permanently 请求永久重定向。

  • 302 Moved Temporarily 请求临时重定向。

  • 304 Not Modified 文件未修改,可以直接使用缓存的文件。

  • 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。

  • 401 Unauthorized 请求未经授权,无法访问。

  • 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因。

  • 404 Not Found 请求的资源不存在,比如输入了错误的URL。

  • 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。

  • 502 Bad Gateway 是一种HTTP协议的服务器端错误状态代码,它表示作为网关或代理角色的服务器,从上游服务器(如tomcat、php-fpm)中接收到的响应是无效的

  • 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。

http与https的区别

  1. https协议需要到ca申请证书,一般免费证书较少,需要付费
  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议
  3. http默认80端口,https默认443端口
  4. http是无状态连接;https协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,安全性更高

对称加密,非对称加密

  1. 对称加密:一般小于256bit,密钥越大,加密越强,但是加密与解密的时间越长;加密与解密用的是同样的密钥;缺点:密钥的管理和分配
  2. 非对称加密:最常用的非对称加密算法是RSA算法;通过一堆密钥:公钥和私钥,私钥由一方安全保管,公钥可以发送给任何请求他的人,只有私钥才能解密,而且不需要将私钥通过网络发送出去,大大提高网络安全性

在浏览器地址栏输入URL之后回车的过程

  • URL解析,抽出域名字段

  • DNS解析,域名解析成IP

    • 浏览器缓存->操作系统缓存->路由器缓存->ISP缓存->根域名服务器->主域名服务器
  • TCP/IP连接,三次握手(Three-way Handshake,是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包)(ISN起始序列号,seq序列号,ack确认号)

    • 建立连接时,随机生成ISN为X,客户端发送(SYN=1,seq=X)(标志位为1,序号为X)的包到服务器,并进入SYN_SEND状态

    • 服务端收到报文,回复报文 SYN 标志位和 ACK 标志位均为1,并且随机生成ISN=Y(seq=Y),确认号ack=X+1,并进入SYN_RCVD状态

    • 客户端收到服务端的回复,发现ack=X+1,ACK的标志位为1,确认服务端收到了seq=X的报文,同时发现SYN=1,知道了服务端同意了这次连接;回复ACK=1,ack=Y+1,seq=X+1报文给服务器,当服务器接收到ACK=1,ack=Y+1,就知道客户端接收到seq=Y的报文了,连接建立

    • 第三次握手可以传数据

  • 发送HTTP请求

  • 服务器处理请求并返回 HTTP 报文

  • 浏览器解析渲染页面

  • 断开连接:TCP 四次挥手

    • 客户端向服务端发出连接释放报文,FIN=1,seq=X+1;需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;一个FIN将占用一个序号,FIN_WAIT1

    • 服务器收到这个FIN,它发回确认报文 包含ACK标志为1,ack=X+2,seq=Y+回复字节数据;此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完,客户端进入FIN_WAIT2,服务端进入CLOSE_WAIT

    • 服务器端将最后的数据发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1),seq=Y+总传输数据,服务端进入LAST_ACK

    • 客户端收到服务端发的FIN报文后,向服务端发出确认报文,包含ACK=1,ack=Y+总数据+1,seq=X+2;注意客户端发出确认报文后不是立马释放TCP连接,客户端进入TIME_WAIT,经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接,进入CLOSED。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。

ajax和fetch区别

  1. ajax本质是利用XMLHttpRequest来实现,fetch采用了Promise的异步处理机制

cdn(Content Delivery Network)

  • 内容分发网络:CDN的工作原理就是将源站的资源缓存到位于全球各地的CDN节点上,用户请求资源时,就近返回节点上缓存的资源,而不需要每个用户的请求都回您的源站获取,避免网络拥塞、缓解源站压力,保证用户访问资源的速度和体验

  • dns解析,缓存,负载均衡,

  • 通过dns解析到全局负载均衡服务器,然后再到区域的负载均衡,之后根据一些条件来找合适的缓存服务器,如果第一次访问就从源站拿过来缓存。需要注意的是一切都是根据请求的ip来的,如果ip不合理,那么可能起不到加速效果。缓存和负载均衡的思想在减轻服务器压力方面其实是很常见的

浏览器缓存策略

浏览器的缓存机制是根据http报文的缓存标识来进行的

1.强缓存:浏览器验证缓存的有效性,不会发送请求到服务器

  • Response Headers 下的 expires:服务器可以通过此设置具体过期时间(格林威治时间),在此时间内都可以读取缓存,这是http1.0的产物,缺点是无法保证客户端的系统时间,如果被篡改则毫无意义

  • Response Headers 下的cache-control:no-cache,no-store 不缓存,发送请求,max-age 单位为秒 设置过期时间,http1.1的产物,可以解决expires的缺点,时间都取自服务器。如果跟expires同时设置,则应用该规则

2.协商缓存:发送请求到服务器,由服务器决定是否从缓存中读取

  • Response Headers 下的last-modified:资源最后修改时间,与下一次请求头(request Headers)的IF-modified-since来比较修改时间,如果返回200(大于),表明服务器的资源已经被修改,则响应新的资源;如果返回304(小于等于),则表明可以继续使用缓存

  • Response Headers 下的 ETag:资源的唯一标识符,与下一次的请求头request Headers)的IF-None-Match来比较标识是否相同,如果返回200(不相同),则响应新资源;如果返回304(相同),使用缓存

react生命周期变更,以及使用方式;为什么变化,为什么取消了那几个生命周期

v16.3去掉componentWillMount,componentWillReceiveProps,componentWillUpdate;新增static getDerivedStateFromProps(props, state),getSnapshotBeforeUpdate(prevProps, prevState)

  • 生命周期:set props & state --> componentWillMount(static getDerivedStateFromProps) --> render -->(getSnapshotBeforeUpdate)--> componentDidMount

  • propsUpdate:componentWillReceiveProps(static getDerivedStateFromProps) --> shouldComponentUpdate --> componentWillUpdate(去掉) --> render --> (getSnapshotBeforeUpdate)--> componentDidUpdate(prevProps, prevState, snapshot)

  • stateUpdate:shouldComponentUpdate --> componentWillUpdate(getSnapshotBeforeUpdate) --> render --> componentDidUpdate(prevProps, prevState, snapshot)

  • 销毁:componentWillUnmount

  • 强制重新渲染组件:forceUpdate()

  • 错误处理: 当渲染过程,生命周期,或子组件的构造函数中抛出错误时,componentDidCatch,static getDerivedStateFromError()

  1. componentWillMount是在组件挂载之前调用,在此方法中调用setstate是不会触发render的,如果是初始化数据尽量写在constructor中,如果是请求数据,建议放在componentDidMount中

  2. componentWillReceiveProps:1.如果需要执行副作用(数据提取或动画),则建议在componentDidUpdate;2.如果是为了props更改时重新计算,请使用 memoization helper(memoize) 代替;3.如果是为了props更改时更改state,请考虑组件完全受控或使用key使组件完全不受控

  3. componentWillUpdate:因为props或者state更新而引起多次调用,而componentDidUpdate就只会被调用一次;不能在此方法中调用setstate

  4. getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

  5. getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等

setstate的同步异步说明

  • 调用setstate时,react.js不会马上修改state,而是先把这个对象放到一个更新队列中,合并之后然后再触发state和组件更新(批处理机制),在componentDidMount 和componentDidUpdate中统一更新

  • isBatchingUpdates,判断是直接更新还是先暂存放入dirtyComponent队列,setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的,

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新

虚拟DOM

  • 定义: 在react中会将代码先转化成一个js对象,然后这个JS对象再转化成真实DOM,这个JS对象就是虚拟DOM。当setstate时,通过diff虚拟DOM得到change tree,然后patch到DOM上

  • 优点:

    1. 提高开发效率:更着重于业务逻辑而不是DOM操作,让state来管理视图

    2. 提高开发效率:如果是首次渲染,VitrualDom不具有任何优势,甚至它要进行更多的计算,消耗更多的内存;VitrualDom的优势在于React的Diff算法和批处理策略。

    3. 跨平台兼容,react根据VitrualDom画出相应平台的ui层。

    4. 跨浏览器兼容:React基于VitrualDom自己实现了一套自己的事件机制,自己模拟了事件冒泡和捕获的过程,采用了事件代理,批量更新等方法,抹平了各个浏览器的事件兼容性问题

reactDOM.render()

  • 首次调用会全部替换容器内部的子节点,后续调用则通过diff算法来进行更新

  • 不会替换容器节点,可以在不覆盖现有子节点的情况下,将组件插入已有的DOM节点中

  • ReactDOM.render() 目前会返回对根组件 ReactComponent 实例的引用。 但是,目前应该避免使用返回的引用,因为它是历史遗留下来的内容,而且在未来版本的 React 中,组件渲染在某些情况下可能会是异步的。 如果你真的需要获得对根组件 ReactComponent 实例的引用,那么推荐为根元素添加 callback ref

  • 使用 ReactDOM.render() 对服务端渲染容器进行 hydrate 操作的方式已经被废弃,并且会在 React 17 被移除。作为替代,请使用 hydrate()。

  1. 首先babel编译为React.createElement,返回legacyRenderSubtreeIntoContainer(父组件,子元素,根节点,forceHydrate协调更新(是否在服务端渲染),callback)

  2. 首次进来判断root,如果没有则创建根节点,创建legacyCreateRootFromDOMContainer并返回一个ReactSyncRoot实例,执行unbatchedUpdates(不需要批量更新,把执行上下文切换成非批量上下文)

    • ReactSyncRoot中createContainer创建fiberRoot(FiberNode)和rootFiber并相互引用
    • 创建完rootFiber之后,会将fiberRoot实例的current属性指向刚创建的rootFiber
    • 同时rootFiber的stateNode属性会指向fiberRoot实例,形成相互引用
  3. 正常更新时,执行updateContainer更新

react事件机制,以及优点

  • 驼峰命名,而不是全部小写

  • react自己实现了一套事件机制,模拟了事件冒泡和捕获的过程,采用了事件代理,批量更新等方法;将所有的事件注册到document上

  • React中的事件是合成事件,并不是原生的DOM事件,解决了跨平台以及兼容性问题

优点: 只需要注册一个事件即可,节省内存开销;可以统一在组件挂载和卸载时做处理;可以手动控制事件流程,特别是对state的批处理;执行顺序:原生事件先执行,然后执行合成事件,最后执行真正在document上挂载的事件

react事件最好不要和原生事件混用,原生事件中如果执行了stopPropagation方法,则会导致其他react事件失效。因为所有元素的事件将无法冒泡到document上

diff算法,用key的原因

  1. React diff 与 传统diff 的不同是 React通过优化将复杂度O(n^3)提升至O(n)

  2. React 通过三个方面对tree diff, component diff, element diff 进行了优化

  3. 在开发时,尽量保持稳定的DOM结构,并且减少将最后的节点移动到首部的操作,能够优化渲染性能

  • 单节点diff

    key和type相同,可以复用

    key不同,直接删除

    key相同,type不同,标记删除该节点和兄弟节点,重新创建节点

  • 多节点diff

    多节点diff有3个遍历,第一个遍历处理节点的更新(props,type(标签)更新和删除),第二个处理节点的新增,第三个处理节点位置改变

fiber是什么,优点是什么

  • 目的:不希望JS长时间的执行,造成其他比如布局,动画等情况一起运行,造成阻塞,导致掉帧

  • Fiber本质是链表,增量渲染(把渲染任务拆分成块,匀到多帧)

  • 更新时能够暂停,终止,复用渲染任务

  • 给不同类型的更新赋予优先级

从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情;构建workInProgress tree的过程就是diff的过程,通过requestIdleCallback来调度执行一组任务,每完成一个任务后回来看看有没有插队的(更紧急的),每完成一组任务,把时间控制权交还给主线程,直到下一次requestIdleCallback回调再继续构建workInProgress tree

  • reconcile过程分为2个阶段(phase):

    1. 可中断)render/reconciliation 通过构造workInProgress tree得出change

    2. 不可中断)commit 应用这些DOM change

旧版 React 通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。而Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback这一 API

ref相关的

目的:用于访问原生dom节点;用法:React.createRef()

  • 管理焦点,文本选择或媒体播放

  • 触发强制动画

  • 集成第三方 DOM 库

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}
// 访问
const node = this.myRef.current;

memo,PureComponent

  • PureComponent实现了shouldcomponentUpdate中的浅比较,所以在props和state较为简单的时候使用能提高性能;如果state和props一直变化的话,还不如使用Component,因为浅比较也是需要花费时间的;适用于展示纯组件;若有shouldComponentUpdate,则执行,否则进行浅比较。

  • memo为高阶组件,只适用于函数组件,仅检查 props 变更,也是做浅比较;此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。如果想要控制比较过程,则将自定义比较函数传入第二个参数

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);

react hooks各个hook的作用

是react 16.8的新特性,Hook 是一个特殊的函数,你可以不用写class就使用state以及react的其他特性;只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用;只能在 React 的函数组件中调用 Hook。Hook 使用了 JavaScript 的闭包机制;Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期

  • useEffect:有点类似生命周期,给函数组件执行副作用(数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用)。可以看成是 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。会在每次渲染后都执行,如果return 一个函数,则可以执行清除操作;如果传入第二个参数,则可以在指定值变化时再执行effect;该 Hook 接收一个包含命令式、且可能有副作用代码的函数。

  • useState:没有this,参数不一定为对象,跟setState功能相同,返回值为当前state和更新他的函数

  • useContext:不使用组件嵌套就可以订阅 React 的 Context;

  • useReducer:通过 reducer 来管理组件本地的复杂 state

    useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

    在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

  • useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

  • useRef:useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”

那么 React 怎么知道哪个 state 对应哪个 useState?答案是 React 靠的是 Hook 调用的顺序。

redux原理,redux能在react中使用根据什么技术点,redux中异步处理方式,mobx区别

目的:解决组件之间的通信。如多级传递数据,中间组件会被迫接收和传递他们并不需要的props;解决兄弟组件传递数据需要先向上传递给父级,再往下传递的问题;rudex可以提供一个全局的store存放需要共享的数据;Redux DevTools 让你检查每一个 state 的变化

  • 使用场景:

    • 简单的,可以用内部state,或者hook
    • 有全局的东西需要共享,可以用context API
    • 有全局的东西,与应用的各独立部分都有交互,并且随着时间的推移,这个应用会越来越大,建议使用redux
  1. View,dispatch发出Action -> Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State -> State 一旦有变化,Store 就会调用监听函数

  2. 中间件(middleware):Action 发出以后,过一段时间再执行 Reducer,这就是异步,需要用到中间件。在发出 Action 和执行 Reducer 这两步之间,添加了其他功能,其实就是对dispatch进行重构

    • 使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数

    • 另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。这就需要使用redux-promise中间件。

  3. connect: connect(mapStateToProps,mapDispatchToProps)(TodoList)

    • mapStateToProps:负责输入逻辑,即将state映射到 UI 组件的参数(props)。是一个函数,会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

    • mapDispatchToProps:负责输出逻辑,建立 UI 组件的参数到store.dispatch方法的映射。

  4. Provider 组件:Provider的唯一功能就是传入store对象。connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数,一种解决方法是将state对象作为参数,传入容器组件,React-Redux 提供Provider组件,可以让容器组件拿到state。它的原理是React组件的context属性。

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

react-router 原理

引入了history库

  1. 使用BrowserRouter(源码在 react-router-dom 中,它是一个高阶组件),用H5的history库创建了一个专门的history,return Router,传递了history和children

  2. Router,设置了一个监听函数,使用的是history库的listen,setState({location}),向下传递history,location,match,staticContext

  3. 使用Route匹配的path,并渲染匹配的组件:Route 接受新的 nextContext 通过 matchPath 函数来判断 path 是否与 location 匹配,如果匹配则渲染,不匹配则不渲染

  4. 使用Link创建一个链接跳转到你想要渲染的组件:当点击页面的Link是,其实是点击的a标签,只不过使用了 preventDefault 阻止 a 标签的页面跳转;通过给a标签添加点击事件去执行 hitsory.push或者history.replace

HOC高阶组件

高阶组件是参数为组件,返回值为新组件的函数

1.withRouter react-router

2.connect redux

react优化方式

componentDidMount 里不建议写 setState

componentWillReceiveProps-》getDerivedStateFormProps 派生state会使状态冗余

shouldComponentUpdate-》PureComponent/memo() 作浅比较

useCallBack(),useMemo()

尽量少出现跨层级的操作