自学内容网 自学内容网

【常考前端面试题总结】---2025

React

fiber架构

1.为什么会出现 React fiber 架构?

React 15 Stack Reconciler 是通过递归更新子组件 。由于递归执行,所以更新一旦开始,中途就无法中断。当层级很深时,递归更新时间超过了 16ms,用户交互就会卡顿。对于特别庞大的DOM树来说,reconciliation过程会很长(x00ms),在这期间,主线程是被 js 占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。网友测试使用React V15,当DOM节点数量达到100000时, 加载页面时间竟然要 7 秒。

总结原因:
(1) 长时间阻塞主线程
(2) 无法分配优先级 高优先级任务(如用户输入)无法抢占低优先级任务(如后台数据更新)。
(3) 缺乏灵活的错误恢复
同步渲染中,如果某一部分出错,整个渲染过程都会中断,无法优雅地处理错误。

2.Fiber是什么?

官方的一句话解释是“React Fiber是对核心算法的一次重新实现” 。我觉得是一种协调。

Fiber架构 = Fiber节点 + Fiber调度算法

1.把一个耗时长的任务分解为一个个的工作单元。 在执行工作单元之前,由浏览器判断是否有空余时间执行,有时间就执行工作单元,执行完成后,继续判断是否还有空闲时间。没有时间就终止执行让浏览器执行其他任务(如 GUI 线程等)。等到下一帧执行时判断是否有空余时间,有时间就从终止的地方继续执行工作单元,一直重复到任务结束。
2.要让终止的任务恢复执行,就必须知道下一工作单元对应那一个。所以要实现工作单元的连接,就要使用链表,在每个工作单元中保存下一个工作单元的指针,就能恢复任务的执行。
3.要知道每一帧的空闲时间,就需要使用 requestIdleCallback Api。传入回调函数,回调函数接收一个参数(剩余时间),如果有剩余时间,那么就执行工作单元,如果时间不足了,则继续requestIdleCallback,等到下一帧继续判断。

总结核心概念
(1)Fiber节点 :Fiber 是 React 中用来表示虚拟 DOM 的一种数据结构。每个 Fiber 节点对应一个 React 元素或组件,Fiber 节点形成一棵链表树,通过 child、sibling 和 return 指针连接起来。
(2)时间切片 :Fiber 允许将渲染任务分成多个小的单元,每次处理一部分任务。如果有更高优先级的任务(如用户输入事件)出现,可以暂停当前任务,先处理高优任务。
(3)** 双缓冲机制** : Current Fiber Tree:表示当前屏幕上渲染的内容。Work-in-progress Fiber Tree:表示正在构建的新 Fiber 树。
(4)优先级调度 :引入了调度器(Scheduler),根据任务的类型和优先级分配资源。高优先级:用户输入(如点击、键盘事件)。中优先级:动画、过渡效果。低优先级:后台数据加载或非关键渲染。

Fiber优点:

  • 可中断更新:长任务被分解成小任务,减少主线程阻塞,提升用户体验。
  • **优先级调度:**任务可以根据优先级被调度和重新安排。
  • **增量渲染:**允许逐步构建UI,避免界面冻结。
  • **增强错误边界:**错误可以被捕获,避免整棵树崩溃。
  • **支持未来特性:**如 Concurrent Mode 和Suspense。

Fiber工作流程?

调度阶段(Scheduling):
当组件的状态或属性发生变化时,React 会通过setState等方式触发更新。把待更新的任务会先放入队列中, 然后通过 requestIdleCallback 请求浏览器调度。
协调阶段(Reconciliation):
现在浏览器有空闲或者超时了就会构建 Fiber 树,workInProgress Fiber树,对比 Fiber 树,通过一种称为 “双缓存” 的技术,将新的workInProgress Fiber树与旧的 Fiber 树进行对比,找出需要更新的节点,这个过程称为 “Diffing”。
渲染阶段(Render):
根据协调阶段对比的结果,生成 DOM 更新操作,移动,更新,删除,这些操作会被记录在一个称为effect list的链表中,批量提交到浏览器的 DOM 中。
提交阶段(Commit):
React 会遍历effect list,按照顺序执行其中的 DOM 更新操作,React 会根据操作的类型调用相应的 DOM API,如createElement、setAttribute、appendChild等。
渲染完成后:React 会根据组件的类型和状态,调用相应的生命周期函数或回调函数。

