2024年最新前端百问面试题之鱼龙混杂(第三篇)(1),阿里前端社招面试

TCP协议

  • TCP 和 UDP 的区别?
  • TCP 三次握手的过程?
  • 为什么是三次而不是两次、四次?
  • 三次握手过程中可以携带数据么?
  • 说说 TCP 四次挥手的过程
  • 为什么是四次挥手而不是三次?
  • 半连接队列和 SYN Flood 攻击的关系
  • 如何应对 SYN Flood 攻击?
  • 介绍一下 TCP 报文头部的字段
  • TCP 快速打开的原理(TFO)
  • 说说TCP报文中时间戳的作用?
  • TCP 的超时重传时间是如何计算的?
  • TCP 的流量控制
  • TCP 的拥塞控制
  • 说说 Nagle 算法和延迟确认?
  • 如何理解 TCP 的 keep-alive?

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

浏览器篇
  • 浏览器缓存?
  • 说一说浏览器的本地存储?各自优劣如何?
  • 说一说从输入URL到页面呈现发生了什么?
  • 谈谈你对重绘和回流的理解
  • XSS攻击
  • CSRF攻击
  • HTTPS为什么让数据传输更安全?
  • 实现事件的防抖和节流?
  • 实现图片懒加载?

3.state 参数传递:使用 history.pushLink 组件进行路由跳转时,还可以通过 state 属性传递参数,location.state

14. Js中浅拷贝和深拷贝有什么区别,如何实现?(3次)

在 JavaScript 中,浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两种常见的数据拷贝方式,它们的区别在于拷贝的程度。

  1. 浅拷贝:浅拷贝仅拷贝对象或数组的引用,而不是拷贝其内部的值。这意味着原始对象和拷贝后的对象会共享同一个内存地址,当其中一个对象修改了属性或元素时,另一个对象也会受到影响。
  2. 深拷贝:深拷贝会创建一个完全独立的对象或数组,并递归地拷贝其所有的属性或元素。这意味着原始对象和拷贝后的对象不共享内存地址,修改其中一个对象不会对另一个对象产生影响。

下面是一些常见的实现方法:

浅拷贝的实现方法:

  • 使用 Object.assign() 方法进行浅拷贝:
  • 使用展开运算符进行浅拷贝:

深拷贝的实现方法:

  • 使用 JSON.parse(JSON.stringify()) 进行深拷贝,但注意该方法有一些限制和注意事项(比如无法拷贝函数、循环引用会导致错误等):
  • 使用递归实现自定义的深拷贝函数:

15. 为什么不能在循环、条件或嵌套函数中调用 Hooks?(2次)

在 React 中,Hooks 是一种用于在函数组件中添加状态和其他 React 特性的方式。Hooks 通过使用特定的钩子函数(如 useStateuseEffect 等)来管理组件的状态和副作用。

根据 React 的规范,Hooks 只能在函数的顶层调用,也就是说不能在循环、条件或嵌套函数中调用 Hooks。这是因为 React 依赖于 Hooks 的调用顺序来正确地追踪组件的状态。如果在循环、条件或嵌套函数中调用 Hooks,会导致 React 无法正常追踪和管理组件的状态,可能引发一些错误或不一致的行为。

16. React有哪些性能优化的方法?(3次)

1.使用虚拟列表和虚拟表格:对于大量数据的情况下,使用虚拟化技术来优化列表和表格的性能。

2.批量更新状态:使用setState的函数式形式(setState(updater)),将多个状态更新合并为一个,从而减少React重复渲染的次数。

3.懒加载组件:使用React.lazy和Suspense进行组件懒加载,在需要时再动态加载组件,提高初始加载性能。

4.使用css过渡和动画:使用CSS过渡和动画来优化组件的过渡效果和用户交互体验,从而提高用户满意度。

5.避免在render方法中调用函数和组件方法:例如在render方法中调用this.props、this.state、this.forceUpdate、this.refs等方法, 会引起组件的重渲染。

17. Webpack有哪些核心概念?

Webpack 是一个现代的 JavaScript 应用程序静态模块打包工具,它的核心概念有以下几个:

  1. 入口(Entry):指示 Webpack 从哪个文件开始构建依赖关系图。入口可以是一个或多个文件,形成一个依赖图的根节点。
  2. 输出(Output):指示 Webpack 将打包后的文件输出到哪里,以及如何命名这些文件。输出通常是一个或多个打包后的文件,可以是 JavaScript、CSS 等类型。
  3. 加载器(Loaders):Webpack 可以通过加载器处理非 JavaScript 文件。加载器允许你在导入文件时进行预处理转换,例如将 Sass 编译为 CSS、将 ES6/TypeScript 转译为 ES5 等。
  4. 插件(Plugins):插件用于扩展 Webpack 的功能。它们可以执行范围更广的任务,从而实现优化、资源管理、注入环境变量等功能。例如,添加 HtmlWebpackPlugin 插件可以为生成的 HTML 文件添加自动引用打包后的脚本。
  5. 模式(Mode):模式指定了 Webpack 的构建环境,可以是开发模式(development)、生产模式(production)或其他自定义环境。不同的模式会触发不同的内置优化策略,例如压缩代码、启用浏览器调试工具等。
  6. 代码分割(Code Splitting):Webpack 支持将代码分割成多个块,以实现按需加载和优化加载性能。通过代码分割,可以将常用的第三方库或业务代码拆分成不同的文件,按需加载,减少初始加载时间。
  7. 缓存(Caching):Webpack 通过使用哈希值作为文件名的一部分,可以根据文件内容的更改来更新缓存。这样,在文件内容不变的情况下,可以利用浏览器缓存,减少重新下载的开销。
  8. DevServer:Webpack DevServer 是一个轻量级的开发服务器,提供了热更新、代理转发、自动刷新等开发时常用的功能。

