文章目录
- 写在前面
- 1.原生
- 2.react
- 3.vue
- 4.ES6
- 5.其他
- 6.场景
- 7. 关于优化
- 8. 计算机网络
- 组件
- 组件1:微站搜索组件
- 组件2:单选框
- 组件3: 多选框
- 组件4: 时间轴 -自动播放
- 其他组件:echarts
写在前面
习题整理,实时更新
1.原生
1.1 盒模型
box-sizing
content-box:默认值。 设置的任何边框和内外边距会被增加到宽高基础之上
border-box:你想要设置的边框和内边距的值是包含在 width 内的
1.2 BFC
BFC块级格式上下文,一个独立显然区域,只有块级盒参与。
产生:
- float不为none
- overflow不为visible
- position为absolute,fixed
- display为inline-block
好处: - 解决margin折叠问题
- 清除浮动
- 解决文字环绕问题。
- 生成量两列布局。(两个box浮动)
1.3 清除浮动
问题:1.浮动元素脱离文档流,导致父元素塌陷(撑不开父元素的高度)2.影响之后元素布局
解决:
- 给父元素固定宽高(不够灵活)
- 在浮动元素后加一个空的块元素{clear:both}
- 给浮动元素的父元素添加属性
.clerafix:after{
content:"";
display:block;
clear:both
}
- 给父元素加overflow:hidden/scroll/auto
1.4 什么时候会造成内存泄漏
1.被遗忘的定时器
2.console.log
3.闭包
4.意外创建的全局变量
5.旧版本浏览器 循环引用
1.5 数组方法 map和forEach的区别
相同点
一个是在调用map/forEach时调用的回调函数,另一个是回调函数执行时使用的上下文
不同点
map 返回其原始数组的新数组,forEach却没有
1.6
1.7 防抖和节流的区别
防抖
指触发事件后在n秒内函数只执行一次,如果在n秒内又重新触发了该事件,那么将会重新计算函数执行的时间。
使用场景:
- 提交按钮:防止多次提交按钮
- 服务端验证场景:搜索联想词功能等(可用lodash.debounce)
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null)
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
节流
使用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
如果一个时间被频繁触发多次,节流函数可以按照固定的频率区执行对应的事件处理方法。函数节流保证一个时间一定时间内只执行一次。
(每次触发事件时,若当前有等待执行的延时函数,则直接return)
//节流throttle代码:
function throttle(fn, delay) {
let canRun = true; // 通过闭包保存一个标记
return function () {
// 在函数开头判断标记是否为true,不为true则return
if (!canRun) return;
// 立即设置为false
canRun = false;
// 将外部传入的函数的执行放在setTimeout中
setTimeout(() => {
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
// 当定时器没有执行的时候标记永远是false,在开头被return掉
fn.apply(this, arguments);
canRun = true;
}, delay);
};
}
function sayHi(e) {
console.log('节流:', e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi, 500));
1.8 数组去重的方法
1.9 数组扁平化处理
flat
1.按照一个可指定深度递归遍历数组,并将所有元素与遍历的子数组中的元素合并为新数组返回。
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
//使用 Infinity,可展开任意深度的嵌套数组
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2.扁平化与数组空项
flat() 方法会移除数组中的空项
let arr = [
[[[[1, 2, 3, 4, , 5]]]]]
let a = arr.flat(Infinity)
console.log('a', a) // [1, 2, 3, 4, 5]
1.10 js模块化
1.10.1 什么是模块化?
- 将一个复杂的程序依据一定的规范封装成几个块(文件),并进行组合在一起
- 每个块的内部的数据与实现是私有的,只是向外暴露一些接口(方法)与外部其他模块通信
1.10.2 模块化的好处
- 避免变量名冲突(减少命名空间污染)
- 更好的分离,按需加载
- 更高复用性
- 高可维护性
1.10.3 IIFE: 匿名函数自调用
利用函数的块级作用域,定义一个函数,立即执行,初步实现了一个最最最简单的模块。
const iifeModule = (() => {
let count = 0;
return {
increase: () => ++count;
reset: () => {
count = 0;
}
}
})();
iifeModule.increase();
iifeModule.reset();
追问: 有额外依赖时,如何优化IIFE相关代码
答: 以参数的形式传参
1.10.4 CJS - Commonjs
暴露模块:通过module.exports =value 或者export.xxx=value
引入模块:require(xxx)
输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
引入:
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
CommonJs率先在服务端实现了,从框架层面解决依赖,全局变量污染的问题。但是CommonJs规范加载是同步的,只有加载完成才能执行后面的代码。
1.10.5 AMD规范
通过define来定义一个模块,然后require进行加载
缺点:不能按需加载
定义:
define('amdModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1, dependencyModule2) => {
// 业务逻辑
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
return {
increase, reset
}
})
引入:
require(['amdModule'], amdModule => {
amdModule.increase();
})
允许非同步加载模块,允许制定回调函数。但是会有引入成本,不能按需加载。
1.10.6 CMD
整合了CommonJs和AMD的特点,模块的加载是异步的,模块使用时才会加载。
定义:
//定义有依赖的模块
define(function(require, exports, module){
//引入依赖模块(同步)
var module2 = require('./module2')
//引入依赖模块(异步)
require.async('./module3', function (m3) {
})
//暴露模块
exports.xxx = value
})
引入:
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
1.10.7 ES6模块化
引入关键字 import
导出关键字 export 、默认导出export defalut
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
与CommonJS的差异:
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
追问:动态模块
import('./esModule.js').then(dynamicEsModule => {
dynamicEsModule.increase();
})
1.10.8 总结
- CommonJS 规范主要用于服务端编程,加载模块是同步的,并不适合在浏览器环境,因为同步意味着阻塞加载。浏览器资源是异步加载,因此有了AMD CMD解决方案
- AMD规范在浏览器环境中异步加载模块,并且可以异步加载多个模块。不过,AMD规范开发成本过高,代码的阅读和书写比较困难。
- CMD与AMD规范相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。
- ES6在语言标准的层面上,实现了模块功能,实现简单,可以取代COmmonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。
1.11 继承与原型、原型链
什么时候使用继承,为什么继承
1.12 call、apply、bind区别
1.13 数组的哪些方法是深拷贝?使用过lodash吗?
1.14 检测数据类型的方法( instanceof \typeof)
检测数据类型的方法都有哪些呢?
typeOf(): 检测基本数据类型,返回值:number, boolean, string, undefined, object, function,symbol
instanceOf():判断某个实例是否属于某构造函数;A instanceof B ,返回值为boolean类型,用来判断A是否是B的实例对象或者B子类的实例对象Array is Array(): 检测是否为一个数组 、返回true和false
Object.prototype.toString.call():可以检测任何数据类型
1.15 实现深拷贝
1.Object.assign(空对象名,要复制的对象名)
一层实现(深拷贝),2、多层实现(浅拷贝))
2.JSON.parse
函数、undefined无法拷贝
3.手写方法
JSON.parse(JSON.stringify())
function deepClone(obj) {
if (typeof obj !== 'object') {
//简单数据类型直接返回
return obj
}
let newObj = {}
for (let i in obj) {
newObj[i] = deepClone(obj[i])
}
return newObj
}
升级版:
function deepClone(obj) {
if (obj === null) return null; //null 的情况
if (obj instanceof RegExp) return new RegExp(obj); //正则表达式的情况
if (obj instanceof Date) return new Date(obj); //日期对象的情况
if (typeof obj == 'Function') return new function(obj){}; //函数的情况
if (typeof obj != "object") {
//非复杂类型,直接返回 也是结束递归的条件
return obj
}
//[].__proto__.constructor=Array() []
//{}.__proto__.constructor=Object() {}
//因此处理数组的情况时,可以取巧用这个办法来new新对象
var newObj = new obj.__proto__.constructor;
for (var key in obj) {
newObj[key] = deepClone(obj[key])
}
return newObj;
}
1.16 git常用命令
git clone 拉取项目
git branch- b 创建一个分支并切换到该分支
git stash
想提交部分代码 add 想提交的文件,然后 git stash,
git list 查看stash了哪些存储
提交之后
git stash 应用第一条存储
git reset 和 git revert 回退版本
git reset
- 彻底回退到指定的commit 版本,该版本后的所有commit将被清除,包括提交历史纪录;
- 执行后不会产生记录
- 执行后无法恢复
- 执行后HEAD会后移
git revert
- 撤销了指定commit的修改,并不影响后续的commit,但所撤销的commit被后续的commit修改了同一地方则会产生冲突
- 执行后会产生记录
- 执行后,不会清除记录,并且会产生新纪录,所以文件不会丢失
- HEAD会一直向前
1.17 数组方法lodash
1.17.1 cloneDeep 深克隆
实现深拷贝
1.17.2 Unique 比较不同、merge
1.18 z-index什么情境下失效
- Z-index 仅能在定位元素上奏效,元素需设置为 absolute、fixed、relative
- 设置z-index的同时还设置了float浮动
2.react
2.1 函数组件和类组件的区别
组件是React中可复用的最小代码片段,它们会返回要在页面上渲染的React元素。类组件是基于面向对象编程的,主打的是继承、生命周期等核心概念;函数组件内核是函数式编程,没有副作用、引用透明等特点。
- 性能优化上。类组件主要依靠shouldComponentUpdate阻断渲染来提升性能。而函数组件依靠React.Memo缓存渲染结果来提升性能。
- 使用场景上。如果存在需要使用生命周期、继承的组件,那么主推类组件。官方更推崇"组合优于继承"的设计概念。
- 上手程度。类组件更容易上手,但由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,更适用React未来的发展。
2.2 setState的几种使用方式
1.需要使用改变之后的状态,可以使用setState第二个参数
handleNum() {
this.setState(
{
num: this.state.num+1
},
()=>{
//所有状态全部更新完成,并且重新渲染后执行
console.log('after',this.state.num)
}
)
}
2.如果新的状态要根据之前的状态进行计算,可使用函数的方式(setState的第一个参数)
this.setState(
cur => {
console.log('cur', cur)
//cur state的所有值,该函数的返回结果会覆盖之前num的值
return {
num: cur.num + 2
}
},
() => {
}
)
2.3 受控组件和非受控组件
受控组件
例如等元素,在使用时都要绑定一个change事件,表单中状态改变时,触发onChange函数,更新组件的state值。这种组件在React中被称为受控组件。
受控组件更新state的流程:
- 可以通过初始state中设置表单的默认值
- 每当表单的值发生变化时,调用onChange事件处理器
- 事件处理器通过事件对象e拿到改变后的状态,并更新组件的state
- 一旦通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
// 受控组件 通过state存储状态
class Input extends Component{
constructor(){
super();
this.state = {val:''};
}
handleChange=(event)=>{
let val = event.target.value;
this.setState({val})
}
render(){
return (
<div>
<p>{this.state.val}</p>
<input type="text" value={this.state.val} onChange={this.handleChange} /> //input就是受控组件 被状态对象的属性控制
</div>
)
}
}
ReactDOM.render(<Input/>,window.app)
非受控组件
如果一个表单组件没有value props(如:checked),就可以称为非受控组件。在非受控组件中,可以使用ref来从DOM获得表单值。而不是为每个状态更新编写一个事件处理程序。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
//非受控组件(输入框的值是用户控制的,和React无关)
class Demo1 extends Component {
render() {
return (
<input />
//<ABC />
)
}
}
ReactDOM.render(<Demo1/>, document.getElementById('content'))
- 受控组件是 React 控制中的组件,并且是表单数据真实的唯一来源。
- 非受控组件是由 DOM 处理表单数据的地方,而不是在 React 组件中。
尽管非受控组件通常更易于实现,因为只需使用refs即可从 DOM 中获取值,但通常建议优先选择受控制的组件,而不是非受控制的组件。
这样做的主要原因是受控组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式。
React官网
在大多数情况下,我们推荐使用受控组件来实现表单。在受控组件中,表单数据由 React 组件负责处理。另外一个选择是不受控组件,因为不受控组件的数据来源是 DOM 元素,当使用不受控组件时很容易实现 React 代码与非 React 代码的集成。如果你希望的是快速开发、不要求代码质量,不受控组件可以一定程度上减少代码量。否则。你应该使用受控组件。
要编写一个未控制组件,你可以使用一个 ref 来从 DOM 获得 表单值,而不是为每个状态更新编写一个事件处理程序。
总结
页面中所有输入类的DOM如果是现用现取的称为非受控组件(用输入框内部的值是用户控制,和React无关)。但是通过setState将输入的值维护到了state中,需要时再从state中取出,这里的数据就收到了state的控制,称为受控组件。
受控组件
好处:可以使用onChange控制用户的输入,使用正则表达式过滤不合理输入
缺点:多个输入框时,代码看起来臃肿
2.4 useMemo和useCallback的用法及区别
2.5 axios的拦截
2.6 MVC、MVVM、MVP的理解
2.6.1 MVC
MVC是指Model、VIew和Controller:
View层: 负责显示逻辑。
Model层: 负责存储页面的业务数据,以及对应数据的操作。
Controller层: 负责应用与用户的相应操作。
当用户与页面交互, View 需要更新时,首先去找 Controller,然后 Controller 通过调用Model层,来完成对Model层的修改,然后Model层再去通知VIew层更新。
其中 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。
2.6.2 MVVM
MVVM 分为 Model、View、ViewModel:
Model: 代表数据模型,数据和业务逻辑都在Model层中定义
View: 代表UI视图,负责数据的展示
ViewModel: 负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接联系,而是通过ViewModel来进行联系的,Model层和VIeModel层有着双向数据绑定的联系,因此Model中的数据改变时会触发View的刷新,用户在view层操作而改变的数据也会在Model层同步。
这种模式实现了Model层和View层的数据自动同步,开发者不需要自己操作DOM。
2.6.3 MVP
view需要更新数据时,首先去找Presenter,然后Presenter去找Model请求数据,Model获取到数据之后通知Presenter,Presenter再通知view更新。
View和Model不会直接交互,所有的交互都是由Presenter进行,Presenter充当了桥梁的作用。而Presenter必须同时拥有View和Model的对象的引用。
MVC中使用了观察者模式,当Model层发生改变时,View层更新。这样VIew层和Model层耦合在一起,当项目中逻辑变得负责是,可能造成代码混乱。
MVP中通过Presenter来实现对View层和Model层的解耦。MVC中的Controller只知道Model的接口,因此它没办法控制view层的更新。
MVP中,view层的接口暴露给了Presenter,因此可以再Present中将Model和View的变化绑定在一起,以此来实现VIew和Model的同步更新。这样就实现了View好Model的解耦。
2.7 对有状态组件和无状态组件的理解
2.7.1 有状态组件
- 是类组件
- 有继承
- 可以使用this
- 可以使用react生命周期
- 使用较多,容易频繁出发生命周期钩子函数,影响性能
- 内部使用state,维护自身状态的变化,有状态组件根据外部组件传入的props和自身的state进行渲染。
使用场景:
- 需要使用到状态的
- 需要使用状态操作组件的
总结:
类组件可以维护自身的状态变量(state)。类组件中的生命周期,可以让开发者在组件的不同阶段(挂载、更新、卸载)对组件做更多的控制。当一个类组件不需要管理自身状态时,也可以称为无状态组件。
2.7.2 无状态组件
- 不依赖自身的状态state
- 可以是类组件或者函数组件
- 可以完全避免使用this关键字。(由于使用的是箭头函数事件无需绑定)
- 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件
- 组件内部不维护state,可以根据外部组件传入的props进行渲染,当props改变时,组件重新渲染
优点: - 简化代码
- 组件不需要被实例化,无生命周期,提高性能。输出(渲染)只取决于输入(属性),无副作用
- 视图和数据的解耦分离
缺点: - 无法使用ref
- 无生命周期方法
- 无法控制组件的重新渲染,因为无法使用shouldComponentUpdate方法,当组件接收到新的属性时,会重新渲染
总结:
如果是一个组件内部的状态并且与外部无关的组件,可以考虑使用无状态组件,这样状态数不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,应优先设计为函数组件。比如自定义的、等
2.8 React Hooks在平时开发中需要注意的问题
2.8.1 useState在设置状态时,只有第一次是生效的,后期需要更新状态,必须通过useEffect
//一个公共组件
export default function MultipleChoice(props){
const { multipleList } = props;
const [pollutionList, setPollutionList] = useState(multipleList);
//正确做法
// useEffect(() =>{setPollutionList(multipleList )},[multipleList])
return(
<>
{multipleList.map((item)=>{
return (<div>{item}</div>)
})}
</>
)
}
2.8.2 不要在循环、条件或嵌套函数中调用hook,必须始终在React函数的顶层使用Hook
这是因为React需要利用调用顺序来正确更新相应的状态,以及相应的钩子函数,一旦在循环条件或条件分支语句中调用Hook,就容易导致调用的顺序不一致性,从而产生难以预料的后果。
2.9 React生命周期
- 挂载阶段(Mount):组件第一次在DOM树中被渲染的过程
- 更新阶段(Update):组件状态发生变化,重新更新渲染的过程
- 卸载过程(Unmount):组件从DOM树中被移除的过程
2.9.1 挂载阶段
constructor
组件的构造函数,第一个被执行,若没有显示定义它,会有一个默认的构造函数,但是若显示定义了构造函数,必须在构造函数中执行super(props),否则无法在构造函数中拿到this。
consturctor中通常只做两件事:
- 初始化组件的state
- 给事件处理方法绑定this
constructor(props) {
super(props);
// 不要在构造函数中调用 setState,可以直接给 state 设置初始值
this.state = { counter: 0 }
this.handleClick = this.handleClick.bind(this)
}
getDerviedStateFromProps
这是个静态方法,不能在这个函数里使用this,有两个参数props和state,分别指接受到的新参数和当前组件的state对象。这个函数会返回一个对象来更新当前的state对象,如果不需要可以返回null。
// 当 props.counter 变化时,赋值给 state
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
if (props.counter !== state.counter) {
return {
counter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
现在可以显示传入counter,但是如果想通过点击实现state.counter的增加,会发现值不会发生任何变化,一直保持props传进来的值。这是由于React16.4^版本中,setState和forceUpdate也会触发这个生命周期,所以当组件内部state变化后,就会重新走这个方法,同时把state赋值为props的值。解决:需要多加一个字段来记录之前的props值,这样就会解决上述问题,代码如下:
// 这里只列出需要变化的地方
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// 增加一个 preCounter 来记录之前的 props 传来的值
preCounter: 0,
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
// 跟 state.preCounter 进行比较
if (props.counter !== state.preCounter) {
return {
counter: props.counter,
preCounter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
render
- 返回一个虚拟DOM,会被挂载到虚拟DOM树中,最终渲染到真实DOM中
- render可能不只运行一次,只要需要重新渲染,就会重新运行
- 严禁使用setState,因为可能会导致无限递归渲染
- 可以返回布尔值或null 控制组件不渲染任何内容
componentDidMount()
- 执行依赖于DOM的操作
- 发送网络请求(官方建议)
- 添加订阅消息(会在componentWillUnmount取消订阅)
如果在componentDidMount中调用setState,就会出发一次额外的渲染,多调用一次render函数。由于它是在浏览器刷新屏幕前执行的,所以用户是没有感知的,但是我们应该避免这样使用,这样会带来一定的性能问题,尽量在constructor中初始化state对象。
2.9.2 组件更新阶段
当组件的props改变了,或者组件内部调用了steState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次。这个过程会依次调用下面方法:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
shouldComponentUpdate
- 指示react是否要重新渲染该组件,通过返回true和fasle来指定
- 默认情况下,会直接返回true
可以比较 this.props 和 nextProps ,this.state 和 nextState 值是否变化,来确认返回 true 或者 false。当返回 false 时,组件的更新过程停止,后续的 render、componentDidUpdate 也不会被调用。
getSnapshotBeforeUpdate
//prevProps prevState:更新之前的props和state
getSnapshotBeforeUpdate(prevProps, prevState)
- 这个方法执行在rendr之后,componentDidUpdate之前(真实的DOM构建完成,但还未实际渲染到页面中)
- 该函数的返回值,会作为componentDidUpdate的第三个参数
- 在该函数中,通常用于实现一些附加的dom操作
componentDidUpdate
// prevProps: 更新前的props
// prevState: 更新前的state
// snapshot: getSnapshotBeforeUpdate()生命周期的返回值
componentDidUpdate(prevProps, prevState, snapshot){}
- 首次渲染不会执行此方法,更新后会被立即调用
- 往往在该函数中使用dom操作,改变元素(也可以在此进行网络请求,例如:当props未发生变化时,则不执行网络请求)
2.9.3 组件卸载阶段
componentWillUnmount
- 一个组件被卸载和销毁之前被调用
- 清除 timer,取消网络请求或清除
- 取消在 componentDidMount() 中创建的订阅等
-不应该在使用setState,因为组件一旦卸载,就不会装载也不会渲染
2.94 useeffect 代替了哪些生命周期
1.代替componentDidMount
useEffect(() => {
console.log('Did mount!');
}, []);
2.代替componentDidUpdate
useEffect(() => {
console.log('Did update!');
},[a]);
3.代替componentWillUnmount
useEffect(() => {
return () => {
console.log('will unmount');
}
});
2.10 react合成事件
在页面上点击按钮,事件开始在原生DOM上走捕获冒泡流程,React监听的是document上的冒泡阶段;事件冒泡到document后,React将事件再派发到组件树中,然后事件开始在组件树DOM中走捕获冒泡流程。
2.10.1 好处:
1.更好实现跨平台:
顶层事件代理机制:保证冒牌一致性,将不同平台事件模拟成合成事件。
2.避免垃圾回收
react引入事件池,事件池中获取或释放事件对象。
react事件不会被释放掉,而是存在一个数组中;当事件触发,就从这个数组中弹出,避免频繁的创建和销毁。
3.方便事件统一管理和事务机制
2.10.2 和原生事件的区别:
**写法:**原生(onclick)react:(onClick)
事件处理函数写法不同:
React JSX语法中,传入一个函数作为事件处理函数
阻止默认行为方式的不同
原生事件:通过返回false 方式阻止默认行为;
React:显式使用preventDefault() 方法阻止;
3.vue
3.1 vue守卫
3.2 双向绑定原理
3.3 keep-alive
4.ES6
4.1 promise 传入函数是异步还是同步
4.2 set与map的区别
4.3 es6新增语法有哪些
4.4 promise 用过哪些api
.then 得到异步任务的正确结果
.catch 获取异常信息
.finally 成功与失败都会执行
.all() 并发处理多个异步任务,所有任务都能执行完成才能得到结果
.race() 并发处理多个异步任务,只要有一个任务完成就能得到结果
4.5 cookie、localstorage、sessionstorage
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
cookie | localstorage | sessionstorage | |
---|---|---|---|
大小 | 4k左右 | 5M | – |
性能 | 会参与到与服务端的请求中 | 单纯的前端存储,不参与服务端通信 | – |
时效性 | 根据服务端设置的过期时间 | 不进行手动删除则一直有效更持久的保存数据 | 当前页面关闭,于保存会话级别的数据 |
跨tab共享 | 同源窗口共享 | 同源窗口支持共享 | sessionStorage不支持跨tab |
5.其他
5.1 发布订阅者模式
5.2 webpack的优化插件
5.1 scss、less、css区别
5.2 MVVM、MVC的区别
5.3 哪些方法能够处理异步
5.4 跨域怎么解决
6.场景
6.1 element-ui 中表格的实现,传入一个columns和datas数组,如何渲染成表格?
6.2 PC端登录时手机二维码扫码登录,如何实现?
6.3 一个input框,默认值为1,输入1234 失去焦点值改为1234,点击取消仍是1,怎么实现?点击取消时会不会执行失去焦点函数?
6.4 一组字符串,获得出现次数最多的字符并返回出现次数
思路:先创建一个对象,然后便利字符串每个字符,如果对象的属性等于该字符,则属性值+1,若不存在,则创建这个属性。循环后,根据对象判断哪个字符出现次数最多。
代码:
let str = 'qwertyuiopasdfghjklrfgvbxcfghyuioss'
let arr = str.split('')
let numObj = {}
arr.forEach((item) => {
if (numObj[item]) {
numObj[item] = numObj[item] + 1
} else {
numObj[item] = 1
}
})
// 循环成一个对象
// numObj Object { a: 5;b: 1;c: 1;d: 1;e: 1;f: 3;g: 3;h: 2;i: 2;j: 1;k: 1;l: 1;o: 2;p: 1;q: 1;r: 2;s: 3;t: 1;u: 2;v: 1;w: 1;x: 1;y: 2}
console.log('numObj', numObj)
let max = 1
let final = ''
for (let i in numObj) {
if (numObj[i] > max) {
max = numObj[i]
final = i
}
}
console.log('max',max)
console.log('final',final)
//max 5
//final a
6.5 一个字符串let a=‘123efw4k5’,实现过滤,只要字母
7. 关于优化
7.1 防抖和节流
(见文章1.7)
7.2 回流与重绘
当触发回流时一定触发重绘,触发重绘不一定触发回流。
7.21 回流
当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。
触发回流的场景:
- 页面的首次渲染
- 浏览器窗口大小发生变化
- 元素内容发生变化
- 元素的字体大小发生变化
- 元素的尺寸或者位置发生变化
- 激活css伪类
- 添加或删除可见的DOM元素
7.22 重绘
当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。
触发重绘的场景:
- color、background相关属性:background-color、background-image等
- outline相关属性:outLine-color、outline-width、text-decoration等
- border-radius、visibility、boxshadow
7.23 如何避免重绘和回流
- 操作DOM时,尽量在底层级的DOM节点进行操作
- 减少使用css表达式的频率
- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化不会影响其他元素
- 避免频繁操作DOM,可以创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后把它添加到文档中
- 将元素先设置display:none,操作结束后再把它显示出来。因为display属性为none的元素上进行DOM操作不会引发回流和重绘
- 将多个DOM操作写在一起,而不是读写操作穿插着写,这得益于浏览器的渲染队列机制
documentFragment:文档接口片段,一个没有父对象的最小文档对象。就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。
渲染队列:浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
7.3 webpack
7.4 图片的优化
- 减少使用。很多时候会使用到修饰类图片,可以用css去代替
- 小图使用base64格式
- 使用css精灵(将多张图片整合到一起,利用背景定位,让不同元素展示图片的不同位置)
- 选择正确的图片格式
- 能够使用WebP格式的浏览器尽量使用Webp格式。因为webp格式具有更好的图像数据压缩算法,能带来更小的图片体积,但是兼容性不好
- 小图使用png,对于大部分图标,png可以替代svg
- 照片使用jpeg
7.5 懒加载
图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源。根据这个原理,将页面上的图片的src属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动时,进行判断,如果图片进入可视区,则从自定义属性中取出真实路径赋值给图片的src属性,以此来实现图片的延迟加载。
优点:
提高网站首屏加载速度,提升用户体验,可以减少服务器的压力。适用于多图片、页面列表较长的场景中。
知识点:
(1) window.innerHeight 是浏览器可视区的高度
(2)document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离
(3)imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离)
(4)图片加载条件:img.offsetTop < window.innerHeight + document.body.scrollTop;
代码实现:
<div class="container">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winHeight= window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winHeight ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
window.onscroll = lozyLoad();
</script>
预加载:
将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。减少用户的等待时间,提高用户体验。
8. 计算机网络
8.1 在浏览器输入地址后,按下回车键,都发生了什么?
- 浏览器自动补全协议、端口
- 浏览器自动完成url编码
- 浏览器根据url地址查找本地缓存,根据缓存规则看是否命中缓存,若命中缓存则直接使用缓存,不再发出请求
- 通过DNS解析找到服务器的IP地址
- 浏览器向服务器发出建立TCP连接的申请,完成三次握手后,连接通道建立
- 若使用了HTTPS协议,则还会进行SSL握手,建立加密信道。使用SSL握手时,会确定是否使用HTTP2
- 浏览器决定要附带哪些cookie到请求头中
- 浏览器自动设置好请求头、协议版本、cookie,发出GET请求
- 服务器处理请求,进入后端处理流程。完成处理后,服务器响应一个HTTP报文给浏览器。
- 浏览器根据使用的协议版本,以及Connection字段的约定,决定是否要保留TCP连接。
- 浏览器根据响应状态码决定如何处理这一次响应
- 浏览器根据响应头中的Content-Type字段识别响应类型,如果是text/html,则对响应体的内容进行HTML解析,否则做其他处理
- 浏览器根据响应头的其他内容完成缓存、cookie的设置
- 浏览器开始从上到下解析HTML,若遇到外部资源链接,则进一步请求资源
- 解析过程中生成DOM树、CSSOM树,然后一边生成,一边把二者合并为渲染树(rendering tree),随后对渲染树中的每个节点计算位置和大小(reflow),最后把每个节点利用GPU绘制到屏幕(repaint)
- 在解析过程中还会触发一系列的事件,当DOM树完成后会触发DOMContentLoaded事件,当所有资源加载完毕后会触发load事件
组件
组件1:微站搜索组件
需求1:封装微站搜索框时,添加新需求:因为微站的数据可能上百个,所以需要在删除的功能里支持搜索删除,并要求百度搜索框效果(即:输入框输入文字,对展示已选中的微站进行联动,不作操作,删除文字,展示原始选中的数据)
上面选中后,后把数据存储到两个数组里,checkedList searchList
searchList一直为动态的,也是展示出来的
checkedList一直为原始值,
百度搜索效果:删除搜索开框输入内容,(先判断内容是否为空,若为空,设置searchList的值为checkedList),否则则去checkedList 中filter满足条件的值, 并设置为searchList
点击删除时.拿到id去checkedList中做修改,并同时更新searchList数组
// 问题1:接口数据与antddesign字段不一致
// 解决:写一个递归,根据childrenlist判断是否继续递归
// const formatMicroStation = (arr) => {
// let newArr = [];
// newArr = arr.map((i) => {
// return {
// title: i.name,
// value: i.name + '|' + i.code,
// key: i.code,
// children:
// i?.child_list?.length > 0 ? formatMicroStation(i.child_list) : [],
// };
// });
// return newArr;
// };
// 问题2:选择树后,返回一个数组,数组里面是选中的label,无法拿到id
// 解决:处理树数据的时候,将label处理为 "汉字|id "格式,这样搜索时,还可以通过id搜索到该条数据
组件2:单选框
// 单选框 - 根据每个对象里的isseleted字段控制 - {name:'',isselect:0}
// 通过点击一条数据时,拿到id,根据id改变isselected,根据isselected控制动态className
组件3: 多选框
// 多选框 - 根据存储id的数组进行控制
// 将选择的数据存储id到数组里。
// 点击一个选项时,根据indexof判断数组里是否已经存在。 没有的话,push数组,有的话splice删除
// jsx循环时,使用数组.includes(某一条数据的id),控制动态className
组件4: 时间轴 -自动播放
// 组件4
// 时间轴自动播放 点击播放,时间轴自动切换选中的时间,依次向下
// 用一个变量存储选中的时间id,useffect监听这个变量,变量改变,判断是否停止,不停止则触发settimeout,settimeout触发选中数据+1的操作
其他组件:echarts
// 折线图,柱状图,饼状图传入的属性不一致,如有的图要求图例,需要传legend,有的需要下载功能。不同的需求 需要传入不同的 对象
// 解决: 直接在父组件中传入一个option