总的来说:React Fiber 的引入,让 React 从一个“同步渲染引擎”转变为一个“异步可中断渲染引擎”。 增量化、可中断、可恢复

React渲染流程

jsx ---- render function ---- vdom ----- Fiber ------ DOM
在编译阶段,Babel 等工具会将 JSX 代码转换为 React.createElement 函数调用的形式。React.createElement 返回的 React 元素是一个普通的 JavaScript 对象,这些对象构成了虚拟 DOM 树。每个虚拟 DOM 节点遍历创建对应的 Fiber 树。Fiber 树是一种链表结构,每个 Fiber 节点都有指向父、子节点和兄弟节点的指针,方便进行遍历和更新操作。在构建完 Fiber 树后,React 会根据 Fiber 树中的信息,创建实际的 DOM 节点。

对React的理解?

react是一个用于构建用户界面的JS库。 怎么构建界面的呢?fn=react+ui。

  1. 官网强调 “组件是 React 的基石”。通过组件化,将用户界面拆分成一个个独立的、可复用的组件。一个按钮、一个表单、一个导航栏等都可以是一个组件,每个组件都有自己的状态和生命周期,通过组合不同的组件来构建复杂的应用界面,很好维护。
  2. 其次在构建界面的时候,在代码的写法采用声明式编程 风格,只需要描述界面应该呈现的样子,而不需要直接操作 DOM 来进行界面更新。当数据发生变化时,React 会自动根据新的数据重新渲染组件。
  3. 那在这个组件渲染状态变化的过程,React 引入了虚拟 DOM的概念, React 会先在虚拟 DOM 上进行更新操作,然后通过对比新旧虚拟 DOM 的差异,只将实际需要更新的部分高效地应用到真实 DOM 上,从而提高渲染性能。

不建议在if语句内使用 Hook ?

在 React 内部,Hook 是通过链表的数据结构来存储和管理的。当组件首次挂载时,React 会按照 Hook 的调用顺序依次将它们添加到链表中,并为每个 Hook 分配一个唯一的索引。在if语句内使用 Hook,可能会破坏 Hook 的调用顺序, Hook 链表的结构和索引就会与首次渲染时不同。
循环语句 || 函数嵌套 || 异步函数 || 条件渲染组件

声明式编程和命令式编程

声明式编程:更注重描述要做什么,而不是具体怎么做。
命令式编程:则侧重于描述具体的操作步骤和执行顺序,即告诉计算机如何一步一步地完成任务。

// 声明式写法
const DeclarationExample = () => {
  return (
    <div>
      <h1>Hello, World!</h1>
      <p>This is a declarative example in React.</p>
    </div>
  );
};

在上面的例子中,用 JSX 描述了一个简单的 React 组件,它声明了 UI 的结构和内容。React 负责将这个声明传递给虚拟 DOM 并进行渲染,我们不需要直接操作底层的 DOM。

// 命令式写法
const ImperativeExample = () => {
  const container = document.createElement('div');
  const heading = document.createElement('h1');
  
  heading.textContent = 'Hello, World!';

  const paragraph = document.createElement('p');
  paragraph.textContent = 'This is an imperative example in plain JavaScript.';

  container.appendChild(heading);
  container.appendChild(paragraph);

  // 将容器添加到页面中
  document.body.appendChild(container);
};