这些都是 Webpack 的核心概念,了解并熟练使用它们可以帮助你更好地使用 Webpack 进行模块打包和构建前端应用程序。

18. 常用的hooks都有哪些,说出他们的作用,最少列出来5个?(2次)

以下是常用的 React Hooks,并列出了它们的作用:

  1. useState:用于在函数组件中添加状态管理。它返回一个包含当前状态和更新状态的数组,可以通过解构赋值来使用。
  2. useEffect:用于在函数组件中执行副作用操作,比如订阅外部数据、操作 DOM 等。它接收一个回调函数和依赖数组,并在每次渲染后执行回调函数。
  3. useContext:用于从 React 的 Context 中获取当前的上下文值。它接收一个 Context 对象,并返回该 Context 的当前值。
  4. useReducer:类似于 Redux 中的 reducer,用于管理复杂的状态逻辑。它返回当前状态和一个 dispatch 函数,用于派发状态更新的动作。
  5. useCallback:用于性能优化,避免不必要的函数重新创建。它接收一个回调函数和依赖数组,并返回一个记忆化的版本,只在依赖数组变化时才会重新创建。
  6. useMemo:用于性能优化,缓存计算结果。它接收一个回调函数和依赖数组,并返回计算结果。只有当依赖数组发生变化时,才会重新计算结果。
  7. useRef:用于在函数组件中创建可变的 ref 对象。它返回一个包含 current 属性的对象,该属性可以保存任意可变值,并在组件重新渲染时保持不变。
  8. useLayoutEffect:类似于 useEffect,但它在 DOM 更新之后同步执行。在大多数情况下,可以使用 useEffect 替代,但在需要获取真实 DOM 属性时,可能需要使用 useLayoutEffect
  9. useImperativeHandle:用于自定义暴露给父组件的实例值。它接收一个 ref 对象和一个工厂函数,在父组件中可以通过 ref 访问工厂函数返回的值。
  10. useDebugValue:用于在 React 开发者工具中显示自定义的钩子值,方便调试和跟踪。它接收一个值和一个格式化函数,用于显示在工具中的自定义标签。

这些是常用的 React Hooks,每个 Hook 都有特定的作用,能够方便地处理组件的状态管理、副作用操作、上下文等功能。使用 Hooks 可以使函数组件更易于编写、理解和维护。

19。Typescript中泛型是什么?(2次)

泛型是 TypeScript 中一个重要的特性,它允许在函数、类、接口等类型声明中使用类型参数,从而实现更加抽象和灵活的类型定义。

通过使用泛型,可以编写具有通用性的代码,不受特定类型的限制。例如,一个数组排序函数不需要关心数组元素的具体类型,只需要确保数组元素是可比较的,就可以实现一个通用的排序方法。

在 TypeScript 中泛型可以针对以下几个部分进行声明:

  1. 函数泛型:通过在函数参数列表前声明类型参数,可以使函数参数和返回值的类型更加灵活。

  2. 类泛型:在声明类时添加泛型类型参数,可以使类的属性和方法可以使用泛型类型参数。

  3. 接口泛型:可以使用泛型定义接口的属性类型和方法参数类型。

20umijs.lugin-access插件如果使用?

umijs/plugin-access 是 Umi.js 的一个插件,它提供了一种方便的方式来管理和控制路由权限。通过配置路由权限规则,可以根据用户的身份、权限等信息来限制用户对某些路由的访问。

要使用 umijs/plugin-access 插件,可以按照以下步骤进行操作:

\1. 安装插件:

shellCopy Code
​
npm install @umijs/plugin-access --save-dev

\2. 在 .umirc.ts 或者 config/config.ts 文件中配置插件:

javascriptCopy Code
​
export default {
​
 plugins: ['@umijs/plugin-access'],
​
 access: {
​
  **// 配置权限规则**
​
  **// 可以是一个函数,也可以是一个包含多个函数的数组**
​
  **// 函数的返回值为 boolean 表示是否有权限访问该路由**
​
  canAccessRoute: (route) => {
​
   **// 在这里根据用户的身份、权限等信息判断是否有权限访问该路由**
​
   **// 返回 true 表示有权限,返回 false 表示没有权限**
​
  },
​
 },
​
};

\3. 在路由配置文件 config/routes.ts 中,可以使用 access 属性来指定每个路由的权限要求:

javascriptCopy Code
​
const routes = [
​
 {
​
  path: '/public',
​
  component: 'PublicPage',
​
 },
​
 {
​
  path: '/private',
​
  component: 'PrivatePage',
​
  access: 'user', **// 这个路由需要用户登录才能访问**
​
 },
​
 **// ...**
​
];

\4. 在页面组件中,可以通过 access 对象访问当前路由的权限信息:

javascriptCopy Code
​
import { useAccess } from 'umi';
​
 
​
export default function PrivatePage() {
​
 const access = useAccess();
​
 
​
 return (
​
    <div>
​
   {access.canAccessRoute('/private') ? (
​
•    <h1>Welcome to private page!</h1>
​
   ) : (
​
•    <h1>Sorry, you don't have access to this page.</h1>
​
   )}
​
  </div>
​
 );
​
}

