HTML
HTML 语义化
- 没有
CSS样式下,页面也能呈现出很好地内容结构、代码结构 - 有利于
SEO,让搜索引擎更容易读懂,有助于爬虫抓取更多的有效信息 - 具有更好的可读性,有利于团队开发和维护
HTML5 新特性
- 新增了语义化标签,如
header、nav、article、aside、footer等等 - 新增了增强型表单
- 新增了音频视频标签
- 新增了
canvas和svg绘图 - 新增了地理定位
- 新增了拖拽API
- 新增了本地缓存
Web Storage - 新增了多线程技术
Web Worker - 新增了即时通讯
WebSocket
CSS
CSS3 新特性
圆角、阴影、背景与渐变、过渡、变形、动画、媒体查询、多栏布局、选择器 等等。
CSS 选择器
CSS 选择器优先级顺序
内联 > ID选择器 > 类选择器 > 标签选择器
CSS 选择器优先级计算规则
- 如果存在
内联样式,那么 A = 1,否则 A = 0 - B 的值等于
ID选择器出现的次数 - C 的值等于
类选择器和伪类选择器和属性选择器出现的总次数 - D 的值等于
标签选择器和伪元素选择器出现的总次数
总结: CSS 选择器优先级是由 A、B、C、D 的值来决定的,从左往右依次进行比较,较大者胜出,如果相等,则继续往右移动一位进行比较,如果四位全相等,后面的覆盖前面的
CSS 盒模型
CSS 盒模型有哪些?
CSS 盒模型有 标准盒模型 和 IE盒模型 两种盒模型。
两种盒模型的区别?
两种盒子模型都是由 content + padding + border + margin 构成,其大小都是由 content + padding + border 决定,盒子内容宽/高度(width/height)的计算范围根据盒模型的不同会有所不同,标准盒模型只包含 content,IE盒模型包含 content + padding + border。
如何设置盒模型?
标准盒模型 box-sizing: content-box
IE盒模型 box-sizing: border-box
清除浮动的几种方案?
- 浮动元素后增加一个空元素且设置
clear: both属性 - 父元素设置
owerflow:auto|hidden属性,IE6中还需要为父元素设置宽高或zoom: 1 - 伪元素设置
display: block; clear: both属性
元素水平垂直居中的几种方案?
1. flex布局
display: flex;
align-items: center;
justify-content: center;
2. 绝对定位和 `transform` 配合
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
3. 绝对定位和 `margin: auto` 配合
// 父元素设置 position: relative
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
4. 文字水平垂直居中
line-height: 500px; // 父元素高度
text-align: center;
Transition 与 Animation 的异同?
Transition 与 Animation 的功能相同,都是通过改变元素属性的值来实现动画效果,二者区别在于 Transition 只能指定属性的开始值与结束值,然后在这个属性值之间使用平滑过渡的方式实现动画效果,不能实现比较复杂的动画效果,Animation 通过定义多个关键帧,以及定义每个关键帧中元素的属性值来实现更为复杂的动画效果。
line-height 如何继承?
- 父元素的
line-height设置了具体数值,如30px,则子元素继承该值 - 父元素的
line-height设置了比例,如1.5或2,则子元素继承该值 - 父元素的
line-height设置了百分比,如200%,则子元素继承的是父元素font-size * 200%计算出来的值
CSS 隐藏元素的方案?
display: none被隐藏的元素不会在页面中占据位置,也不会响应绑定的监听事件visibility: hidden被隐藏的元素使用的空间保持不变opacity: 0或transform: scale(0)或transform: translate(-9999px, 0px)或position: absolute; left: -999px等等通过改变元素透明度或者平移、缩小等方式
JavaScript
JavaScript 有哪些数据类型?
JavaScript 共有八种数据类型,七种基本数据类型包含 String、Number、Boolean、Null、Undefined、Symbol、BigInt,一种引用数据类型 Object,包含 Array、Date、Function 等等。
JavaScript 数据类型有哪些判断方法?
1. typeof
typeof '1' === 'string' => true
typeof 1 === 'number' => true
typeof true === 'boolean' => true
typeof null === 'null' => false // object
typeof undefined === 'undefined' => true
typeof {} === 'object' => true
typeof [] === 'array' => false // object
typeof function() {} === 'function' => true
总结: 可以用来判断除 Null 以外的基本类型。
2. instanceof
'1' instanceof String => false
1 instanceof Number => false
true instanceof Boolean => false
{} instanceof Object => true
[] instanceof Array => true
function () {} instanceof Function => true
总结: 可以用来判断引用数据类型。
3. constructor
'1'.constructor === String => true
(1).constructor === Number => true
true.constructor === Boolean => true
{}.constructor === Object => true
[].constructor === Array => true
function () {}.constructor === Function => true
总结: 可以用来判断所有数据类型,有缺陷。
4. Object.prototype.toString.call
Object.prototype.toString.call('1') === '[object String]' => true
Object.prototype.toString.call(1) === '[object Number]' => true
Object.prototype.toString.call(true) === '[object Boolean]' => true
Object.prototype.toString.call(null) === '[object Null]' => true
Object.prototype.toString.call(undefined) === '[object Undefined]' => true
Object.prototype.toString.call({}) === '[object Object]' => true
Object.prototype.toString.call([]) === '[object Array]' => true
Object.prototype.toString.call(function () {}) === '[object Function]' => true
总结: 可以用来判断所有数据类型,最完美的解决方案。
undefined 与 null 有什么区别?
Undefined与Null都是基本数据类型,他们的值分别为undefined和nullundefined代表的是变量未定义,null代表的是一个空对象,常用来作为变量的初始值- 通过
typeof判断类型时,undefined返回undefined,null返回object undefined在JavaScript中不是一个保留字,可以使用undefined来作为一个变量名
var、let、const 的区别
块级作用域var没有块级作用域,let及const具有块级作用域变量提升var存在变量提升,let及 const` 不存在变量提升重复声明var 可以重复声明变量,let及 const 不可以重复声明变量暂时性死区var 声明的变量可以在声明之前使用,let及 const 声明的变量在声明之前不可用指针指向var及let声明的变量可以改变指针指向,const声明的变量不可以改变指针指向
ES6 的新特性有哪些?
let和const声明变量- 字符串模版
- 箭头函数
- 解构赋值
- 扩展运算符
闭包
简介
闭包是指有权访问另一个函数作用域中变量的函数。
用途
- 从外部读取函数内部的变量
- 让变量的值始终保持在内存中
原型及原型链
原型
在 JavaScript 中,每一个对象从被创建的时候就和另一个对象关联,从另一个对象上继承其属性,这个另一个对象就是原型。
原型链
当我们访问一个对象的属性时,先在对象的本身查找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,这个查找属性的链路就叫原型链。
事件模型
定义
事件模型描述的是从页面中接收事件的顺序,包括下面几个阶段,分别为:事件捕获阶段 处于目标阶段 事件冒泡阶段。
事件委托
事件委托指的是,不在事件的发生地上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,从而来做出不同的响应。
如何判断一个数组?
1. Object.prototype.toString.call([]) === '[object Array]'
2. [] instanceof Array === true
3. [].constructor === Array
3. Array.isArrray([]) === true
类数组
定义
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象。
类数组转数组的方法有哪些?
1. Array.prototype.slice.call(arguments)
2. Array.form(arguments)
3. [...arguments]
JavaScript 继承有哪几种?
1. 原型链继承
原理:将父类的实例作为子类的原型
缺点:父类的引用属性会被所有子类共享,更改一个子类的引用属性,其他子类也会受影响
function Parent() {
this.info = { name: 'yhd', age: 18 };
};
Parent.prototype.getInfo = function() {
console.log(this.info);
};
function Child() {};
Child.prototype = new Parent();
let child1 = new Child();
child1.info.gender = '男';
child1.getInfo(); // { name: 'yhd', age: 18, gender: '男' }
let child2 = new Child();
child2.getInfo(); // { name: 'yhd', age: 18, gender: '男' }
2. 构造函数继承
原理:使用父类的构造函数来增强子类实例,等同于复制父类的实例属性和方法给子类
缺点:只能继承父类的实例属性和方法,不能继承父类的原型属性和方法
function Parent() {
this.info = { name: 'yhd', age: 18 };
this.getInfo = function() {
console.log(this.info);
};
};
function Child() {
Parent.call(this);
}
let child1 = new Child();
child1.info.gender = '男';
child1.getInfo(); // { name: 'yhd', age: 18, gender: '男' }
let child2 = new Child();
child2.getInfo(); // { name: 'yhd', age: 18 }
3. 原型链与构造函数组合继承
原理:1. 通过父类的构造函数实现对父类实例属性和方法的继承
2. 通过原型链实现对父类原型属性和方法的继承(会将父类的实例属性和方法添加到子类的原型上)
function Parent(name, age) {
this.name = name;
this.age = age;
this.info = { name: 'yhd', age: 18 };
};
Parent.prototype.getAge = function() {
console.log(this.age);
};
Parent.prototype.getName = function() {
console.log(this.name);
};
Parent.prototype.getInfo = function() {
console.log(this.info);
};
function Child(name, age) {
Parent.call(this, name, age);
};
Child.prototype = new Parent();
let child1 = new Child('小红', 18);
child1.info.gender = '男';
child1.getName(); // 小红
child1.getAge(); // 18
child1.getInfo(); { name: 'yhd', age: 18, gender: '男' }
let child2 = new Child('小明', 20);
child2.getName(); // 小明
child2.getAge(); // 20
child2.getInfo(); { name: 'yhd', age: 18 }
4. 原型式继承
原理:将某个对象直接赋值给构造函数的原型
缺点:父类的引用属性会被所有子类共享,更改一个子类的引用属性,其他子类也会受影响
function copyObject(object) {
function Fun() {};
Fun.prototype = object;
return new Fun();
};
let person = {
name: '小明',
colors: ['red', 'blue', 'yellow'],
getName: function() {
console.log(this.name)
},
getColors: function() {
console.log(this.colors)
}
};
let child1 = copyObject(person);
child1.name = '小红';
child1.colors.push('green');
child1.getName(); // 小红
child1.getColors(); // ['red', 'blue', 'yellow', 'green']
let child2 = copyObject(person);
child2.name = '小王';
child2.colors.push('black');
child2.getName(); // 小王
child2.getColors(); // ['red', 'blue', 'yellow', 'green', 'black']
5. 寄生式继承(在原型式继承的基础上,增强对象的实例属性和方法)
原理:将某个对象直接赋值给构造函数的原型
缺点:父类的引用属性会被所有子类共享,更改一个子类的引用属性,其他子类也会受影响
function copyObject(object) {
function Fun() {};
Fun.prototype = object;
return new Fun();
};
function createAnother(original) {
let clone = copyObject(original);
clone.getName = function() {
console.log(this.name);
}
clone.getColors = function() {
console.log(this.colors);
}
};
let person = {
name: '小明',
colors: ['red', 'blue', 'yellow']
};
let child1 = createAnother(person);
child1.name = '小红';
child1.colors.push('green');
child1.getName(); // 小红
child1.getColors(); // ['red', 'blue', 'yellow', 'green']
let child2 = createAnother(person);
child2.name = '小王';
child2.colors.push('black');
child2.getName(); // 小王
child2.getColors(); // ['red', 'blue', 'yellow', 'green', 'black']
6. 寄生式组合继承
原理:1. 通过父类的构造函数实现对父类实例属性和方法的继承
2. 通过原型链实现对父类原型属性和方法的继承
function copyObject(object) {
function Fun() {};
Fun.prototype = object;
return new Fun();
};
function inheritPrototype(child, parent) {
let prototype = copyObject(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.getName = function() {
console.log(this.name);
}
Parent.prototype.getColors = function() {
console.log(this.colors);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.getAge = function() {
console.log(this.age);
};
let child1 = new Child('小明', 18);
child1.getName(); // '小明'
child1.getAge(); // 18
child.colors.push('green');
child1.getColors(); // ['red', 'blue', 'yellow', 'green']
let child2 = new Child('小红', 20);
child2.getName(); // '小红'
child2.getAge(); // 20
child2.getColors(); // ['red', 'blue', 'yellow']
Promise
常用手写
节流
let timer = null;
if (timer) return;
timer = setTimeout(() => {
clearTimeout(timer);
console.log('Hello World!');
}, 200);
防抖
let timer = null;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
clearTimeout(timer);
console.log('Hello World!');
}, 200);
深拷贝
function deepCopy(obj) {
let _obj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
for (let i in obj) {
_obj[i] = typeof value === "object" && value !== null ? deepCopy(obj[i]) : obj[i];
};
return _obj;
};
冒泡排序
function bubbleSort(array) {
let t = 0;
for(let i = 0; i < array.length - 1; i++) {
for (let j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j+1]) {
t = array[j];
array[j] = array[j+1];
array[j+1] = t;
t = 0;
}
}
}
};
选择排序
function selectSort(array) {
let t = 0;
let index = 0;
for(let i = 0; i < array.length; i++) {
index = i;
for(let j = i; j < array.length; j++) {
if (array[j] < array[index]) {
index = j;
}
}
t = array[index]
array[index] = array[i]
array[i] = t
}
};
自定义 call 函数
function myCall(target, ...args) {
target = target || window;
const symbolKey = Symbol();
target[symbolKey] = this;
const res = target[symbolKey](...args);
delete target[symbolKey];
return res;
};
自定义 apply 函数
function myApply(target, args) {
target = target || window;
const symbolKey = Symbol();
target[symbolKey] = this;
const res = target[symbolKey](...args);
delete target[symbolKey];
return res;
};
自定义 bind 函数
function myBind(target, ...outArgs) {
target = target || {};
const symbolKey = Symbol();
target[symbolKey] = this;
return function(...innerArgs) {
const res = target[symbolKey](...outArgs, ...innerArgs);
return res;
}
}
webpack
简介
webpack 是一种用于构建 JavaScript 应用程序的静态模块打包器,它能够以一种相对一致且开放的处理方式,加载应用中的所有资源文件,并将其合并打包成浏览器兼容的资源文件。
核心概念
entry一个可执行模块或者库的入口chunk多个文件组成一个代码块loader文件转换器plugin扩展功能的插件output编译结果文件输出
构建流程
初始化参数结合配置文件和Shell的参数,计算出最终的配置参数开始编译通过初始化参数初始化compiler对象,注册所有配置的插件,执行compiler对象的run方法开始执行编译确定入口根据entry配置找出入口文件编译模块从配置的entry入口开始,根据配置的loader对模块进行转译,如果该模块还有依赖的模块,再递归本步骤直到所有依赖的文件都进行了转译完成模块编译经过loader编译完所有模块后,得到每个模块编译后的内容及每个模块之间的依赖关系输出资源根据入口文件和每个模块之间的依赖关系,生成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独文件加入到输出列表中这步是可以修改输出内容的最后机会输出完成根据输出项配置,将文件内容写到文件系统
Loader
定义
Loader 本质上是一个函数,负责代码的转译,即对接收的内容进行转译后并将结果返回,在 Module.rules 中以数组的形式进行配置
常见的 loader 及其作用
raw-loader加载文件的原始内容babel-loader将 ES6 转为 ES5file-loader可以指定要复制和放置资源文件的位置,并在代码中通过URL去引用输出的文件url-loader和file-loader功能相似,但可以通过设置阈值根据文件的大小使用不同的处理方式,大于阈值的交给file-loader处理,小于阈值的返回base64格式编码并将文件的data-url内联到bundle中img-loader加载并压缩图片资源style-loader把CSS代码注入到JavaScript中,通过DOM操作去加载CSS代码css-loader加载CSS代码,支持模块化、压缩、文件导入等功能sass-loader将SASS/SCSS代码转换成CSSpostcss-loader扩展CSS语法,可以自动补齐CSS3前缀eslint-loader通过ESLint检查JavaScript代码tslint-loader通过TSLint检查JavaScript代码cache-loader添加到一些开销较大的loader之前,将结果缓存到磁盘中source-map-loader加载额外的Source Map文件
PLugin
定义
PLugin 本质上是一个函数,基于事件流框架 Tapable,通过监听 webpack 运行过程中广播的事件,在合适的时机通过 webpack 提供的 API 来改变输出结果,在 plugins 中以数组的形式进行配置
常见的 plugin 及其作用
web-webpack-plugin为单页面应用输出HTMLcss-minimize-webpack-plugin压缩CSS代码uglifyjs-webpack-plugin压缩JavaScript代码terser-webpack-plugin压缩ES6代码mini-css-extract-plugin将CSS提取为独立文件,支持按需加载clean-webpack-plugin清理目录ignore-plugin忽略部分文件
Babel 原理
解析进行代码分析,将代码分割成token流,再根据token流生成AST转换遍历AST节点并生成新的AST节点生成根据新的AST生成目标代码
文件监听
文件监听原理
webpack 轮询判断文件文件的最后修改时间,当发现文件修改时间发生变化后,会先缓存起来等到 aggregateTimeout 再统一执行,重新构建出新的输出文件
开启文件监听方式
- 启动
webpack命令时,带上--watch参数 - 在配置文件
webpack.config.js中设置watch: true
热更新
定义
webpack 热更新又称热替换,全称 Hot Module Replacement,缩写 HRM,此机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
原理
- 使用
webpack-dev-server (WDS)托管静态资源 - 浏览器加载页面后与
WDS建立WebSocket连接 webpack监听到文件变化后,向浏览器推送更新并携带新的hash- 浏览器接收到
hash事件与之前的hash进行对比,加载变更的增量模块 webpack触发变更模块的module.hot.accept回调,执行代码变更逻辑
Tree Shaking
定义
Tree Shaking 是一种基于 ES Module 规范的 Dead Code Elimination 技术,它会在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾被其他模块引用,将其删除,以此实现打包的优化。
原理
- 收集模块导出变量并记录到模块依赖关系图中,遍历模块依赖关系图并标记哪些导出未被使用
- 使用
Terser将未被使用的导出删除
优化 webpack 构建的方式有哪些?
- 使用高版本的
webpack和node - 使用
thread-loader进行多线程构建 - 压缩代码
- 缩小文件的打包范围
- 将静态资源存储到
CDN - 利用缓存提升二次构建速度
git 常用命令
git config --global user.name ""配置用户名git config --global user.email ""配置用户邮箱git branch查看本地分支git branch -f查看远程分支git branch -a查看本地和远程分支git branch -d <branch-name>删除本地分支git push origin -d <branch-name>删除远程分支git checkout <branch-name>切换分支git checkout -b <branch-name>创建并切换到新建分支git status查看文件状态git log查看提交日志git add .将全部文件上传到暂存区git commit -m""提交到本地仓库git push <主机名> <本地分支名>:<远程分支名>提交到远程仓库git fetch <主机名> <远程分支名>:<本地分支名>拉取远程分支代码到本地分支git pull <主机名> <远程分支名>:<本地分支名>拉取远程分支代码到本地分支并合并git merge <branch-name>合并分支到本地分支
浏览器
重排与重绘
什么是浏览器的重排与重绘?
重排是重新排列,重新计算元素的位置和几何信息,会影响部分或整个页面的布局。
触发重排与重绘的情景
- 页面首次渲染
- 页面内容改变
- 添加或者删除可见的DOM元素
- 元素位置、尺寸、样式改变
- 浏览器窗口尺寸改变
如何减少重排与重绘
- 尽量避免频繁操作DOM
- 尽量避免频繁操作样式
- 尽量避免频繁读取会引发回流/重绘的属性
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流
- 尽量避免使用table布局
- 尽量避免设置多层内联样式
- 尽量避免使用CSS表达式
- 尽量在DOM树的最末端改变class
总结:重排一定会引起重绘,重绘不一定引起重排
垃圾回收机制
简介
由于字符串、数组和对象没有固定大小,所以当他们的大小已知时,才能对他们进行动态的存储分配,JavaScript 每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要这样动态的分配了内存,最终都要释放这些内存以便他们能够被再次使用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃,JavaScript 通过垃圾回收机制来自动管理内存.
原理
现在大多数浏览器都采用 标记清除 算法来进行内存的管理:当变量进入环境时,将变量标记为 进入环境,当变量离开环境时,将变量标记为 离开环境,在某一时刻垃圾回收器会过滤环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。
chrome V8引擎的垃圾回收策略
- 将整个堆内存分为新生代内存和老龄代内存,所有的内存分配操作发生在新生代
- 新生代内存又分为
Form和To两部分空间,所有的内存分配操作发生在Form空间 - 新生代空间GC(复制算法)
- 将
From空间中存活的对象复制到To空间,释放未存活的对象 - 转换二者的角色,
Form变 为To空间,To变为From空间 - 如果某个对象已经经历过一次复制算法,就将该对象复制到老龄代空间
- 如果
To空间使用率超过25%,将整个空间的对象复制到老龄代空间,主要为了角色转换之后留足分配内存的空间
- 将
- 老龄代空间GC(标记清除与标记合并)
- 主要采用
标记清除算法,通过标记清除算法清理未存活的对象 - 清理完成之后会是内存空间出现不连续的状态,这种内存碎片会对后续的内存分配造成问题,因此在内存空间不足的时候采用
标记合并算法,将活着的对象移动到内存的一端,再清除另一端的对象
- 主要采用
- 新生代GC触发的要比老龄代GC频繁
同源策略
同源策略指的是一个域下的 js脚本 在未经允许的情况下,不能够访问另一个域的内容,同源的指的是两个域的协议、域名、端口号必须相同,否则则不属于同一个域,同源政策的目的主要是为了保证用户的信息安全,它只是对 js脚本 的一种限制,并不是对浏览器的限制,对于一般的 img 或 script脚本 请求都不会有限制。
引起内存泄漏有哪些原因?
- 使用未声明的变量,会创建了一个全局变量,而使这个变量一直留在内存中无法被回收
- 设置了
setTimeout或setInterval未清除时会被一直留在内存中无法被回收 - 不合理的使用闭包,会导致某些变量一直被留在内存中无法被回收
localStorage、sessionStorage、Cookie之间有什么区别?
生命周期localStorage永久有效,sessionStorage仅在当前会话下有效,关闭浏览器后则失效,Cookie可以设置过期时间数据大小localStorage及sessionStorage可以存储5MB数据,Cookie可以存储4KB数据网络请求localStorage及sessionStorage仅在客户端中保存,Cookie则会被携带在HTTP请求头中传输到服务器端
浏览器缓存
HTTP 与 HTTPS
三次握手与四次挥手
地址栏输入一个URL后发生了什么?
React
React 简介
React 是一个用于构建用户界面的 JavaScript 库,提供 UI 层面的解决方案,React 拥有组件化、JSX语法、虚拟 DOM、单向数据绑定、数据驱动视图等特性。
React JSX 语法
JSX 语法是 JavaScript 语法的扩展,用于描述 UI 界面,并拥有 JavaScript 的全部功能,可以和 JavaScript 融合在一起使用,JSX 的顶层只能有一个根元素,为了方便阅读,我们通常在 JSX 的外层包裹一个小括号。
React 虚拟 DOM
简介
虚拟 DOM 是一个轻量级的 JavaScript 对象,是真实 DOM 结构的映射。
工作原理
挂载阶段 React 结合 JSX 的描述,构建出虚拟 DOM 树,然后通过 ReactDOM.render 将虚拟 DOM 映射到真实 DOM。
更新阶段 数据变化时会再次构建新的虚拟 DOM 树,借助算法对比出哪些虚拟 DOM 需要被改变,然后再将这些改变作用于真实 DOM。
优点
虚拟 DOM 能够有效避免真实 DOM 频繁更新,减少重排与重绘,能够很大程度上提高性能。
React Diff 算法
React Diff 策略
- Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
- 对于同一层级的一组子节点,它们可以通过唯一
key进行区分
React Diff 算法
React对虚拟 DOM 树进行分层比较、层级控制,只对同一父节点的子节点进行比较,当发现某一子节点不在了直接删除该节点以及其所有子节点React对于同一类型的组件,通过shouldComponentUpdate方法优化,减少组件不必要的比较,对于非同一类型的组件,则替换整个组件下的所有子节点React通过唯一Key来判断老集合中是否存在相同的节点,如果有,则判断是否可复用,如果没有,则创建新节点
React 组件通信
父子组件通信
父传子 父组件调用子组件时,将需要传递的信息存放到自定义属性中,子组件通过 props 来获取相应的数据。
子传父 父组件将一个函数传递给子组件,子组件调用该回调函数,便可以向父组件通信。
跨级组件通信
- 通过中间组件层层传递
props - 通过
context - 通过
redux
兄弟组件通信
- 通过事件订阅
- 通过
redux
React Hook
简介
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 使用遵循哪些规则?
- 只允许在最顶层使用
Hook - 只允许在
React函数中调用Hook - 只允许在自定义
Hook中调用其他Hook
Hook 解决了 class 类组件哪些痛点?
- 组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的
class
Redux
简介
Redux 是一个 JavaScript 状态容器,提供可预测化的状态管理。
核心
store 用来连接 action 和 reducer。
action 用来传递 state 到 store,是 store 的唯一来源。
reducer 用来处理 action,通过传入新的 state 和 action 来指明如何更新 state,并返回新的 state。
三大原则
单一数据源 整个应用的 state 被储存在唯一一个 store 中。
state 是只读的 改变 state 的唯一方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
使用纯函数修改 为了描述 action 如何改变 state,你需要编写 reducer,reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
工作原理
Redux会将整个应用状态存储到到一个地方,称为store,这个store里面保存一棵状态树- 组件改变
state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,完成state更新 - 各个组件通过订阅
store中的状态来更新自己的视图
react-router
react-router-dom、react-router、history 三者之间有什么关系?
history是react-router的核心,也是整个路由原理的核心,集成了popState、history.popState、history.replaceState等方法react-router是react-router的核心,封装了Router、Switch、Route等核心组件,实现了从路由切换到组件更新的核心功能
react-router 原理
- 调用
history.push()方法改变路由,触发history.popState()方法,然后触发history的setstate方法,生成新的location对象,通知Router组件更新 Router组件接收到新的location后将其传递给Switch组件Switch组件接收到更新流后,根据Route组件配置的path属性,匹配出对应的Route组件并将其渲染
React 性能优化
- 使用
shouldComponentUpdate来避免不必要的 dom 操作 - 使用
useMemo和useCallback来缓存计算结果和函数 - 使用
key来辅助 Diff 算法进行虚拟 DOM 计算,避免不必要的渲染 - 使用
React.Fragment来避免不必要的div