使用原生 JavaScript 创建了 DOM 元素,并通过命令式的方式逐步操作这些元素的创建和组装过程。这样的写法更注重详细的步骤和控制。
总体而言,声明式的代码更直观和简洁,而命令式的代码更灵活,可以更精细地控制每个步骤。

react中state和constructor的区别

state: 在组件内部存储和管理数据,专门用于存储组件的可变数据,这些数据通常与组件的用户交互、业务逻辑等相关。例如,一个输入框组件的输入值、一个列表组件的列表数据等都可以存储在state中。可以根据需要多次调用。
**constructor:**本身并不直接存储数据,而是用于初始化组件所需的数据和操作。如绑定this指针等,但它不会直接存储组件的业务数据。在组件实例化时只被调用一次,用于进行组件的初始化操作。之后,无论组件的状态如何变化,constructor都不会再次被调用,除非重新创建组件实例。

函数组件和类组件的区别

  1. 定义方式:
    类组件: 使用 ES6 的class定义,需要继承React.Component或React.PureComponent,并且必须用render方法来返回组件的 UI 结构。
    函数组件: 是一个Js函数,接收props作为参数,返回一个 React 元素来描述组件的 UI 结构。
  2. 状态管理:
    类组件: 通过this.state来存储和管理组件的内部状态。具有丰富的生命周期方法。
    函数组件: 在hooks出来之前函数组件是无状态的,被称为 “纯函数组件”。在没有 Hooks 之前,函数组件不存在生命周期的概念。引入 Hooks 后,可以使用useEffect等钩子函数来模拟类组件的部分生命周期行为。
  3. 使用场景
    类组件: 在处理复杂表单时,类组件的状态管理和生命周期方法能更好地处理input表单数据的收集、验证和提交。 包括我的组件库搭建时候弹窗组件,动画与过渡效果:需要在组件挂载、更新或卸载时添加动画或过渡效果,类组件的生命周期方法提供了更精细的控制。
    函数组件: 实时数据展示与交互:如实时聊天应用中的消息列表组件,使用函数组件结合useEffect和useState可以方便地监听消息数据的变化并实时更新界面。

受控组件和非受控组件 以及类组件和函数组件的关系?

在 React 中,受控组件和非受控组件是从组件数据处理方式 角度进行的分类,而类组件和函数组件则是从组件的定义形式 角度进行的划分。
受控组件: 其值受到 React 状态的控制,通常通过onChange等事件来更新状态,并且表单元素的值由状态来决定。
非受控组件: 其值不受 React 状态控制,而是由 DOM 本身来管理。通常通过ref来获取 DOM 元素的值,在需要时进行操作。例如,一个只有一个输入框的登录表单,使用非受控组件可以快速完成功能开发。

React事件机制

事件处理流程

  1. 事件触发:当用户在浏览器中执行某个操作,如点击按钮、输入文本等,浏览器会触发相应的原生事件。
  2. 事件委托:React 采用了事件委托的机制,将所有的事件都绑定在组件的根元素上。当事件发生时,通过事件冒泡机制,事件会从触发的目标元素向上冒泡到组件的根元素。
  3. 合成事件生成:在事件冒泡到根元素的过程中,React 会根据原生事件创建相应的合成事件对象,并将其传递给事件处理函数。
  4. 事件处理函数执行:React 会调用绑定在元素上的事件处理函数,并将合成事件对象作为参数传递给它。在事件处理函数中,开发者可以根据需要进行相应的操作,如更新状态、调用其他函数等。

react所有事件都挂在documnet对象上,先触发真实dom事件,再触发react事件,最后执行document上挂载的事件。


Webpack

核心概念:module、chunk 和 bundle,module 是任何通过 import 或者 require 导入的代码,包括 js、css、图片资源等;多个 module 可以组成一个 chunk,每个 chunk 经过处理后会输出一个 bundle 文件。

webpack解决了什么?