21.数组【1,2,3,4,5,3,2,2,4,2,2,3,1,3,5】中出现次数最多的数,并统计出现多 少次,编写一个函数?

 function findMostFrequentNumber(arr) {
​
 const counter = arr.reduce((acc, num) => {
​
  if (num in acc) {
​
   acc[num]++;
​
  } else {
​
   acc[num] = 1;
​
  }
​
  return acc;
​
 }, {});
​
 
​
 let maxNum = null;
​
 let maxCount = 0;
​
 for (const num in counter) {
​
  if (counter[num] > maxCount) {
​
   maxNum = num;
​
   maxCount = counter[num];
​
  }
​
 }
​
 
​
 return { number: maxNum, count: maxCount };
​
}
​
 
​
**// 调用示例**
​
const arr = [1,2,3,4,5,3,2,2,4,2,2,3,1,3,5];
​
const result = findMostFrequentNumber(arr);
​
console.log(`出现次数最多的数字是 ${result.number},出现了 ${result.count} 次`);

22大文件如何做断点续传?(2)

\1. 将大文件分割为小块:将大文件分割成多个固定大小的小块,例如每个小块的大小可以设置为 1MB。这样可以方便进行分片上传和管理。

\2. 记录上传进度:在客户端和服务端都需要记录上传进度。客户端可以通过记录已成功上传的分片信息来跟踪上传进度,服务端可以通过记录已接收的分片信息来跟踪接收进度。

\3. 分片上传:将分割后的小块逐个上传到服务器,并记录已成功上传的分片信息。可以使用 HTTP 或其他协议来进行分片上传。

\4. 上传失败处理:如果某个分片上传失败,需要记录下该分片的状态,以便后续重新上传。可以保存上传失败的分片信息,并将其标记为未完成。

\5. 断点续传:在下次上传时,检查之前的上传进度和已完成的分片信息。根据记录的信息,跳过已成功上传的分片,只上传未完成的分片。确保已上传的分片不会重复上传。

\6. 合并分片:当所有分片都上传完成后,在服务端将这些分片按顺序合并为完整的文件。

23.元素js如何实现上拉加载下拉刷新?(2)

24说说设备像素、css像素、设备独立像素、dpr、ppi之间的区别?(2)

\1. 设备像素 设备像素指物理屏幕上的像素点数量,通常用“px”表示。屏幕上的每个px将有一个相对应的硬件像素来对应显示。设备像素决定了屏幕的绝对分辨率。

\2. CSS 像素 CSS 像素在设计中用来表示页面中图形和文本等元素尺寸的虚拟单位,通常用“px”表示。CSS 像素并不是“真实”的像素,它是相对于屏幕分辨率独立的。

\3. 设备独立像素 设备独立像素(DP,Density-Independent Pixel),也称为逻辑像素,是一种基于屏幕密度的抽象概念,通常用“dp”或“dip”表示,Android系统中也将其称为“sp”。设备独立像素是与屏幕像素密度有关的像素单位,屏幕像素密度越高,同样大小的物体所占设备独立像素就越高。

\4. DPR(设备像素比) DPR指的是设备像素和CSS像素的比例关系,即“设备像素 / CSS像素” 。它用来描述物理像素与逻辑像素之间的关系。常见的 DPR 值包括 1、2、3 等。

\5. PPI(每英寸像素数) PPI指的是屏幕上每英寸显示的像素点数量,通常用来表示屏幕密度以及屏幕显示清晰程度。PPI 值可以通过计算设备像素和屏幕尺寸之间的比例得到。

25说说TCP为什么需要三次握手和四次握手?(2)

\1. 三次握手 初始化双方 seq。 确认双方信道可以实现最低限度的全双工三次握手是指在建立 TCP 连接时,客户端与服务器之间需要进行三次通信以确认连接的建立。其流程如下:

· 第一次握手:客户端向服务器发送 SYN 报文,并随机生成一个初始序号(ISN)。

· 第二次握手:服务器收到SYN报文后,回复一个SYN+ACK报文作为确认,其也会随机生成一个ISN值。

· 第三次握手:客户端再次向服务器回复一个ACK报文,确认连接已建立。

\2. 四次握手 TCP 要支持半关闭连接四次握手是指在释放 TCP 连接时,客户端与服务器之间需要通过四次通信来确认连接已经关闭。其流程如下:

· 第一次握手:客户端向服务器发送FIN报文,请求关闭连接。

· 第二次握手:服务器收到FIN报文后,回复一个ACK报文作为确认,但此时仍可向客户端发送数据。

· 第三次握手:服务器完成数据的发送后,则向客户端发送一个FIN报文,表示服务器的数据发送完毕,请求关闭连接。

第四次握手:客户端收到服务器的FIN报文后,向服务器回复一个ACK报文作为确认,进入 TIME_WAIT 状态,等待 2MSL 后断开连接。

26.react之间如何通信(3)

1.props:通过向子组件传递 props,可以实现父子组件之间的通信。这是 React 中最基本的通信方式。父组件可以将需要传递给子组 件的数据作为 props 传递给子组件,子组件可以通过 this.props 访问这些数据。

2.Context:Context 是 React 中的一种全局数据管理方式。它可以让子组件在不通过 props 传递的情况下,直接访问父组件或者祖先 组件中的数据。使用 Context 需要先创建一个 Context 对象,然后在祖先组件中通过 Provider 提供数据,在子孙组件中通 过 Consumer 访问数据。

3.Refs:Refs 允许我们访问在组件中创建的 DOM 或者其他组件实例。通过 Refs,组件可以在不通过 props 或者 context 的情况下, 直接修改子组件或者 DOM 元素的属性。

4.Event Bus:Event Bus 是一种跨组件通信方式,它可以让任何两个组件之间都可以进行通信。

