前端面试题

本文深入讲解前端开发中的关键技术和概念,包括盒模型、BFC、内存泄漏预防、React与Vue框架特性、ES6新特性、模块化编程、事件处理机制等,并探讨了优化技巧和常见场景解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

写在前面

习题整理,实时更新

1.原生

1.1 盒模型

box-sizing
content-box:默认值。 设置的任何边框和内外边距会被增加到宽高基础之上
border-box:你想要设置的边框和内边距的值是包含在 width 内的

1.2 BFC

BFC块级格式上下文,一个独立显然区域,只有块级盒参与。
产生:

  1. float不为none
  2. overflow不为visible
  3. position为absolute,fixed
  4. display为inline-block
    好处:
  5. 解决margin折叠问题
  6. 清除浮动
  7. 解决文字环绕问题。
  8. 生成量两列布局。(两个box浮动)
1.3 清除浮动

问题:1.浮动元素脱离文档流,导致父元素塌陷(撑不开父元素的高度)2.影响之后元素布局
解决:

  1. 给父元素固定宽高(不够灵活)
  2. 在浮动元素后加一个空的块元素{clear:both}
  3. 给浮动元素的父元素添加属性
.clerafix:after{
content:"";
display:block;
clear:both
}
  1. 给父元素加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. 避免变量名冲突(减少命名空间污染)
  2. 更好的分离,按需加载
  3. 更高复用性
  4. 高可维护性
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的差异:

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. 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什么情境下失效
  1. Z-index 仅能在定位元素上奏效,元素需设置为 absolute、fixed、relative
  2. 设置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

同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;

cookielocalstoragesessionstorage
大小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 如何避免重绘和回流
  1. 操作DOM时,尽量在底层级的DOM节点进行操作
  2. 减少使用css表达式的频率
  3. 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式
  4. 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化不会影响其他元素
  5. 避免频繁操作DOM,可以创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后把它添加到文档中
  6. 将元素先设置display:none,操作结束后再把它显示出来。因为display属性为none的元素上进行DOM操作不会引发回流和重绘
  7. 将多个DOM操作写在一起,而不是读写操作穿插着写,这得益于浏览器的渲染队列机制

documentFragment:文档接口片段,一个没有父对象的最小文档对象。就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。
渲染队列:浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

7.3 webpack

在这里插入图片描述

7.4 图片的优化
  1. 减少使用。很多时候会使用到修饰类图片,可以用css去代替
  2. 小图使用base64格式
  3. 使用css精灵(将多张图片整合到一起,利用背景定位,让不同元素展示图片的不同位置)
  4. 选择正确的图片格式
    • 能够使用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 在浏览器输入地址后,按下回车键,都发生了什么?
  1. 浏览器自动补全协议、端口
  2. 浏览器自动完成url编码
  3. 浏览器根据url地址查找本地缓存,根据缓存规则看是否命中缓存,若命中缓存则直接使用缓存,不再发出请求
  4. 通过DNS解析找到服务器的IP地址
  5. 浏览器向服务器发出建立TCP连接的申请,完成三次握手后,连接通道建立
  6. 若使用了HTTPS协议,则还会进行SSL握手,建立加密信道。使用SSL握手时,会确定是否使用HTTP2
  7. 浏览器决定要附带哪些cookie到请求头中
  8. 浏览器自动设置好请求头、协议版本、cookie,发出GET请求
  9. 服务器处理请求,进入后端处理流程。完成处理后,服务器响应一个HTTP报文给浏览器。
  10. 浏览器根据使用的协议版本,以及Connection字段的约定,决定是否要保留TCP连接。
  11. 浏览器根据响应状态码决定如何处理这一次响应
  12. 浏览器根据响应头中的Content-Type字段识别响应类型,如果是text/html,则对响应体的内容进行HTML解析,否则做其他处理
  13. 浏览器根据响应头的其他内容完成缓存、cookie的设置
  14. 浏览器开始从上到下解析HTML,若遇到外部资源链接,则进一步请求资源
  15. 解析过程中生成DOM树、CSSOM树,然后一边生成,一边把二者合并为渲染树(rendering tree),随后对渲染树中的每个节点计算位置和大小(reflow),最后把每个节点利用GPU绘制到屏幕(repaint)
  16. 在解析过程中还会触发一系列的事件,当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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值