背景: 更高效地管理和维护项目中的每一个资源,通过文件划分的形式实现模块化,但是模块都是在全局中工作,大量模块成员污染了环境,模块与模块之间并没有依赖关系、维护困难、没有私有空间等问题。就出现了命名空间方式,规定每个模块只暴露一个全局对象,window.moduleA这种方式也并没有解决第一种方式的依赖等问题。再后来,我们使用立即执行函数为模块提供私有空间,通过参数的形式作为依赖声明。但是仍然存在一些没有解决的问题。我们是用过script标签在页面引入这些模块的,这些模块的加载并不受代码的控制,时间一久维护起来也十分的麻烦。

理想的解决方式是,在页面中引入一个JS入口文件,其余用到的模块可以通过代码控制,按需加载进来。除了模块加载的问题以外,还需要规定模块化的规范,如今流行的则是CommonJS、ES Modules。

需求:
1.监听文件的变化来并且反映到浏览器上,提高开发的效率
2.使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码
3.开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化

解决了解决模块依赖复杂的问题,资源整合与优化问题(代码压缩、文件分割),开发流程优化问题(热更新、自动化构建过程)、跨平台环境兼容性的问题(适配不同浏览器、支持多种环境)

webpack配置用过哪些?

一般在webpack.config.js的文件里面配置;

entry和output

  1. 单入口单出口,一般用于单页应用,最终只会产生一个 chunk。
  2. 多入口单出口。entry: ['./src/index1.js','./src/index2.js'], entry作为数组。
  3. 多入口多出口。 entry改为对象,key表示打包后输出文件的名字。
  4. output: filename:xxxx,path:reslove(__dirname,'build')

配置模式mode

默认production | development

JS语法检查

eslint-loader: 在eslintConfig配置项中设置 ,

  1. “rules”: { // eslint检查的规则 0 忽略 1 警告 2 错误
    “no-console”: 0, // 不检查console
    “eqeqeq”: 2, // 用而不用=就报错
    “no-alert”: 2 // 不能使用alert
    },
  2. “env”: { // 设置环境
    “browser”: true, // 支持浏览器环境: 能够使用window上的全局变量
    “node”: true // 支持服务器环境: 能够使用node上global的全局变量
    },

JS语法转换

babel-loader 将浏览器不能识别的新语法转换成原来识别的旧语法,做浏览器兼容性处理

js兼容性处理

  1. 使用经典的polyfill @babel/polyfill
    优点: 解决babel只能转换部分低级语法的问题(如:let/const/解构赋值…),引入polyfill可以转换高级语法(如:Promise…)
    2.core-js

打包样式文件中的图片资源

图片文件webpack不能解析,需要借助loader编译解。析添加2张图片:• 小图, 小于8kb • 大图, 大于8kb • 在less文件中通过背景图的方式引入图片
file-loader url-loader

  {
    test: /\.(png|jpg|gif)$/,
    use: {
      loader: 'url-loader',
      options: {
        limit: 8192, // 8kb --> 8kb以下的图片会base64处理
        outputPath: 'images', // 决定文件本地输出路径
        publicPath: '../build/images',  // 决定图片的url路径
        name: '[hash:8].[ext]' // 修改文件名称 [hash:8] hash值取8位  [ext] 文件扩展名
      }
    }
  },

打包HTML文件

html文件webpack不能解析,需要借助插件编译解析 html-webpack-plugin

打包html中图片资源

html中的图片url-loader没法处理,它只能处理js中引入的图片 / 样式中图片,不能处理html中img标签,需要引入其他html-loader处理。

webpack构建流程?

啊
初始化-编译构建-输出