27.什么是闭包,应用场景是什么(2)

闭包是指在函数内部创建的函数,并且该函数能够访问到其外部函数的作用域。

闭包有以下特点:

  1. 内部函数可以访问外部函数中的变量和参数。
  2. 外部函数的执行上下文被保留在内存中,即使外部函数执行完成后,内部函数仍然可以访问外部函数的作用域。
  3. 多个内部函数可以共享同一个父级作用域,形成一个闭包函数族。

闭包的应用场景包括但不限于:

  1. 保护变量:通过使用闭包,可以隐藏变量,只提供对外部函数公开的接口。这样,可以确保变量不会被外部直接修改,增加了数据的安全性。
  2. 计数器和累加器:通过闭包,可以在函数外部保存一个内部状态,并在每次调用内部函数时修改该状态。这一特性可用于实现计数器、累加器等功能。
  3. 延迟执行和回调函数:将函数作为返回值,可以实现延迟执行或者在特定条件满足时回调执行。

28.JS数组之间进行合并,写出三种合并方式?

concat() 方法:

concat() 方法可以合并两个或多个数组,并返回一个新数组。它会将所有输入数组中的元素添加到一个新的数组中。

spread 运算符(扩展运算符):

ES6 中引入的 spread 运算符可以很方便地合并数组。它可以将一个数组转换为独立的参数列表,从而可以很容易地合并多个数组。

push() 方法:

push() 方法可以将一个或多个元素添加到数组的末尾,并返回修改后的数组长度。因此,可以通过循环或迭代来将一个数组中的元素添加到另一个数组中。

29.为什么 useState 返回的是数组而不是对象?

在 React 中,useState 是一个用于在函数组件中声明状态的钩子函数。它的返回值是一个长度固定为 2 的数组,而不是一个对象,这是由设计选择所决定的。

使用数组来表示状态,是因为它具有以下优势:

  1. 简单直观:将状态以数组的形式进行管理,可以更容易地理解和使用。
  2. 顺序保持一致:使用 useState 声明多个状态时,它们的顺序和声明的顺序是完全一致的。你可以根据索引访问和更新每个状态,而不需要命名或记住状态的特定名称。
  3. 无需每次指定键:当更新状态时,不需要像使用对象那样指定键。只要调用 useState 返回的第二个元素(通常命名为 setXXX)即可。

30.React 的生命周期?每个生命周期都做了什么?

在React中,组件的生命周期可以分为三个主要的阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。以下是每个阶段对应的方法:

  1. 挂载阶段(Mounting Phase):
  • constructor:组件实例被创建时调用,用于初始化状态和绑定事件处理函数。
  • static getDerivedStateFromProps:在组件实例化和更新时都会调用,它接收新的属性和当前状态,并返回一个更新后的状态。
  • render:根据组件的状态和属性,返回JSX元素进行渲染,负责生成组件的UI结构。
  • componentDidMount:组件已经被渲染到DOM中后调用,此时可以进行网络请求、订阅事件等副作用操作。
  1. 更新阶段(Updating Phase):
  • static getDerivedStateFromProps:在组件更新时调用,接收新的属性和当前状态,并返回一个更新后的状态。
  • shouldComponentUpdate:在组件即将更新之前调用,返回一个布尔值,用于判断是否需要进行组件的重新渲染。
  • render:重新渲染组件的UI结构。
  • componentDidUpdate:组件已经重新渲染并且更新到DOM上后调用,此时可以进行状态更新、网络请求等副作用操作。
  1. 卸载阶段(Unmounting Phase):
  • componentWillUnmount:组件将要从DOM中移除时调用,可以进行清理工作,如取消订阅、清除定时器等。

32.Js中深拷贝和浅拷贝的定义以及具体实现都有哪些方法?

浅拷贝是指创建一个新对象或数组,该对象或数组的值是原始对象或数组中的引用。换句话说,浅拷贝只复制对象或数组的第一层,而不会递归复制嵌套的对象或数组。

常见的实现浅拷贝的方法有:

  1. 扩展运算符(Spread Operator)
javascriptCopy Codeconst originalArray = [1, 2, 3];
const newArray = [...originalArray];
javascriptCopy Codeconst originalObject = { name: "John", age: 25 };
const newObject = { ...originalObject };
  1. Object.assign()
javascriptCopy Codeconst originalObject = { name: "John", age: 25 };
const newObject = Object.assign({}, originalObject);

注意:Object.assign() 在浅拷贝对象时是深拷贝属性值。
3. Array.slice()

javascriptCopy Codeconst originalArray = [1, 2, 3];
const newArray = originalArray.slice();

深拷贝是指创建一个全新的对象或数组,并递归地复制原始对象或数组的所有嵌套对象或数组。

常见的实现深拷贝的方法有:

  1. 使用 JSON 序列化和反序列化
javascriptCopy Codeconst originalObject = { name: "John", age: 25 };
const newObject = JSON.parse(JSON.stringify(originalObject));

这种方法可以处理大多数情况,但是对于包含函数、正则表达式等特殊对象的深拷贝可能会有问题。
2. 递归实现深拷贝

javascriptCopy Codefunction deepCopy(obj) {
  if (typeof obj === "object" && obj !== null) {
    const copy = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        copy[key] = deepCopy(obj[key]);
      }
    }
    return copy;
  }
  return obj;
}

const originalObject = { name: "John", age: 25 };
const newObject = deepCopy(originalObject);

33.Umi中如何实现路由权限,实现按钮权限?

在 Umi 中实现路由权限和按钮权限可以通过以下步骤:

\1. 路由权限:

· 在路由配置文件中,为每个需要进行权限控制的路由添加 authority 属性,值为权限标识,例如角色名或权限码。

在全局的路由守卫(比如 layouts 中的页面组件)中,判断当前用户是否有权限访问该路由。可以使用 Access 组件配合 authority 属性来进行权限控制。

\2. 按钮权限:

· 在页面组件里,在需要进行权限控制的按钮上添加 authority 属性,值为权限标识,例如角色名或权限码。

自定义一个 Button 组件,根据当前权限判断是否显示按钮。

然后,在页面组件中使用 AccessibleButton 来替代 antd 的 Button 组件,传递相应的 authority 属性。

32.props和state相同点和不同点?render方法在哪些情况下会执行?

Props(属性)和 State(状态)是 React 组件中用于管理数据的两个概念。

相同点:

  1. 它们都是用于存储和管理组件数据的。
  2. 当它们的值发生变化时,都可以触发组件重新渲染。

不同点:

  1. Source(数据来源):Props 是从父组件传递给子组件的,而 State 则是在组件内部定义和管理的。
  2. Mutability(可变性):Props 是只读的,无法在子组件内部直接修改,只能通过父组件重新传递新的 Props。而 State 可以在组件内部进行修改和更新。
  3. 更新方式:当 Props 发生变化时,React 会自动进行组件的重新渲染。而 State 的更新需要调用组件的 setState 方法来触发重新渲染。

关于 render 方法的执行情况:

  1. 初始渲染:组件首次被渲染时,render 方法会被调用,并生成对应的虚拟 DOM 树。
  2. 数据变化:当组件的 Props 或 State 发生变化时,React 会重新调用 render 方法来更新虚拟 DOM,并与之前的虚拟 DOM 进行对比。
  3. 强制更新:通过调用组件的 forceUpdate 方法可以强制触发 render 方法的执行,即使 Props 和 State 没有发生变化。
  4. 父组件更新:如果父组件进行重新渲染,子组件的 render 方法也会被调用。

需要注意的是,只有在 render 方法中返回的虚拟 DOM 与之前的虚拟 DOM 不同,React 才会重新渲染真实 DOM,并更新到页面上。

33.说说你对Object.defineProperty()的理解?

Object.defineProperty() 是 JavaScript 中用于定义或修改对象属性的方法。通过这个方法,可以精确地控制属性的特性,例如可枚举性、可写性、可配置性以及属性的值。

这个方法接受三个参数:要定义属性的对象、属性名和属性描述符对象。属性描述符对象可以包含以下属性:

  1. value:属性的值。
  2. writable:属性是否可写。
  3. enumerable:属性是否可枚举。
  4. configurable:属性是否可配置。

使用 Object.defineProperty() 方法可以实现对属性的定制化控制,例如可以定义一个不可枚举、不可写、但可配置的属性,从而实现对属性的严格保护或隐藏。

34.说说React中的虚拟dom?在虚拟dom计算的时候diff和key之间有什么关系?

虚拟 DOM(Virtual DOM)是 React 中的一种机制,通过在内存中构建一棵轻量级的虚拟 DOM 树来代替操作浏览器 DOM,从而提高组件的渲染性能和用户体验。

在 React 中,当组件的 Props 或 State 发生变化时,React 会根据最新的数据重新生成一棵虚拟 DOM 树,并与之前的虚拟 DOM 树进行对比。在对比的过程中,React 会找到两棵树中不同的节点,并将它们对应的真实 DOM 节点进行修改、删除或添加,最终将更新后的 DOM 渲染到页面上。

虚拟 DOM 的 diff 算法是 React 优化渲染性能的核心。在 diff 算法中,每个节点都有一个唯一的标识符,称为 key。当新旧虚拟 DOM 树进行对比时,React 会通过 key 来判断两个节点是否表示相同的内容。在判断过程中,React 会先以新节点为基准,在旧节点中查找对应的节点。如果找到了相同的节点,则进行更新;否则,将新节点插入到旧节点中或从旧节点中删除。

在使用 React 进行开发时,我们应该尽量避免使用索引作为 key,因为索引本身并没有表示唯一性,容易造成错误的判断结果和性能问题。相反,我们应该在数据中为每个元素提供一个唯一的标识符,例如数据库中的 ID 或者全局唯一的 UUID。

需要注意的是,虽然虚拟 DOM 可以有效地降低浏览器对真实 DOM 的操作次数,但也会带来一些额外的开销。例如,在生成和比较虚拟 DOM 树时,需要进行大量的计算和判断,可能会影响应用的整体性能。因此,在实际开发中,我们需要根据具体情况,权衡使用虚拟 DOM 的益处和代价,选择最适合自己应用的方案。

35.react新出来两个钩子函数是什么?和删掉的will系列有什么区别?(2)

react新出的哪两个钩子函数? 新出的getDerivedStateFromProps 与 getSnapshotBeforeUpdate 两个钩子。 少了componentWillMount,componentWillReceiveProps与componentWillUpdate三个都带有will的钩子。 1、getDerivedStateFromProps 这个钩子的作用其实就是从props获取衍生的state。getDerivedStateFromProps中返回一个对象用于更新当前组件的state,而不是直接取代。 2、getSnapshotBeforeUpdate 这个钩子的意思其实就是再组件更新前获取快照,此方法一般是结合componentDidUpdate使用,getSnapshotBeforeUpdate中返回的值将作为第三参数传递给componentDidUpdate。 为什么删掉will? 三个钩子函数是被废弃,但是不是直接不能用了,而是官方会给出警告并推荐我们在这三个钩子前添加UNSAFE_前缀。 为什么被废弃呢,因为使用率并不太高,这三个钩子很容易被误解和滥用,这几个钩子不稳定。