1.初始化参数。获取我们在webpack.config.js文件配置的参数
2.然后开始编译,初始化一个叫compiler的对象,还有各种plugins插件,这个时候插件会开始监听webpack构建过程中的事件。
3.然后webpack要确定入口,一般是从entry开始,开始解析文件构建ast语法树,找抽依赖,递归下去。
4.这个递归的过程,根据文件类型和loader配置,调用相应的loader对不同的文件做转换处理,在找出该模块依赖的模块,递归本操作。
5.递归结束,编译后的 Module 组合成 Chunk,根据entry以及output等配置生成代码块chunk,输出到文件系统

Loader和Plugin的区别?

loader,它是一个转换器,将A文件进行编译成B文件,比如:将A.less转换为A.css,单纯的文件转换过程。

plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务。插件的范围包括,打包优化,代码压缩,甚至可以重新定义环境中的变量。

loader针对代码或资源,plugins针对工程。

官方文档说:相对于loader转换指定类型的模块功能,plugins能够被用于执行更广泛的任务比如打包优化、文件管理、环境注入等……

手写一个loader?

假设我们要写一个 loader,它的功能是将输入的文本内容里的所有单词首字母都转换为大写形式。

  1. 创建 loader 文件:uppercase-loader.js
module.exports = function (source) {
    const words = source.split(' ');
    const transformedWords = words.map(word => {
        return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
    });
    return transformedWords.join(' ') + '\n';
};
  1. 使用 loader:要在 Webpack 项目中使用这个 loader,需要在 Webpack 配置文件(通常是 webpack.config.js)中进行配置
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.txt$/, // 假设我们要处理的是.txt文件内容
                use: [
                    path.resolve(__dirname, 'uppercase-loader.js') // 使用我们自定义的loader
                ]
            }
        ]
    }
};

通过 module.rules 数组来定义模块的处理规则。test 属性指定了要应用该规则的文件类型,use 配置中指定了要使用的 loader这样,当 Webpack 打包项目时,如果遇到 .txt 文件,就会使用我们自定义的 uppercase-loader 来处理文件内容,将其中的单词首字母都转换为大写形式。

JS

null和undefined区别

1.含义上来说null表示空对象,undefined是未定义
2.undefined在js中不是一个保留字,null是

let null = 1; // 语法错误
function undefined() {} // 语法错误
console.log(typeof null); // “object” 历史问题 对象类型的类型标记位为 000,而 null 在底层表示上全是 0,
console.log(typeof undefined); // “undefined”
console.log(null == undefined); // true
console.log(null === undefined); // false

4.使用场景:
nudefined 变量声明但未赋值时,访问对象不存在的属性时,函数没有显式返回值时。 null将某个对象赋值为空,以便垃圾回收器进行回收。

JS编写的规范

  • 代码中出现地址、时间等字符串时需要使用常量代替。
  • 在进行比较的时候吧,尽量使用’=', '!‘代替’==', ‘!=’
  • 不要在内置对象的原型上添加方法,如 Array, Date。
  • switch 语句必须带有 default 分支。
  • for 循环必须使用大括号。
  • if 语句必须使用大括号。

原型链

在这里插入图片描述
原型对象:每个函数在创建的时候,都会生成一个属性prototype,这个prototype指向一个对象,这个对象就是函数的原型对象。比如说 function Person() {} Person.prototype = person.__proto; 你把这个person new成实例对象之后,每个实例对象都有一个 proto 属性(又叫隐式原型属性),指向它构造函数的原型对象。原型链又叫隐式原型链,原型链的作用是用来查找对象的属性的,如果一个属性A.name在当前的对象上没有找到就会沿着原型链向上查找,直到找到这个属性或者到达原型链的顶端。Object.prototype.__proto__的值是null;
Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
Object.prototype.__proto__设置为null,就表示原型链在这里结束了,再往上就没有其他对象可查找了,这符合原型链的设计逻辑。如果Object.prototype的__proto__指向了其他非null对象,那么在进行原型链查找时,就会继续沿着这个指向去查找,可能会导致无限循环或者不必要的查找开销。


原文地址:https://blog.csdn.net/weixin_45817780/article/details/144394388

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!