36.React的props.children使用map函数来遍历会收到异常显示,为什么?应该 如何遍历?

使用 map 函数遍历 React 组件的 props.children 时,可能会遇到异常显示的问题。这是因为 props.children 在不同情况下的类 型不同,导致 map 函数操作的方式也不同。

解决方法:

1.首先检查 props.children 的类型。

2.如果它是一个数组,那么可以直接使用 map 函数进行遍历。

3.如果它只有一个子元素,需要先将它转换为数组,然后再进行遍历

37.ts中抽象类的理解?

在 TypeScript 中,抽象类是一种特殊的类,用于作为其他类的基类或父类,并且不能直接实例化。抽象类主要用于定义一组通用的属性和方法,然后让其他类继承并实现这些抽象类中定义的方法。

抽象类通过使用 abstract 关键字进行声明。抽象类可以包含抽象方法、普通方法和属性。

抽象类的主要特点包括:

  • 不能直接实例化,只能被继承。
  • 可以包含抽象方法、普通方法和属性。
  • 抽象方法必须在派生类中实现。
  • 子类可以覆盖或扩展抽象类中的方法和属性。

38.redux本来是同步的,为什么它能执行异步代码?实现原理是什么?中间件的实现原理是什么?

Redux 是一个使用单一数据源和不可变数据的状态管理库,它本身是同步的,但通过中间件可以实现异步操作。Redux 中最常用的异步中间件是 Redux Thunk、Redux Saga 和 Redux Observable。

具体来说,当我们需要在 Redux 中执行异步代码时,通常会使用如下流程:

  1. 在 View 中触发一个异步 Action。
  2. Action 发送到 Reducer,Reducer 更新 Store 中的状态。
  3. 如果需要执行异步操作,Middleware 捕获到这个 Action,并执行异步代码。
  4. 异步代码完成后, Middleware 发送新的 Action 到 Reducer,Reducer 再次更新 Store 中的状态。

实现原理是中间件利用了 Redux 提供的 action 和 reducer 的单向数据流机制,使得 action 可以被拦截并且异步处理后再次派发出去。中间件本质上是对 dispatch 函数的重写,并且它可以执行某些操作,例如异步处理、日志记录、错误报告等。

中间件的原理是 Redux 通过在 dispatch 中间注入中间件的执行代码,在 action 到达 reducer 之前对 action 进行了修改或者是对应的 side effect 操作。具体来说,每个中间件都是一个函数,它接收 store 的 dispatch 和 getState 函数作为参数,返回一个新的函数,这个函数被用来包装 action creator,在 dispatch 前后进行操作。这种方式支持链式调用多个中间件,以便进行不同操作,例如数据处理、异步调用、日志记录、错误报告等。

39.redux中同步action与异步action最大的区别是什么?

1.同步 Action: 同步 action 是指在触发后立即执行并完成的 action。具体表现为,在 Redux 中通过 dispatch 触发同步 action 后,它 会立即被发送到 reducer 进行状态更新

2.异步 Action: 异步 action 是指在触发后需要一定时间进行处理的 action,通常用于处理异步操作,比如网络请求、定时器等。异步 action 通常会触发多个相关的同步 action,以表示异步操作的不同阶段

40.redux-saga和redux-thunk的区别与使用场景?

Redux Saga 和 Redux Thunk 都是用于处理异步操作的 Redux 中间件,它们在实现和使用上有一些区别,适用于不同的场景。

  1. 区别:
  • 实现方式:Redux Thunk 是一个函数,允许我们在 Action Creator 中返回一个函数,这个函数可以进行异步操作并手动调用 dispatch。而 Redux Saga 则基于 ES6 的 Generator 函数,通过使用特定的语法来处理异步操作。
  • 控制流程:Redux Thunk 使用简单的回调函数来处理异步操作,通常是通过链式调用多个 Action,从而实现异步流程控制。而 Redux Saga 则使用生成器来定义和控制异步操作的流程,通过监听和响应不同的 Action 来执行相应的操作。
  • 异常处理:Redux Thunk 需要手动处理错误,通过 try-catch 捕获异常,并在回调函数中 dispatch 错误信息。而 Redux Saga 具备异常捕获和处理的能力,在 Generator 函数内部可以使用 try-catch 捕获异常,并且可以派发不同的 Action 处理异常情况。
  1. 使用场景:
  • Redux Thunk 适用于简单的异步场景,例如发起一个 AJAX 请求并在请求成功后更新状态。它的学习曲线比较低,容易上手,适合于对异步处理需求不复杂的项目。
  • Redux Saga 适用于复杂的异步场景,例如需要处理多个连续的异步操作、任务取消、并发请求等。它提供了更强大和灵活的异步处理能力,并且通过 Generator 函数的形式使得异步流程易于阅读和维护。但是相对复杂性也较高,需要掌握 Generator 函数和 Saga 相关的代码结构。

总而言之,Redux Thunk 适用于简单的异步操作,学习曲线较低;而 Redux Saga 适用于复杂的异步场景,提供了更强大和灵活的异步处理能力。选择哪个中间件取决于项目的具体需求和开发团队的技术背景。

41.在使用redux过程中,如何防止定义的action-type的常量重复?(2)

在 Redux 中,为了避免定义的 action type 常量重复,可以采用以下几种方式:

  1. 使用字符串常量:定义 action type 时使用字符串常量,在不同的模块或文件中使用不同的命名空间来确保唯一性。
  2. 使用枚举类型:使用 TypeScript 的枚举类型来定义 action type,枚举成员的名称是唯一的
  3. 使用工具库:可以使用一些辅助工具库来帮助管理和生成唯一的 action type,例如 redux-actredux-actions 等。

42.CDN的特点及意义?

CDN(Content Delivery Network,内容分发网络)是一种分布式的网络架构,其目标是通过将内容缓存在离用户较近的边缘节点上,提供更快速、稳定的内容分发服务。CDN的特点和意义如下:

  1. 高效快速:CDN利用分布在全球各地的边缘节点,将内容缓存到离用户最近的节点上,当用户请求内容时,可以从最近的节点获取,减少了传输距离和网络拥堵,提供了更快的响应速度和较低的延迟。
  2. 负载均衡:CDN通过智能路由和负载均衡技术,将用户请求分配到最优的节点,避免了单一服务器的过载,提高了整体的系统吞吐量和可用性。
  3. 提升用户体验:由于CDN提供了更快的响应速度和较低的延迟,用户可以更快速地访问和加载网页、图片、视频等内容,提升了用户的体验感受,降低了用户的等待时间。
  4. 减轻源站压力:通过将静态内容缓存到CDN节点上,可以减轻源站的负载压力,提高源站的可扩展性和稳定性,同时减少了源站与用户之间的直接流量,降低了网络成本。
  5. 抵御网络攻击:CDN可以通过分布式的架构和安全防护机制,提供一定程度的网络攻击防护,如分布式拒绝服务攻击(DDoS)的缓解。
  6. 全球覆盖:CDN网络分布在全球各地的节点,可以实现全球范围内的内容分发,无论用户身处何地,都可以享受到较快速的网络访问。

综上所述,CDN具有高效快速、负载均衡、提升用户体验、减轻源站压力、抵御网络攻击和全球覆盖等特点。在大规模的网络应用中,使用CDN能够显著提升网站性能、可靠性和安全性,为用户提供更好的体验,并降低运营成本。

43.为什么for循环比forEach性能高

在 JavaScript 中,常用的循环有 for 循环和 forEach 循环。虽然两者都可以遍历数组,但它们的实现方式不同,因此性能也有所不同。

for 循环是一种基于索引值(或下标)的循环方式,通过数组的下标索引来访问数组元素。而 forEach 循环则是一种迭代器,对数组中的每个元素都执行一次回调函数。

因此,for 循环相对于 forEach 循环具有以下优势:

  1. for 循环不需要编写额外的函数,可以直接对数组进行操作,因此其执行过程相对更加高效。
  2. 在 for 循环中,可以通过定义一个本地变量(如var len = arr.length)将数组长度缓存起来,避免多次访问 arr.length 属性导致的性能损失。
  3. 在需要对数组进行修改时,for 循环比 forEach 循环更为方便且高效。因为在 for 循环中,可以通过获取数组的下标索引来修改数组元素,而在 forEach 循环中无法直接修改数组元素。

总体而言,在大多数情况下,for 循环比 forEach 循环更具有优势。但是,对于需要进行异步操作的情况,forEach 循环可能更为适用,因为它可以支持 async/await 操作,而 for 循环不支持。

需要注意的是,虽然 for 循环比 forEach 循环更快,但在实际应用中,在性能方面的差别通常不会太大。因此,选择使用哪种循环方式应该根据实际情况而定,以符合代码的可读性、可维护性和执行效率等方面的要求。

44.redux实现原理?

Redux 是一个 JavaScript 状态管理库,它可以用于管理应用程序中的状态。Redux 的实现原理可以简单概括为以下几个步骤:

创建 store:创建一个全局的存储对象作为状态管理的中心。Store 由 Redux 提供,它包含应用程序的当前状态,还包括实现更新状态的 reducer。

创建 reducer:reducer 是纯函数,它接收旧的 state 和 action,返回新的 state。Reducer 的主要作用是根据 action 的类型来更新 state,同时保证 state 的不可变性。

创建 action:action 是一个普通的 JavaScript 对象,它描述了要发生的操作。Action 包含一个 type 属性,表示要执行的行动类型,还可以包含其他数据。

分发 action:通过调用 store.dispatch() 方法将 action 分发到 reducer,这个过程会触发 state 的更新。

更新 state:reducer 根据接收的 action 类型更新 store 中的 state,同时返回一个新的 state。

订阅 state 的变化:可以通过 store.subscribe() 方法监听 state 的变化,在 state 更新时执行相应的操作。

45.React render方法的原理,在什么时候会触发?(2)

render() 方法的原理:

根据组件的状态和属性(props)来生成对应的虚拟 DOM 树。React 使用虚拟 DOM 来表示组件的界面结构,并通过比较更新前后 的虚拟 DOM 树找出差异,然后将差异部分进行高效地更新到真实的 DOM 上,从而实现页面的局部更新

render() 方法会在以下情况下触发:

1.组件首次挂载:当组件第一次被渲染到真实的 DOM 上时,render() 方法会被调用

2.组件的状态或属性发生变化:当组件的状态(通过 setState() 方法更新)或属性发生变化时,React 会自动重新调用 render() 方法,生成新的虚拟 DOM,并进行比较和更新

3.父组件重新渲染:如果父组件的 render() 方法被调用,那么其中包含的子组件的 render() 方法也会被触发

46.谈谈你是如何做移动端适配的?

对于移动端适配,一种常用的方法是响应式布局(Responsive Layout)和媒体查询(Media Queries)。以下是一些常见的移动端适配策略:

  1. 使用Viewport meta标签:在HTML文档的头部添加Viewport meta标签,通过设置width=device-width,可以告诉浏览器使用设备的宽度作为页面的宽度。另外,还可以设置initial-scalemaximum-scaleminimum-scale等属性来控制缩放行为。
  2. 使用相对单位:在样式表中使用相对单位(如百分比、em、rem)来定义元素的尺寸和布局,相对单位可以根据屏幕尺寸进行适配。
  3. 弹性布局:使用弹性盒模型(Flexbox)或网格布局(Grid)等弹性布局方式,使得元素能够根据可用空间自动调整大小和位置。
  4. 媒体查询:利用CSS3中的媒体查询功能,根据不同的屏幕尺寸和设备特性,为不同的屏幕宽度范围应用不同的样式规则。可以根据需要调整字体大小、布局结构、隐藏或显示某些元素等。
  5. 图片适配:通过设置图片的最大宽度为100%来保证图片在不同尺寸的屏幕上自适应。同时,使用高分辨率的图片(如Retina屏幕),以提供更清晰的图像。
  6. 测试和调试:在开发过程中,使用模拟器或真实设备进行测试,并通过调试工具(如Chrome开发者工具)来检查和优化页面在不同屏幕大小下的样式和布局。

综合使用以上方法,可以使网页在不同尺寸的移动设备上呈现出良好的用户体验,适应不同的屏幕大小和设备特性。

当然,对于较为复杂的移动端适配需求,可以考虑使用CSS预处理器(如Sass、Less)或CSS框架(如Bootstrap、Foundation)等工具来简化和优化适配过程。

47.移动端1像素的解决方案?

在移动端开发中,由于不同设备的像素密度差异,1像素问题成为了一个常见的难题。如果我们不对这个问题进行针对性的解决,那么会导致页面显示效果不美观,甚至影响用户体验。

以下是一些解决方案:

  1. 使用css3的scale属性:将要渲染的元素放大一倍,然后通过scale缩小回去。例如,将一个1像素的边框放大到2像素,再通过scale(0.5)恢复原来大小。这种方法可以使边框看起来更加清晰,但是可能会影响元素的布局和性能。
  2. 通过伪元素实现:使用伪元素before或after,并设置其content属性为空,然后通过border设置为1像素粗细的边框。例如:
cssCopy Code.box::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    border: 1px solid #ddd;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    z-index: -1;
}

这种方法可以避免影响元素布局,但是可能会增加HTML代码量和CSS复杂度。
3. 通过JavaScript动态设置viewport缩放比例: 使用JavaScript获取设备物理像素和设备独立像素的比例,然后动态设置viewport的缩放比例,从而实现1像素问题的解决。例如:

javascriptCopy Codevar scale = 1 / window.devicePixelRatio;


### 自学几个月前端,为什么感觉什么都没学到??

----------------------------------------------------------------------------------

这种现象在很多的初学者和自学前端的同学中是比较的常见的。

因为自学走的弯路是比较的多的,会踩很多的坑,学习的过程中是比较的迷茫的。

最重要的是,在学习的过程中,不知道每个部分该学哪些知识点,学到什么程度才算好,学了能做什么。

很多自学的朋友往往都是自己去找资料学习的,资料上有的或许就学到了,资料上没有的或许就没有学到。



这就会给人一个错误的信息就是,我把资料上的学完了,估计也-就差不多的了。

但是真的是这样的吗?非也,因为很多人找的资料就是很基础的。学完了也就是掌握一点基础的东西。分享给你一份前端分析路线,你可以参考。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

![](https://img-blog.csdnimg.cn/img_convert/15be8206a9f6e5bd9e8e930303b613ee.png)



还有很多的同学在学习的过程中一味的追求学的速度,很快速的刷视频,写了后面忘了前面,最后什么都没有学到,什么都知道,但是什么都不懂,要具体说,也说不出个所以然。



所以学习编程一定要注重实践操作,练习敲代码的时间一定要多余看视频的时间。



d #ddd;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    z-index: -1;
}

这种方法可以避免影响元素布局,但是可能会增加HTML代码量和CSS复杂度。
3. 通过JavaScript动态设置viewport缩放比例: 使用JavaScript获取设备物理像素和设备独立像素的比例,然后动态设置viewport的缩放比例,从而实现1像素问题的解决。例如:

javascriptCopy Codevar scale = 1 / window.devicePixelRatio;


### 自学几个月前端,为什么感觉什么都没学到??

----------------------------------------------------------------------------------

这种现象在很多的初学者和自学前端的同学中是比较的常见的。

因为自学走的弯路是比较的多的,会踩很多的坑,学习的过程中是比较的迷茫的。

最重要的是,在学习的过程中,不知道每个部分该学哪些知识点,学到什么程度才算好,学了能做什么。

很多自学的朋友往往都是自己去找资料学习的,资料上有的或许就学到了,资料上没有的或许就没有学到。



这就会给人一个错误的信息就是,我把资料上的学完了,估计也-就差不多的了。

但是真的是这样的吗?非也,因为很多人找的资料就是很基础的。学完了也就是掌握一点基础的东西。分享给你一份前端分析路线,你可以参考。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

![](https://img-blog.csdnimg.cn/img_convert/15be8206a9f6e5bd9e8e930303b613ee.png)



还有很多的同学在学习的过程中一味的追求学的速度,很快速的刷视频,写了后面忘了前面,最后什么都没有学到,什么都知道,但是什么都不懂,要具体说,也说不出个所以然。



所以学习编程一定要注重实践操作,练习敲代码的时间一定要多余看视频的时间。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值