Tiny'Wo | 小窝

网络中的一小块自留地

面试官:说说你对操作系统的理解?核心概念有哪些?

一、是什�?

操作系统(Operating System,缩写:OS)是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序,同时也是计算机系统的内核与基�?
简单来讲,操作系统就是一种复杂的软件,相当于软件管家

操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务,

操作系统的类型非常多样,不同机器安装的操作系统可从简单到复杂,可从移动电话的嵌入式系统到超级电脑的大型操作系统,在计算机与用户之间起接口的作用,如下图:

许多操作系统制造者对它涵盖范畴的定义也不尽一致,例如有些操作系统集成了图形用户界面,而有些仅使用命令行界面,将图形用户界面视为一种非必要的应用程�?

二、核心概�?

操作系统的核心概念都是对具体物理硬件的抽象,主要有如下:

  • 进程(线程):进程(线程)是操作系统对CPU的抽�?- 虚拟内存(地址空间):虚拟内存是操作系统对物理内存的抽�?- 文件:文件是操作系统对物理磁盘的抽象

  • shell:它是一个程序,可从键盘获取命令并将其提供给操作系统以执行�?- GUI :是一种用户界面,允许用户通过图形图标和音频指示符与电子设备进行交�?- 计算机架�?computer architecture)�?在计算机工程中,计算机体系结构是描述计算机系统功能,组织和实现的一组规则和方法。它主要包括指令集、内存管理、I/O 和总线结构

  • 多处理系�?Computer multitasking):是指计算机同时运行多个程序的能�?- 程序计数�?Program counter):程序计数器 是一�?CPU 中的寄存器,用于指示计算机在其程序序列中的位�?- 多线�?multithreading):是指从软件或者硬件上实现多个线程并发执行的技�?

  • CPU 核心(core):它�?CPU 的大脑,它接收指令,并执行计算或运算以满足这些指令。一�?CPU 可以有多个内�?- 图形处理�?Graphics Processing Unit):又称显示核心、视觉处理器、显示芯片或绘图芯片

  • 缓存命中(cache hit):当应用程序或软件请求数据时,会首先发生缓存命中

  • RAM((Random Access Memory):随机存取存储器,也叫主存,是与 CPU 直接交换数据的内部存储器

  • ROM (Read Only Memory):只读存储器是一种半导体存储器,其特性是一旦存储数据就无法改变或删�?

  • 虚拟地址(virtual memory)�?虚拟内存是计算机系统内存管理的一种机�?

  • 驱动程序(device driver):设备驱动程序,简称驱动程序(driver),是一个允许高级别电脑软件与硬件交互的程序

  • USB(Universal Serial Bus):是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规�?

  • 地址空间(address space):地址空间是内存中可供程序或进程使用的有效地址�?

  • 进程间通信(interprocess communication)�?指至少两个进程或线程间传送数据或信号的一些技术或方法

  • 目录(directory)�?在计算机或相关设备中,一个目录或文件夹就是一个装有数字文件系统的虚拟容器

  • 路径(path name)�?路径是一种电脑文件或目录的名称的通用表现形式,它指向文件系统上的一个唯一位置�?- 根目�?root directory):根目录指的就是计算机系统中的顶层目录,比如 Windows 中的 C 盘和 D 盘,Linux 中的 /

  • 工作目录(Working directory):它是一个计算机用语。用户在操作系统内所在的目录,用户可在此目录之下,用相对文件名访问文件�?- 文件描述�?file descriptor)�?文件描述符是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念

  • 客户�?clients):客户端是访问服务器提供的服务的计算机硬件或软件�?- 服务�?servers)�?在计算中,服务器是为其他程序或设备提供功能的计算机程序或设备

三、总结

  • 操作系统是管理计算机硬件与软件资源的程序,是计算机的基石
  • 操作系统本质上是一个运行在计算机上的软件程�?,用于管理计算机硬件和软件资�?- 操作系统存在屏蔽了硬件层的复杂性�?操作系统就像是硬件使用的负责人,统筹着各种相关事项
  • 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理�?内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定�?

参考文�?

面试官:说说你对输入输出重定向和管道的理解?应用场景�?

一、是什�?

linux中有三种标准输入输出,分别是STDINSTDOUTSTDERR,对应的数字�?�?�?�?

  • STDIN 是标准输入,默认从键盘读取信�?- STDOUT 是标准输出,默认将输出结果输出至终端
  • STDERR 是标准错误,默认将输出结果输出至终端

对于任何linux命令的执行会有下面的过程�?

一条命令的执行需要键盘等的标准输入,命令的执行和正确或错误,其中的每一个双向箭头就是一个通道,所以数据流可以流入到文件端�?*重定向或管道**�?
简单来讲,重定向就是把本来要显示在终端的命令结果,输送到别的地方,分成:

  • 输入重定向:流出到屏幕如果命令所需的输入不是来自键盘,而是来自指定的文�?- 输出重定向:命令的输出可以不显示在屏幕,而是写在指定的文件中

管道就是把两个命令连接起来使用,一个命令的输出作为另一个命令的输入

两者的区别在于�?

  • 管道触发两个子进程,执行 | 两边的程序;而重定向是在一个进程内执行�?- 管道两边都是shell命令
  • 重定向符号的右边只能是Linux文件
  • 重定向符号的优先级大于管�?

二、命�?

重定向常见的命令符号有:

  • > �?输出重定向到一个文件或设备 覆盖原来的文�?

    如果该文件不存在,则新建一个文�?>
    如果该文件已经存在,会把文件内容覆盖

    这些操纵不会征用用户的确�?

  • >> :输出重定向到一个文件或设备,但是是 追加原来的文件的末尾
  • < :用于制定命令的输入
  • << :从键盘的输入重定向为某个命令的输入

以逐行输入的模式(回车键进行换行)

所有输入的行都将在输入结束字符串之后发送给命令

  • 2> 将一个标准错误输出重定向到一个文件或设备,会覆盖原来的文�?- 2>> 将一个标准错误输出重定向到一个文件或设备,是追加到原来的文件
  • 2>&1:组合符号,将标准错误输出重定向到标准输出相同的地方

1就是代表标准输出

  • >& 将一个标准错误输出重定向到一个文件或设备覆盖原来的文�?- |& 将一个标准错误管道输出到另一个命令作为输�?

三、应用场�?

将当前目录的文件输出重定向到1.txt文件中,并且会清空原有的1.txt的内�?

1
ls -a > 1.txt

或者以追加的形式,重定向输入到1.txt�?

1
ls -a >> 1.txt

将标准错误输出到某个文件,可以如下:

1
2
3
4
5
6
7
8
9
10
11
$ touch 2> 2.txt
$ cat 2.txt
touch: 缺少了文件操作数
请尝试执�?"touch --help" 来获取更多信息�?```

通过组合符号将两者结合一起,无论进程输出的信息是正确还是错误的信息,都会重定向到指定的文件里

```cmd
[root@linguanghui home]# abc &> file.txt
[root@linguanghui home]# cat file.txt
-bash: abc: command not found

再者通过管道查询文件内容是否包含想要的信息:

1
cat test.txt | grep -n 'xxx'

上述cat test.txt会将test.txt的内容作为标准输出,然后利用管道,将其作为grep -n 'xxx'命令的标准输入�?

参考文�?

面试官:说说你对 shell 的理解?常见的命令?

一、是什�?

Shell 是一个由c语言编写的应用程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言

它连接了用户和Linux内核,让用户能够更加高效、安全、低成本地使�?Linux 内核

其本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、微信等其它软件没有什么区别,特殊的地方就是开机立马启动,并呈现在用户面前

主要作用是接收用户输入的命令,并对命令进行处理,处理完毕后再将结果反馈给用户,比如输出到显示器、写入到文件等,同样能够调用和组织其他的应用程序,相当于一个领导者的身份,如下图�?

那么shell脚本就是多个 Shell 命令的组合并通过 if 条件分支控制或循环来组合运算,实现一些复杂功能,文件后缀名为.sh

常用�?ls 命令,它本身也是一�?Shell 脚本,通过执行这个 Shell 脚本可以列举当前目录下的文件列表,如下创建一个hello.sh脚本

1
2
3
4
#!/bin/bash

# 执行的命令主�?ls
echo "hello world"
  • #!/bin/bash :指定脚本要使用�?Shell 类型�?Bash

  • ls、echo�?脚本文件的内容,表明我们执行 hello.sh 脚本时会列举出当前目录的文件列表并且会向控制台打�?`hello world

执行方式为.hello.zsh

二、种�?

Linux �?Shell 种类众多,只要能给用户提供命令行环境的程序,常见的有�?

  • Bourne Shell(sh),是目前所�?Shell 的祖先,被安装在几乎所有发源于 Unix 的操作系统上

  • Bourne Again shell(bash�?,是 sh 的一个进阶版本,�?sh 更优秀�?bash 是目前大多数 Linux 发行版以�?macOS 操作系统的默�?Shell

  • C Shell(csh�?,它的语法类�?C 语言

  • TENEX C Shell(tcsh�?,它�?csh 的优化版�?

  • Korn shell(ksh�?,一般在收费�?Unix 版本上比较多�?

  • Z Shell(zsh�?,它是一种比较新近的 Shell ,集 bash �?ksh �?tcsh 各家之大�?

关于 Shell 的几个常见命令:

  • ls:查看文�?- cd:切换工作目�?- pwd:显示用户当前目�?- mkdir:创建目�?- cp:拷�?- rm:删�?- mv:移�?- du:显示目录所占用的磁盘空�?

三、命�?

Shell 并不是简单的堆砌命令,我们还可以�?Shell 中编程,这和使用 C++C#JavaPython 等常见的编程语言并没有什么两样�?
Shell 虽然没有 C++、Java、Python 等强大,但也支持了基本的编程元素,例如:

  • if…else 选择结构,case…in 开关语句,for、while、until 循环�?- 变量、数组、字符串、注释、加减乘除、逻辑运算等概念;
  • 函数,包括用户自定义的函数和内置函数(例�?printf、export、eval 等)

下面以bash为例简单了解一下shell的基本使�?

变量

Bash 没有数据类型的概念,所有的变量值都是字符串,可以保存一个数字、一个字符、一个字符串等等

同时无需提前声明变量,给变量赋值会直接创建变量

访问变量的语法形式为:${var} �?$var �?
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号�?

1
2
3
word="hello"
echo ${word}
# Output: hello

条件控制

跟其它程序设计语言一样,Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]里的表达�?
跟其他语言一样,使用if...else进行表达,如果中括号里的表达式为真,那么thenfi之间的代码会被执行,如果则elsefi之间的代码会被执�?

1
2
3
4
5
6
if [[ 2 -ne 1 ]]; then
echo "true"
else
echo "false"
fi
# Output: true

fi标志着条件代码块的结束

函数

bash 函数定义语法如下�?

1
2
3
4
[ function ] funname [()] {
action;
[return int;]
}
  • 函数定义时,function 关键字可有可�?- 函数返回�?- return 返回函数返回值,返回值类型只能为整数�?-255)。如果不�?return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回�?- 函数返回值在调用该函数后通过 $? 来获�?- 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可

参考文�?

面试官:说说什么是进程?什么是线程?区别?

一、进�?

操作系统中最核心的概念就是进程,进程是对正在运行中的程序的一个抽象,是系统进行资源分配和调度的基本单�?
操作系统的其他所有内容都是围绕着进程展开的,负责执行这些任务的是CPU

进程是一种抽象的概念,从来没有统一的标准定义看,一般由程序、数据集合和进程控制块三部分组成�?

  • 程序用于描述进程要完成的功能,是控制进程执行的指令集
  • 数据集合是程序在执行时所需要的数据和工作区
  • 程序控制块,包含进程的描述信息和控制信息,是进程存在的唯一标志

二、线�?

线程(thread)是操作系统能够进行运算调度的最小单位,其是进程中的一个执行任务(控制单元),负责当前进程中程序的执行

一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存,线程之间可以共享对象、资源,如果有冲突或需要协同,还可以随时沟通以解决冲突或保持同�?
举个例子,假设你经营着一家物业管理公司。最初,业务量很小,事事都需要你亲力亲为。给老张家修完暖气管道,立马再去老李家换电灯泡——这叫单线程,所有的工作都得顺序执行

后来业务拓展了,你雇佣了几个工人,这样,你的物业公司就可以同时为多户人家提供服务了——这叫多线程,你是主线程

但实际上,并不是线程越多,进程的工作效率越高,这是因为在一个进程内,不管你创建了多少线程,它们总是被限定在一颗CPU内,或者多核CPU的一个核�?
这意味着,多线程在宏观上是并行的,在微观上则是分时切换串行的,多线程编程无法充分发挥多核计算资源的优�?
这导致使用多线程做任务并行处理时,线程数量超过一定数值后,线程越多速度反倒越慢的原因

三、区�?

  • 本质区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

  • 在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销�?

  • **所处环�?*:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

  • 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资�?

  • 包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程

举个例子:进�?火车,线�?车厢

  • 线程在进程下行进(单纯的车厢无法运行�?- 一个进程可以包含多个线程(一辆火车可以有多个车厢�?- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘�?- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)

参考文�?

面试官:说说 linux 系统�?文本编辑常用的命令有哪些�?

一、是什�?

Vim是从 vi 发展出来的一个文本编辑器,代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用�?
简单的来说�?vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地�?
vim 可以说是程序开发者的一项很好用的工�?

二、使�?

基本�?vi/vim 共分为三种模式,分别是:

  • 命令模式(Command mode�?- 输入模式(Insert mode�?- 底线命令模式(Last line mode�?

命令模式

Vim 的默认模式,在这个模式下,你不能输入文本,但是可以让我们在文本间移动,删除一行文本,复制黏贴文本,跳转到指定行,撤销操作,等�?

移动光标

常用的命令如下:

  • h 向左移动一个字�?- j 向下移动一个字�?- k 向上移动一个字�?- i 向右移动一个字�?
    或者使用方向键进行控制

如果想要向下移动n行,可通过使用 “nj” �?”n�? 的组合按�?

搜索

常见的命令如下:

  • /word:向光标之下寻找一个名称为 word 的字�?
  • ?word:向光标之上寻找一个字符串名称�?word 的字符串
  • n:代表重复前一个搜寻的动作,即再次执行上一次的操作
  • N:反向进行前一个搜索动�?

删除、复制、粘�?

常用的命令如下:

  • x:向后删除一个字�?- X:向前删除一个字�?- nc:n 为数字,连续向后删除 n 个字�?- dd:删除游标所在的那一整行
  • d0:删除游标所在处,到该行的最前面一个字�?- d$删除游标所在处,到该行的最后一个字�?- ndd:除光标所在的向下 n �?- yy:复制游标所在的那一�?- y0:复制光标所在的那个字符到该行行首的所有数�?- y$:复制光标所在的那个字符到该行行尾的所有数�?- p:已复制的数据在光标下一行贴�?- P:已复制的数据在光标上一行贴�?- nc:重复删除n行数�?

输入模式

命令模式通过输入大小写iao可以切换到输入模式,如下�?

  • i:从目前光标所在处输入
  • I:在目前所在行的第一个非空格符处开始输�?- a:从目前光标所在的下一个字符处开始输�?- A:从光标所在行的最后一个字符处开始输�?- o:在目前光标所在的下一行处输入新的一�?- O:目前光标所在的上一行处输入新的一�?
    输入模式我们熟悉的文本编辑器的模式,就是可以输入任何你想输入的内�?
    如果想从插入模式回到命令模式,使用按下键盘左上角的ESC�?

底线命令模式

这个模式下可以运行一些命令例如“退出”,“保存”,等动作,为了进入底线命令模式,首先要进入命令模式,再按下冒号键:

常见的命令如下:

  • w:将编辑的数据写入硬盘档案中
  • w!:若文件属性为『只读』时,强制写入该档案
  • q:未修改,直接退�?- q!:修改过但不存储
  • wq:储存后离开

参考文�?

鸿蒙应用开�?

鸿蒙中地图功能如何实现,申请流程是什么样�?

::: details

  1. 主要通过 集成 Map Kit 的功能来实现
  2. Map Kit 功能很强大,比如�? 1. 创建地图:呈现内容包括建筑、道路、水系等�? 2. 地图交互:控制地图的交互手势和交互按钮�? 3. 在地图上绘制:添加位置标记、覆盖物以及各种形状等�? 4. 位置搜索:多种查�?Poi 信息的能力�? 5. 路径规划:提供驾车、步行、骑行路径规划能力�? 6. 静态图:获取一张地图图片�? 7. 地图 Picker:提供地点详情展示控件、地点选取控件、区划选择控件�? 8. 通过 Petal 地图应用实现导航等能力:查看位置详情、查看路径规划、发起导航、发起内容搜索�? 9. 地图计算工具:华为地图涉及的 2 种坐标系及其使用区域和转�?3. 在编码之前需�? 1. 完成证书的申请和公钥指纹的一些配�? 2. 还要�?AGC 平台上开通地图服务应�? 3. 代码中使�?项目�?client_id
    1. 最后开始编�?
      :::

一多开发是如何实现�?

::: details

  1. 一多开发是一次开发多端部�?2. 主要分成三个核心部分
    1. 工程级一�? 2. 界面级一�? 3. 能力级一�?3. 工程级一多主要指的是使用华为鸿蒙推荐的三层架构来搭建项目,比�? 1. 第一层,最底层�?common-公共能力层,用于存放公共基础能力集合(如工具库、公共配置等),一般是使用 HSP �?动态共享包),这样它被项目中多个模块引入的话,也只会保留一个备份�? 2. 第二层,�?features-基础特性层,用于存放基础特性集合(如应用中相对独立的各个功能的 UI 及业务逻辑实现等)
    2. 顶层是,products-产品定制层,用于针对不同设备形态进行功能和特性集�?4. 界面级一多指的是一套代码可以适配不同尺寸、形态的设备,主要通过以下这些技术来实现
    3. 自适应布局 等比拉伸缩放等等相关技�? 2. 响应式布局 通过断点、媒体查询、栅格布局来实�?5. 能力级一多主要指的是不同硬件设备支持能力不一样,如蓝牙、摄像头、传感器等等。这些主要通过判断当前设置是否支持该能力来决定是否调用相关�?api 功能。如利用编辑器工具的智能提示、和代码中使用的 caniuse 或�?try-catch 进行判断使用�?
      :::

三层架构

::: details

  1. 第一层,最底层�?common-公共能力层,用于存放公共基础能力集合(如工具库、公共配置等),一般是使用 HSP �?动态共享包),这样它被项目中多个模块引入的话,也只会保留一个备份�?2. 第二层,�?features-基础特性层,用于存放基础特性集合(如应用中相对独立的各个功能的 UI 及业务逻辑实现等)
  2. 顶层是,products-产品定制层,用于针对不同设备形态进行功能和特性集�?
    :::

录音有做过吗?avrecoder 有几种状态?

::: details

录音可以通过 AVRecorder �?AudioCapturer 来实现。两者区别主要在支持录制声音的格式不同和控制录音文件的细小粒度不同上。AVRecorder 会简单一些,AudioCapturer 会复杂一�?还可以搭�?ai 语音功能使用

AVRecorder 主要有以下这些状态:

类型说明’idle’闲置状态�?prepared’参数设置完成’started’正在录制�?paused’录制暂停�?stopped’录制停止�?released’录制资源释放�?error’错误状态�?
image-20250326151004432

:::

AVRecord 的录音步�?

::: details

  1. 创建 AVRecorder 实例,实例创建完成进�?idle 状态�?2. 设置业务需要的监听事件,监听状态变化及错误上报�?3. 配置音频录制参数,调�?prepare()接口,此时进�?prepared 状态�?4. 开始录制,调用 start()接口,此时进�?started 状态�?
    :::

图片上传有做过吗?图片处理,旋转、缩放、图片保存有做过吗?

::: details

做过相册图片的上传(如果是沙箱内的图片只需�?1 个步骤即可,直接上传),流程主要�?3 个步骤,基于 photoAccessHelper 、CoreFileKit、NetworkKit 来实现的

  1. photoAccessHelper 用来实现选择要上传的相册的图�?2. CoreFileKit 将相册图片拷贝到沙箱目录
  2. NetworkKit 负责将沙箱目录内的图片上传到服务器上

图片处理,旋转、缩放、图片保存主要基�?Image Kit 来实现。它提供�?

  • 图片解码
  • 指将所支持格式的存档图片解码成统一�?PixelMap,以便在应用或系统中进行图片显示或图片处理�?- PixelMap
  • 指图片解码后无压缩的位图,用于图片显示或图片处理�?- 图片处理
  • 指对 PixelMap 进行相关的操作,如旋转、缩放、设置透明度、获取图片信息、读写像素数据等�?- 图片编码
  • 指将 PixelMap 编码成不同格式的存档图片,用于后续处理,如保存、传输等�?
    其中压缩图片是通过 一�?ImageKit �?packing 函数,传入压缩比�?0-100)来是实现的。值越小体积越�?
    :::

视频有做过吗�?

::: details

  1. 如果是普通的视频播放直接使用 Video 组件来播放即可。功能相对弱一�?2. 如果是对视频播放进行神帝的一些处理,如流媒体、本地资源解析、媒体资源解封装、视频解码和自定义渲染的这些功能,可以使�?AVPlayer 来实现�?3. 如果类似做一个编辑视频的软件,那么就需要使用到对应�?CAPI 接口来实现了(调用底层 c++的能�?

:::

同事发给你代码,你怎么知道它的 bundlename

::: details

一般直接看 AppScope 中的字段就行

:::

鸿蒙如何和网页端通信�?

::: details

  1. 如果是应用的话,使用 web 组件和对应的 controller 的一些接口,�?runJavaScript()registerJavaScriptProxy
  2. 如果是元服务的话,使�?AtomicServiceWeb 来实现,因为 2025 �?1 �?22 日后不支持使�?web。还�?AtomicServiceWeb 没有�?web 中的�?runJavaScript()registerJavaScriptProxy接口,但是它一样可以通过页面�?url 进行参数的传递和鸿蒙端提供了 js sdk,也可以很方便的�?h5 端调用鸿蒙端的功�?
    :::

跨域是怎么处理的?

::: details

跨域存在于不同源的浏览器和服务器的网络通信中,因为鸿蒙端嵌套了 web 组件,理解成就是一个浏览器,因此也会存在跨�?
为了提高安全性,ArkWeb 内核不允�?file 协议或�?resource 协议访问 URL 上下文中来自跨域的请求。因此,在使�?Web 组件加载本地离线

资源的时候,Web 组件会拦�?file 协议�?resource 协议的跨域访问�?
主要有两种解决方�?

  1. 将本地资源替换成网络资源,也就是 file 协议访问的是本地的资源,我们将本地资源放在网络上,通过 http 请求的方式来加载,然后在后端设置 cors 跨域即可。同时,开发者需利用 Web 组件的onInterceptRequest方法,对本地资源进行拦截和相应的替换
  2. 通过setPathAllowingUniversalAccess 白名单设置一个路径列表。当使用 file 协议访问该列表中的资源时,允许进行跨域访问本地文�?
    :::

录音过程中息屏怎么处理�?

::: details

可以通过申请长时任务,实现在后台长时间运行。长时任务支持的类型,包含数据传输、音视频播放、录制、定位导航、蓝牙相关、多设备互联、WLAN 相关、音视频通话和计算任�?
开发步骤如下:

  1. 需要申�?ohos.permission.KEEP_BACKGROUND_RUNNING 权限
  2. 声明后台模式类型(录音等�?3. 通过 @ohos.resourceschedule.backgroundTaskManager 和@ohos.app.ability.wantAgent 进行编码处理

:::

有做过华为支付吗�?

::: details

需要企业资质、需要在 AGC 平台上开通服务�?
image-20250326120252517

  1. 商户客户端请求商户服务器创建商品订单�?2. 商户服务器按照商户模型调�?Payment Kit 服务端直连商户预下单平台类商�?服务商预下单接口�?3. 华为支付服务端返回预支付 ID(prepayId)�?4. 商户服务端组建订单信息参数orderStr返回给商户客户端�?5. 商户客户端调用requestPayment接口调起 Payment Kit 支付收银台�?6. Payment Kit 客户端展示收银台�?7. 用户通过收银台完成支付,Payment Kit 客户端会收到支付结果信息并请�?Payment Kit 服务端处理支付�?8. Payment Kit 服务端成功受理支付订单并异步处理支付�?9. Payment Kit 服务端将支付结果返回�?Payment Kit 客户端�?10. Payment Kit 客户端展示支付结果页�?11. 用户关闭支付结果页后 Payment Kit 客户端会返回支付状态给商户客户端�?12. 支付处理完成后,Payment Kit 服务端会调用回调接口返回支付结果信息给商户服务端�?13. 商户服务端收到支付结果回调响应后,使用SM2 验签方式对支付结果进行验签�?
    :::

说一下多线程

::: details

参考

img

:::

HarmonyOS 中的生命周期

::: details

页面生命周期

  1. onpageshow:页面每次显示时触发,包括路由过程、应用进入前台等场景。例如,用户从后台切换应用到前台,或者通过路由跳转到该页面时,此方法会被调�?2. onpagehide:页面每次隐藏时触发,包括路由过程、应用进入后台等场景。比如用户按下主页键将应用切换到后台,或者通过路由跳转到其他页面时,该页面�?onpagehide 方法会被执行

  2. onbackpress:当用户点击返回按钮时触发。如果返回值为 true,表示页面自己处理返回逻辑,不进行页面路由;返�?false 则表示使用默认的路由返回逻辑,不设置返回值时按照 false 处理

  3. abouttoappear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行�?build () 函数之前执行。在该函数中可以修改变量,更改将在后续执�?build () 函数中生�?5. abouttodisappear:在自定义组件析构销毁之前执行。在此函数中不允许改变状态变量,特别�?@link 变量的修改可能会导致应用程序行为不稳�?
    组件生命周期独有�?

  4. abouttoappear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行�?build () 函数之前执行。在该函数中可以修改变量,更改将在后续执�?build () 函数中生�?2. abouttodisappear:在自定义组件析构销毁之前执行。在此函数中不允许改变状态变量,特别�?@link 变量的修改可能会导致应用程序行为不稳�?
    UIAbility 生命周期

  5. create 状态:在应用加载过程中,UIAbility 实例创建完成时触发,系统会调�?oncreate () 回调。可以在该回调中进行页面初始化操作,例如变量定义、资源加载等,用于后续的 UI 展示

  6. windowstagecreate 状态:UIAbility 实例创建完成之后,在进入 foreground 之前,系统会创建一�?windowstage。windowstage 创建完成后会进入 onwindowstagecreate () 回调,可以在该回调中设置 UI 加载、设�?windowstage 的事件订阅,如获�?/ 失焦、可�?/ 不可见等事件

  7. foreground 状态:�?UIAbility 实例切换至前台时触发,对应于 onforeground () 回调。在 onforeground () 中可以申请系统需要的资源,或者重新申请在 onbackground 中释放的资源.

  8. background 状态:�?UIAbility 实例切换至后台时触发,对应于 onbackground () 回调。在该回调中可以释放 UI 界面不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等.

  9. windowstagedestroy 状态:�?UIAbility 实例销毁之前,会先进入 onwindowstagedestroy 回调,可以在该回调中释放 UI 界面资源

  10. destroy 状态:�?UIAbility 实例销毁时触发,可以在 ondestroy () 回调中进行系统资源的释放、数据的保存等操�?
    :::

�?Entry �?Navigation 装饰的页面有哪些区别

::: details

  1. @Entry 装饰的页面是应用的入口页面,通常用于展示应用的初始界面,�?Navigation 组件是一个导航容器,挂载在单个页面下,支持跨模块的动态路由�?2. @Entry 页面具有通用的生命周期方法,�?Navigation 组件里的页面不执�?onPageShow、onPageHide 等生命周期回调�?
    :::

HarmonyOS 中里面有几种包,分别有什么作�?

::: details

HarmonyOS 中有三种类型的包:HAP(HarmonyOS Ability Package)、HAR(Harmony Archive)、HSP(Harmony Shared Package)�?

  1. HAP 是应用安装和运行的基本单元,分为 entry �?feature 两种类型�?2. HAR 是静态共享包,用于代码和资源的共享�?3. HSP 是动态共享包,用于应用内共享代码和资源�?
    :::

简单介绍一�?Stage 模型

::: details

  1. Stage 模型�?HarmonyOS 应用开发的基础架构,它提供了面向对象的开发方式,规范化了进程创建的方式,并提供组件化开发机制�?2. Stage 模型的组件天生具备分布式迁移和协同的能力,支持多设备形态和多窗口形态,重新定义了应用能力边界�?
    :::

HarmonyOS 中的动画

::: details

HarmonyOS 提供了多种动画能力,包括属性动画、显式动画、转场动画、路径动画和粒子动画�?
:::

如何进行路由页面传参

::: details

�?HarmonyOS 中,可以通过 router.pushUrl 方法跳转到目标页面,并携带参数。在进入被分享页面时,通过 router.getParams()来获�?
传递的数据。此外,还可以使�?LocalStorage 等在页面间共享状态�?
:::

ArkTS �?TS 的区别有哪些区别

::: details

ArkTS �?HarmonyOS 优选的主力应用开发语言,它保持�?TypeScript 的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。ArkTS �?TS 的主要区别在�?ArkTS 是静态类型的,�?TS 支持动态类型。ArkTS 在编译时进行类型检查,有助于在代码运行前发现和修复错误�?

  1. [强制使用静态类型](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/typescript-to-arkts-migration-guide-V5#强制使用静态类�? *ArkTS 中禁止使�?any 类型�?
  2. 禁止在运行时变更对象布局
  • 向对象中添加新的属性或方法�?- 从对象中删除已有的属性或方法�?- 将任意类型的值赋值给对象属性�?
  1. 限制运算符的语义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 一元运算符`+`只能作用于数值类型:
let t = +42; // 合法运算
let s = +'42'; // 编译时错�?```

:::

## 常见装饰�?
::: details

- [@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-state-V5):@State 装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新�?- [@Prop](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-prop-V5):@Prop 装饰的变量可以和父组件建立单向同步关系,@Prop 装饰的变量是可变的,但修改不会同步回父组件。深拷贝�?- [@Link](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-link-V5):@Link 装饰的变量可以和父组件建立双向同步关系,子组件中@Link 装饰变量的修改会同步给父组件中建立双向数据绑定的数据源,父组件的更新也会同步给@Link 装饰的变量�?- [@Provide/@Consume](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-provide-and-consume-V5):@Provide/@Consume 装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过 alias(别名)或者属性名绑定�?- [@Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-observed-and-objectlink-V5):@Observed 装饰 class,需要观察多层嵌套场景的 class 需要被@Observed 装饰。单独使用@Observed 没有任何作用,需要和@ObjectLink、@Prop 联用�?- [@ObjectLink](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-observed-and-objectlink-V5):@ObjectLink 装饰的变量接收@Observed 装饰�?class 的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步�?- [AppStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-appstorage-V5)是应用程序中的一个特殊的单例[LocalStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-localstorage-V5)对象,是应用级的数据库,和进程绑定,通过[@StorageProp](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-appstorage-V5#storageprop)和[@StorageLink](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-appstorage-V5#storagelink)装饰器可以和组件联动�?- AppStorage 是应用状态的“中枢”,将需要与组件(UI)交互的数据存入 AppStorage,比如持久化数据[PersistentStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-persiststorage-V5)和环境变量[Environment](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-environment-V5)。UI 再通过 AppStorage 提供的装饰器或�?API 接口,访问这些数据�?- 框架还提供了 LocalStorage,AppStorage �?LocalStorage 特殊的单例。LocalStorage 是应用程序声明的应用状态的内存“数据库”,通常用于页面级的状态共享,通过[@LocalStorageProp](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-localstorage-V5#localstorageprop)和[@LocalStorageLink](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-localstorage-V5#localstoragelink)装饰器可以和 UI 联动�?
:::

## 鸿蒙�?router �?Navigation 的对�?
::: details

1. router 最多页面栈�?32 个,Navigation 无限�?2. Navigation 支持一多开发,Auto 模式自适应单栏跟双栏显�?3. Navigation 支持获取指定页面参数
4. Navigation 清理指定路由
5. Navigation 支持路由拦截

:::

## 能力对比

::: details

| 业务场景 | Navigation | Router |
| :-------------------------------------------- | :-------------------------------------- | :------------------------------------------ |
| **一多能�?* | **支持,Auto 模式自适应单栏跟双栏显�?* | **不支�?* |
| 跳转指定页面 | pushPath & pushDestination | pushUrl & pushNameRoute |
| 跳转 HSP 中页�? | 支持 | 支持 |
| 跳转 HAR 中页�? | 支持 | 支持 |
| 跳转传参 | 支持 | 支持 |
| **获取指定页面参数** | **支持** | **不支�?* |
| 传参类型 | 传参为对象形�? | 传参为对象形式,对象中暂不支持方法变�? |
| 跳转结果回调 | 支持 | 支持 |
| 跳转单例页面 | 支持 | 支持 |
| 页面返回 | 支持 | 支持 |
| 页面返回传参 | 支持 | 支持 |
| 返回指定路由 | 支持 | 支持 |
| 页面返回弹窗 | 支持,通过路由拦截实现 | showAlertBeforeBackPage |
| 路由替换 | replacePath & replacePathByName | replaceUrl & replaceNameRoute |
| 路由栈清�? | clear | clear |
| **清理指定路由** | **removeByIndexes & removeByName** | 不支�? |
| 转场动画 | 支持 | 支持 |
| 自定义转场动�? | 支持 | 支持,动画类型受�? |
| 屏蔽转场动画 | 支持全局和单�? | 支持 设置 pageTransition 方法 duration �?0 |
| geometryTransition 共享元素动画 | 支持(NavDestination 之间共享�? | 不支�? |
| 页面生命周期监听 | UIObserver.on('navDestinationUpdate') | UIObserver.on('routerPageUpdate') |
| 获取页面栈对�? | 支持 | 不支�? |
| **路由拦截** | 支持通过 setInercption 做路由拦�? | 不支�? |
| 路由栈信息查�? | 支持 | getState() & getLength() |
| 路由�?move 操作 | moveToTop & moveIndexToTop | 不支�? |
| 沉浸式页�? | 支持 | 不支持,需通过 window 配置 |
| 设置页面标题栏(titlebar)和工具栏(toolbar�?| 支持 | 不支�? |
| 模态嵌套路�? | 支持 | 不支�? |

:::

## 页面下拉刷新和页面上拉加�?
::: details

1. 下拉刷新可以使用[Refresh](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-refresh-V5)组件,它提供�?onStateChange �?onRefreshing 事件 用来实现下拉刷新的业�?2. [List](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-list-V5#onreachend)、Scroll、Grid、WaterFall 等组件都提供�?*上拉加载更多**事件,比�?List 组件�?onReachEnd 事件就是

:::

## 响应式布局

::: details

> [链接](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/responsive-layout-V5#栅格布局)

1. 断点
1. �?�?UIAbility 的[onWindowStageCreate](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/uiability-lifecycle-V5)生命周期回调中监�?窗口尺寸变化事件,获取到当前窗口大小
2. 因为窗口大小单位�?px,需要调�?px2vp 函数转成 vp
3. 然后存到 AppStorage �? 4. 最后页�?使用 AppStorage 即可
2. 媒体查询
1. 主要通过 mediaquery 结合 断点来使�?3. 栅格布局
1. 通过 GridRow �?GridCol 来实�? 2. 一列分成了 12 份, 结合栅格组件默认提供 xs、sm、md、lg 四个断点

:::

## 断点续传

::: details

> 鸿蒙发送网络请求有两套方案
>
> 1. Request �?我们使用�?axios 就是 基于它封装的
> 2. RCP ,Remote Communication Kit(远场通信服务)是华为提供�?HTTP 发起数据请求�?NAPI 封装 目前新项目再推动�?> 3. [远场通信场景](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-scenario-V5)
> 1. [获取服务器资源](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-getserverresources-V5)
> 2. [发送数据到服务器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-senddatatoserver-V5)
> 3. [断点续传](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-httpresume-V5)
> 4. [双向证书校验](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-certificateverification-V5)
> 5. [拦截器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-interceptor-V5)
> 6. [使用自定义证书校验](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-validation-V5)
> 7. [上传下载文件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-updownload-V5)
> 8. [设置 TLS 版本号和加密套件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-settls-V5)
> 9. [获取服务器资�?(C/C++)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-getserverresources-c-V5)
> 10. [发送数据到服务�?(C/C++)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-senddatatoserver-c-V5)
> 11. [断点续传 (C/C++)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-httpresume-c-V5)
> 12. [双向证书校验 (C/C++)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-certificateverification-c-V5)
> 13. [拦截�?(C/C++)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-interceptor-c-V5)
> 14. [证书锁定](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-validationlock-V5)
> 15. [响应校验](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-responsecheck-V5)
> 16. [读写超时](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-readtimeout-V5)
> 17. [请求暂停和恢复](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-pause-resume-V5)
> 18. [同步读写流](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/remote-communication-asyncreadwrite-V5)

:::

### [断点续传](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/re

::: details
mote-communication-httpresume-V5)

1. 利用了远场通信 RemoteCommunicationKit
2. 发送网络请求,利用[TransferRange](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/remote-communication-rcp-V5#section838945575618)�?from �?to 属�?进行截取下载内容,拼接到文件上即�?
:::

## 双向证书校验

::: details

> 用于验证服务端和客户端之间的身份和数据完整性,确保通信的安全性�?
1. 导入远场通信模块和文件读写模�? 1. ```HTML
import { rcp } from '@kit.RemoteCommunicationKit';
import { fileIo } from '@kit.CoreFileKit';
  1. 使用文件读写模块 读取存在客户端的证书
    1. // 读取
       fileIo.read(file.fd
       // 存到字符�?content�?       // 将读取的数据转换为字符串
      let content = String.fromChar
      
      1
      2
      3
      4
      5
      6
      3. 然后调用给远场通信�?configuration 方法设置�?security.certificate.content 属性中
      1. ```HTML
      request.configuration = {
      security: {
      certificate: {
      content: content,

:::

项目优化

::: details

  • **图片懒加�?*:列表里的图片滑到可见区域再加载,减少内存占用�?- 数据缓存:用Preferences或数据库缓存首页数据,下次启动先展示缓存再刷新�?- 减少布局嵌套:用@Builder 代替自定义组件,多用线性布局,少�?flex 等弹性布局
  • 线程管理:把 JSON 解析、图片解码丢到Worker线程,防止主线程卡顿�?- 内存泄漏排查:用 DevEco Studio �?Profiler 工具,发现有个页面退出后监听器没注销,赶紧加了onPageHide里的释放逻辑�?
    :::

forEach �?LazyForEach

::: details

  1. forEach 会把数据全部渲染出来
  2. LazyForEach 只会渲染可视区域

:::

LazyForEach 如何实现更新

::: details

  • **数据源绑�?*:LazyForEach 需要与实现�?IDataSource 接口的数据源(如 LazyDataSource)绑定。当数据源发生变化(增、删、改)时,框架会自动触发更新�?- **观察者模�?*:数据源通过 DataChangeListener 通知 LazyForEach 数据变更。只有实际变化的项会触发局部更新,而非重新渲染整个列表�?
    :::

Class �?interface 的区�?

::: details

  1. Interface 只能定义类型,class 可以定义类型和保护功能实�?2. interface 可以同时继承多个接口,class 只能同时继承一个父�?3. 工作中两个都用,比如�?class 来封装了一些工具库 avplayer、首选项、全�?沉浸式、axios �?
    :::

AVPlayer 的播放步�?

::: details

  1. 创建实例 createAVPlayer(),AVPlayer 初始�?idle 状态�?

  2. 设置业务需要的监听事件

    事件类型 说明
    stateChange 必要事件,监听播放器�?state 属性改变�?
    error 必要事件,监听播放器的错误信息�?
    durationUpdate 用于进度条,监听进度条长度,刷新资源时长�?
    timeUpdate 用于进度条,监听进度条当前位置,刷新当前时间�?
    seekDone 响应 API 调用,监�?seek()请求完成情况。当使用 seek()跳转到指定播放位置后,如�?seek 操作成功,将上报该事件�?
    speedDone 响应 API 调用,监�?setSpeed()请求完成情况。当使用 setSpeed()设置播放倍速后,如�?setSpeed 操作成功,将上报该事件�?
    volumeChange 响应 API 调用,监�?setVolume()请求完成情况。当使用 setVolume()调节播放音量后,如果 setVolume 操作成功,将上报该事件�?
    bufferingUpdate 用于网络播放,监听网络播放缓冲信息,用于上报缓冲百分比以及缓存播放进度�?
    audioInterrupt 监听音频焦点切换信息,搭配属�?audioInterruptMode 使用。如果当前设备存在多个音频正在播放,音频焦点被切换(即播放其他媒体如通话等)时将上报该事件,应用可以及时处理�?
  3. 设置资源:设置属�?url,AVPlayer 进入 initialized 状态�?

  4. 准备播放:调�?prepare(),AVPlayer 进入 prepared 状态,此时可以获取 duration,设置音量�?

  5. 音频播控:播�?play(),暂�?pause(),跳�?seek(),停�?stop() 等操作�?

  6. (可选)更换资源:调�?reset()重置资源,AVPlayer 重新进入 idle 状态,允许更换资源 url�?

  7. 退出播放:调用 release()销毁实例,AVPlayer 进入 released 状态,退出播放�?
    :::

手动签名和自动签名的区别

::: details

:::

核心区别总结

::: details

对比维度 自动签名 手动签名
适用场景 单设备调试(单真机可用) 多设备调试、断网环境调�?
签名证书管理 �?DevEco Studio 自动生成签名证书并绑定当前设�?UDID 需�?*AGC**控制台申请调试证书****、注册调试设�?UDID、配置调�?Profile
安装限制 仅允许当前绑定的设备安装 支持注册的所有调试设备安�?
权限支持 **不支�?*受限开放权限(如健康服务) 支持受限权限(需通过 AGC 审核并提交场景说明)
*发布用�? 禁止用于发布 可生成与发布版本一致的签名包(需替换为正式证书)
受限服务依赖 无法使用部分依赖签名的开放能力(�?Health Kit�? 支持所有开放能�?
公钥指纹管理 自动生成调试指纹,需在发布前手动更新为发布指�? 需手动维护调试和发布的指纹

:::

webview 的性能优化(怎么加快 webview 的响应速度)

::: details

  • 可以通过prepareForPageLoad()来预解析或者预连接将要加载的页�?- 能够预测�?Web 组件将要加载的页面或者即将要跳转的页面。可以通过prefetchPage()来预加载即将要加载页�?- 可以通过 prefetchResource()预获取将要加载页面中�?post 请求。在页面加载结束时,可以通过 clearPrefetchedResource()清除后续不再使用的预获取资源缓存
  • 预编译生成编译缓�?可以通过 precompileJavaScript()在页面加载前提前生成脚本文件的编译缓存�?
    :::

前端工程�?

前端工程化通过自动化工具和标准化流程,提升开发效率、代码质量和可维护性。其核心目标是优化开发、构建、测试和部署流程,减少人工干预和重复劳动,便于项目扩展和团队协作�?
常见的工具,如Vite和Webpack,提供高效的构建和打包能力,显著提升开发效率并丰富前端生态。这些工具的广泛应用使前端开发更加高效,且成为近年来面试中的热门话题�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

Vite为什么更快?

::: details 参考答�?
Vite 相比传统构建工具(如 Webpack)更快🚀,主要得益于以下几个核心特性:

  • 基于原生 ES 模块(ESM):Vite 利用浏览器原生的 ES 模块,在开发模式下按需加载模块,避免了整体打包,从而减少了启动时间。它通过只编译实际修改的文件,提升了开发过程中的反馈速度�?- 高效的热模块替换(HMR):Vite 在开发模式下利用原生 ES 模块实现模块级的热更新。当文件发生变化时,Vite 只会重新加载发生变化的模块,而不是重新打包整个应用,极大提高了热更新的速度�?- 使用 esbuild 进行快速编译:Vite 默认使用 esbuild 作为编译工具,相比传统的 JavaScript 编译工具(如 Babel、Terser),esbuild 提供显著的性能提升,能够快速完成代码转换和压缩,从而加速开发和构建过程�?- 现代 JavaScript 特性支持:Vite 在生产环境中使用 Rollup 构建,支持优秀的树摇和代码拆分,有效减小构建体积。同时,Vite 利用现代浏览器特性(如动态导入、ES2015+ 模块),减少�?polyfill 的使用,提升了加载速度�?- 预构建和缓存:Vite 在开发时会预构建常用依赖(如 Vue、React),并将其转换为浏览器可执行的格式,避免每次启动时重新编译。同时,Vite 会缓存这些预构建的依赖,并在启动时复用缓存,从而加快启动速度�?
    :::

vite中如何使用环境变量?

::: details 参考答�?
根据当前的代码环境变化的变量就叫�?*环境变量**。比如,在生产环境和开发环境将BASE_URL设置成不同的值,用来请求不同的环境的接口�?
Vite内置�?dotenv 这个第三方库�?dotenv会自动读�?.env 文件�?dotenv 从你�?环境目录 中的下列文件加载额外的环境变量:

.env # 所有情况下都会加载
.env.[mode] # 只在指定模式下加�?
默认情况�?

  • npm run dev 会加�?.env �?.env.development 内的配置
  • npm run build 会加�?.env �?.env.production 内的配置
  • mode 可以通过命令�?--mode 选项来重写�? 环境变量需�?VITE_ 前缀定义,且通过 import.meta.env 访问�?
    示例�?.env.development�?
    1
    VITE_API_URL = 'http://localhost:3000'

在代码中使用�?

1
console.log(import.meta.env.VITE_API_URL) // http://localhost:3000

参考博文:vite中环境变量的使用与配置

:::

vite如何实现根据不同环境(qa、dev、prod)加载不同的配置文件?

::: details 参考答�?
�?Vite 中,根据不同环境设置不同配置的方式,类似�?Webpack 时代的配置方法,但更加简化。Vite 使用 defineConfig 函数,通过判断 command �?mode 来加载不同的配置�?

  • 通过 defineConfig 动态配置:

Vite 提供�?defineConfig 函数可以根据 command 来区分开发环境( serve )和生产环境�?build ),并返回不同的配置�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { defineConfig } from 'vite'
import viteBaseConfig from './vite.base.config'
import viteDevConfig from './vite.dev.config'
import viteProdConfig from './vite.prod.config'

export default defineConfig(({ command, mode, ssrBuild }) => {
if (command === 'serve') {
// 开发环境独有配�? return {
...viteBaseConfig,
...viteDevConfig,
}
} else {
// 生产环境独有配置
return {
...viteBaseConfig,
...viteProdConfig,
}
}
})
  • *创建不同的配置文�?

vite.base.config.ts :基础配置,适用于所有环境�?

1
2
3
4
5
6
7
8
9
10
import {
defineConfig
} from "vite";
export default defineConfig({
// 基础配置->使用所有场�? return {
plugins: [
vue()
],
}
});

vite.dev.config.ts :开发环境配置�?

1
2
3
import { defineConfig } from 'vite'
export default defineConfig({
// 开发环境专有配�?})

vite.prod.config.ts :生产环境配置�?

1
2
3
4
import { defineConfig } from 'vite'
export default defineConfig({
// 生产环境专有配置
})

参考博文:vite指定配置文件及其在多环境下的配置集成方案

:::

简述Vite的依赖预加载机制�?

::: details 参考答�?
Vite 的依赖预构建机制通过在开发模式下提前处理常用依赖(如 Vue、React 等),将这些依赖转换为浏览器可以直接执行的格式。这避免了每次启动时重新编译这些依赖,显著提升了启动速度。预构建的依赖被缓存,并在后续启动时复用缓存,进一步加速了开发过程中的构建和启动时间�?
具体来说,它的工作原理如下:

  • **依赖识别和路径补�?*�?Vite 会首先识别项目中需要的依赖,并对非绝对路径或相对路径的引用进行路径补全。比如,Vue 的加载路径会变为 node_modules/.vite/deps/Vue.js?v=1484ebe8,这一路径显示�?Vite �?node_modules/.vite/deps 文件夹下存放了经过预处理的依赖文件�?- 转换�?ES 模块�?一些第三方包(特别是遵�?CommonJS 规范的包)在浏览器中无法直接使用。为了应对这种情况,Vite 会使�?esbuild 工具将这些依赖转换为符合 ES 模块规范的代码。转换后的代码会被存放在 node_modules/.vite/deps 文件夹下,这样浏览器就能直接识别并加载这些依赖�?- 统一集成 ES 模块�?Vite 会对每个包的不同模块进行统一集成,将各个分散的模块(如不同的 ES 函数或组件)合并成一个或几个文件。这不仅减少了浏览器发起多个请求的次数,还能够加快页面加载速度�?

    参考博文:vite的基础使用及其依赖预加载机制手写vite让你深刻了解Vite的文件加载原理

:::

vite中如何加载、处理静态资源?

::: details 参考答�?
🎯 **静态资源目录(public 目录�?*�?

  • 静态资源可以放�?public 目录下,这些文件不会经过构建处理,直接按原样复制到输出目录。在开发时可以通过 / 路径直接访问,如 /icon.png�?- public 目录可通过 vite.config.js 中的 publicDir 配置项修改�?
    🎯 资源引入�?
  • **图片、字体、视�?*:通过 import 引入,Vite 会自动将其处理为 URL 并生成带哈希值的文件名。在开发时,引用会是根路径(如 /img.png),在生产构建后会是�?/assets/img.2d8efhg.png 的路径�?- CSS、JS:CSS 会被自动注入到页面中,JS 按模块处理�?
    🎯 强制作为 URL 引入:通过 ?url 后缀可以显式强制将某些资源作�?URL 引入�?
    1
    import imgUrl from './img.png?url'

🎯 强制作为原始内容引入:通过 ?raw 后缀将文件内容作为字符串引入�?
🎯 new URL() :通过 import.meta.url 可以动态构建资源的 URL,这对于一些动态路径很有用�?

1
2
const imgUrl = new URL('./img.png', import.meta.url).href
document.getElementById('hero-img').src = imgUrl

参考博文:vite中静态资源(css、img、svg等)的加载机制及其相关配

:::

如何在Vite项目中引入CSS预处理器?

::: details 参考答�?
�?Vite 中使�?CSS 预处理器(如 Sass、Less)是非常简单的,Vite 默认支持这些预处理器,我们只需要安装相应的依赖即可�?
安装依赖�?

1
npm install sass--save - dev

�?Vue 组件中使用:

1
2
3
4
5
6
<style lang="scss">
$primary-color: #42b983;
body {
background-color: $primary-color;
}
</style>

此外,我们可以通过在vite�?preprocessorOptions 中进行配置,使用CSS 预处理器的一些强大功能�?
对于 Less,假如我们需要在项目中全局使用某些变量,我们可以在 vite.config.js 中配�?globalVars ,使得变量在所有文件中无需单独引入�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
less: {
globalVars: {
blue: '#1CC0FF', // 定义全局变量
},
},
},
},
})

一旦配置了全局变量,我们就可以在任�?Vue 组件中直接使用它,无需再次引入�?

1
2
3
4
5
6
<style scoped lang="less">
.wrap {
background: red;
color: @blue; // 使用全局变量
}
</style>

参考博文:vite中如何更优雅的使用cssVite中预处理�?如less)的配置使用postcss完善vite项目中的css配置

:::

vite中可做的项目优化有哪些?

::: details 参考答�?
1️⃣ 启用 Gzip/Brotli 压缩

使用 vite-plugin-compression 插件开�?Gzip �?Brotli 压缩,可以有效减小传输的文件体积,提升加载速度�?
安装依赖�?

1
npm install vite - plugin - compression--save - dev

配置示例�?

1
2
3
4
5
6
7
8
import compression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
compression({
algorithm: 'gzip', // �?'brotli' 压缩
threshold: 10240, // 文件大于 10KB 时启用压�? }),
],
})

参考博文:vite打包优化vite-plugin-compression的使用

2️⃣ 代码分割

  • 🎯 路由分割

使用动态导入实现按需加载,减小初始包的体积,提高页面加载速度�?

1
2
3
4
5
const module = import('./module.js') // 动态导�?```

或者在路由中使用懒加载�?
```javascript
const MyComponent = () => import('./MyComponent.vue')
  • 🎯 手动控制分包

�?Vite 中,你可以通过配置 Rollup �?manualChunks 选项来手动控制如何分割代码。这个策略适用于想要将特定的依赖或模块提取成单独的 chunk 文件�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineConfig } from 'vite'
export default defineConfig({
build: {
minify: false,
// 在这里配置打包时的rollup配置
rollupOptions: {
manualChunks: (id) => {
if (id.includes('node_modules')) {
return 'vendor'
}
},
},
},
})

参考博文:Vite性能优化之分包策略

3️⃣ 图片优化

使用 vite-plugin-imagemin 插件对项目中的图片进行压缩,减少图片体积,提升加载速度�?

1
npm install vite - plugin - imagemin--save - dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default defineConfig({
plugins: [
ViteImagemin({
gifsicle: {
optimizationLevel: 3,
},
optipng: {
optimizationLevel: 7,
},
mozjpeg: {
quality: 85,
},
pngquant: {
quality: [0.65, 0.9],
},
}),
],
})

4️⃣ 依赖优化

配置 Vite �?optimizeDeps 选项,提前预构建常用依赖,减少开发环境下的启动时间�?

1
2
3
4
export default defineConfig({
optimizeDeps: {
include: ['lodash', 'vue', 'react'], // 预构建依�? },
})

参考博文:vite的基础使用及其依赖预加载机制

:::

简述vite插件开发流程?

::: details 参考答�?
Vite 插件开发基�?Rollup 插件系统,因此其生命周期和钩子与 Rollup 插件非常相似。以下是开发流程和关键步骤�?
1️⃣ 理解插件生命周期
Vite 插件有一系列生命周期钩子,每个钩子对应不同的功能需求,主要钩子包括�?

  • config:用于修�?Vite 配置,通常在构建或开发过程中使用�?- configureServer:用于修改开发服务器的行为,如自定义请求处理�?- transform:对文件内容进行转换,适用于文件类型转换或代码处理�?- buildStart �?buildEnd:在构建过程开始和结束时触发,适用于日志记录或优化操作�?
    插件开发的核心是根据具体需求,在合适的生命周期钩子中实现业务逻辑�?
    2️⃣ 插件基本结构

Vite 插件的基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function myVitePlugin() {
return {
name: 'vite-plugin-example', // 插件名称
config(config) {
// 修改 Vite 配置
},
configureServer(server) {
// 修改开发服务器行为
},
transform(src, id) {
// 对文件内容进行转�? },
}
}

插件对象必须包含一�?name 属性,用于标识插件,还可以根据需求实现其他钩子�?
3️⃣ *插件开�?

在插件开发过程中,根据需求实现不同的钩子逻辑。例如,假设我们需要创建一个插件来处理自定义文件类型并将其转换�?JavaScript�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fileRegex = /\.(my-file-ext)$/

export default function transformFilePlugin() {
return {
name: 'vite-plugin-transform-file',
transform(src, id) {
if (fileRegex.test(id)) {
return {
code: compileFileToJS(src), // 将文件内容转换为 JavaScript
map: null, // 可以返回 source map
}
}
},
}
}
  • transform:此钩子对符�?fileRegex 正则表达式的文件(.my-file-ext)进行转换,并返回转换后�?JavaScript 代码�?
    4️⃣ 插件使用

插件开发完成后,可以在 Vite 配置中使用:

1
2
3
4
5
import transformFilePlugin from 'vite-plugin-transform-file'

export default {
plugins: [transformFilePlugin()],
}

5️⃣ 发布插件

开发完成后,插件可以通过 npm 发布,或者将其托管在 GitHub 上,方便团队或社区使用�?

参考博文:https://juejin.cn/post/7270528132167417915

:::

如何在Vite中配置代理?

::: details 参考答�?�?Vite 中配置代理可以通过 server.proxy 选项来实现。以下是一个示例配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
server: {
proxy: {
// 代理 /api 请求到目标服务器
'/api': {
target: 'http://localhost:5000', // 目标服务器地址
changeOrigin: true, // 修改请求头中�?Origin 字段为目标服务器�?origin
secure: false, // 是否允许 HTTPS 请求
rewrite: (path) => path.replace(/^\/api/, ''), // 重写请求路径,将 /api 替换为空
},

// 代理某些静态资源请�? '/assets': {
target: 'http://cdn-server.com', // 目标是静态资源服务器
changeOrigin: true,
rewrite: (path) => path.replace(/^\/assets/, '/static'), // �?/assets 路径重写�?/static
},
},
},
})

:::

Vite如何集成TypeScript?如何配置?

::: details 参考方�?
Vite �?TypeScript 提供了开箱即用的支持,无需额外安装插件�?
我们创建一�?index.html 文件并引�?main.ts 文件�?

1
2
3
<script src="./main.ts" type="module">
{' '}
</script>

�?main.ts 中,可以写入一�?TypeScript 代码�?

1
2
let tip: string = "这是一个vite项目,使用了ts语法";
console.log('tip: ', tip);

运行 vite 后,可以看到控制台输出内容,表明 Vite 天生支持 TypeScript�?
�?Vite 项目中,虽然默认支持 TypeScript,但 Vite 本身不会阻止编译时出�?TypeScript 错误。为了更严格的类型检查和错误提示,我们需要配�?TypeScript�?

  • 添加 TypeScript 配置(如果没有)

通过以下命令生成 tsconfig.json 配置文件

1
npx tsc --init

创建�?tsconfig.json 后,Vite 会根据该配置文件来编�?TypeScript�?

  • 强化 TypeScript 错误提示

Vite 默认不会阻止编译时的 TypeScript 错误。如果我们想要在开发时严格检�?TypeScript 错误并阻止编译,可以使用 vite-plugin-checker 插件�?

1
npm i vite - plugin - checker--save - dev

然后�?vite.config.ts 中引入并配置该插件:

1
2
3
4
5
6
7
// vite.config.ts
import checker from 'vite-plugin-checker'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [checker({ typescript: true })],
})

这样,任�?TypeScript 语法错误都会在控制台显示,并阻止编译�?

  • 打包时进�?TypeScript 检�?
    虽然 Vite 只会执行 .ts 文件的转译,而不会执行类型检查,但我们可以通过以下方式确保在打包时进行 TypeScript 类型检查�?
    修改 package.json 配置
1
2
3
4
5
6
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build"
}
}

tsc --noEmit 会执行类型检查,但不会生成编译后的文件。如果存在类型错误,打包过程会被阻止�?

  • TypeScript 智能提示

Vite 默认�?import.meta.env 提供了类型定义,但是对于自定义的 .env 文件,TypeScript 的智能提示默认不生效。为了实现智能提示,可以�?src 目录下创建一�?env.d.ts 文件�?

1
2
3
4
5
6
7
8
9
10
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_APP_HAHA: string
}

interface ImportMeta {
readonly env: ImportMetaEnv
}

参考博�?https://juejin.cn/post/7177210200330829885

:::

什么是 Webpack?它的作用是什么?

参考答�?
::: details

Webpack 是一个开源的 **前端静态模块打包工�?*,主要用于将现代 JavaScript 应用中的各种资源(代码、样式、图片等)转换为优化的静态文件。它是现代前端开发的核心工具之一,尤其在复杂项目中扮演着关键角色�?
*Webpack 的核心作�?

  1. *模块化支�?

    • 解决问题:将代码拆分为多个模块(文件),管理依赖关系�? - 支持语法�?
      • ES Modules ( import/export )
      • CommonJS ( require/module.exports )
      • AMD 等模块化方案�?
1
2
// 模块化开�?import Header from './components/Header.js'
import styles from './styles/main.css'
  1. 资源整合

    • 处理�?JS 文件:将 CSS、图片、字体、JSON 等资源视为模块,统一管理�?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // webpack.config.js
      module.exports = {
      module: {
      rules: [
      {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
      },
      {
      test: /\.(png|svg)$/,
      type: 'asset/resource',
      },
      ],
      },
      }
  2. 代码优化

    • 功能�?
      • Tree Shaking:删除未使用的代码�? - **代码分割(Code Splitting�?*:按需加载代码,减少首屏体积�? - 压缩:减小文件体积,提升加载速度�?
1
2
3
4
// 动态导入实现按需加载
button.addEventListener('click', () => {
import('./module.js').then((module) => module.run())
})
  1. *开发工具集�?

    • 功能�?
      • **热更新(HMR�?*:实时预览代码修改效果�? - Source Map:调试时映射压缩代码到源代码�? - **本地服务�?*:快速启动开发环境�?
1
2
3
devServer: {
hot: true, // 启用热更�? open: true, // 自动打开浏览�? },
devtool: 'source-map', // 生成 Source Map
  1. *生态扩�?
    • Loader:处理特定类型文件(�?.scss �?.css )�? - Plugin:优化构建流程(如生�?HTML、压缩代码)�?
      1
      2
      3
      4
      5
      6
      plugins: [
      new HtmlWebpackPlugin({
      template: './src/index.html'
      }),
      new MiniCssExtractPlugin(),
      ],

*Webpack 的工作流�?

  1. **入口(Entry�?*:从指定文件(如 index.js)开始分析依赖�?2. **依赖图(Dependency Graph�?*:递归构建模块间的依赖关系�?3. **加载器(Loaders�?*:转换非 JS 资源(如编译 Sass、处理图片)�?4. **插件(Plugins�?*:在构建生命周期中执行优化任务�?5. **输出(Output�?*:生成优化后的静态文件(�?bundle.js)�?
    *与其他工具对�?
    工具 定位 *�?Webpack 的区�?
    Gulp/Grunt 任务运行器(Task Runner�? 处理文件流,但无模块化支�?
    Rollup 库打包工�? 更适合库开发,Tree Shaking 更激�?
    Vite 新一代构建工�? 基于原生 ESM,开发环境更快,生产依赖 Rollup

适用场景

  • **单页应用(SPA�?*:如 React、Vue、Angular 项目�?- 复杂前端工程:多页面、微前端架构�?- **静态网站生�?*:结�?Markdown、模板引擎使用�?
    Webpack 通过 **模块化整�?�?代码优化 �?**开发效率提�?*,解决了前端工程中资源管理混乱、性能瓶颈和开发体验差的问题。它不仅是打包工具,更是现代前端工程化的基础设施�?
    :::

如何使用 Webpack 配置多环境的不同构建配置�?

参考答�?
::: details

�?Webpack 中配置多环境(如开发环境、测试环境、生产环境)的构建配置,可以通过 环境变量注入 �?配置合并 的方式实现�?
*步骤 1:安装依赖工�?

1
npm install webpack-merge cross-env --save-dev
  • webpack-merge:用于合并基础配置和环境专属配置�?- cross-env:跨平台设置环境变量(兼�?Windows �?macOS/Linux)�?
    *步骤 2:创建配置文件结�?
1
2
3
4
5
6
7
project/
├── config/
�? ├── webpack.common.js # 公共配置
�? ├── webpack.dev.js # 开发环境配�?�? └── webpack.prod.js # 生产环境配置
├── src/
�? └── ... # 项目源码
└── package.json

步骤 3:编写公共配�?( webpack.common.js )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// config/webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
entry: './src/index.js',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
}

*步骤 4:编写环境专属配�?

开发环�?( webpack.dev.js )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// config/webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const webpack = require('webpack')

module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
hot: true,
open: true,
port: 3000,
},
plugins: [
// 注入环境变量(可在代码中通过 process.env.API_URL 访问�? new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://dev.api.com'),
'process.env.NODE_ENV': JSON.stringify('development'),
}),
],
})

生产环境 ( webpack.prod.js )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// config/webpack.prod.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const webpack = require('webpack')

module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
optimization: {
minimizer: [
'...', // 保留默认�?JS 压缩配置
new CssMinimizerPlugin(),
],
},
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://prod.api.com'),
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],
})

步骤 5:配�?package.json 脚本

1
2
3
4
5
6
7
{
"scripts": {
"start": "cross-env NODE_ENV=development webpack serve --config config/webpack.dev.js",
"build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
"build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
}
}

*步骤 6:在代码中使用环境变�?

1
2
3
4
5
6
7
8
9
10
// src/index.js
console.log('当前环境:', process.env.NODE_ENV)
console.log('API 地址:', process.env.API_URL)

// 根据不同环境执行不同逻辑
if (process.env.NODE_ENV === 'development') {
console.log('这是开发环�?)
} else {
console.log('这是生产环境')
}

*步骤 7:运行命�?

1
2
3
4
5
6
# 启动开发服务器(热更新�?npm run start

# 构建开发环境产�?npm run build:dev

# 构建生产环境产物
npm run build:prod

扩展:支持更多环境(如测试环境)

  1. 创建 webpack.stage.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config/webpack.stage.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const webpack = require('webpack')

module.exports = merge(common, {
mode: 'production',
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://stage.api.com'),
'process.env.NODE_ENV': JSON.stringify('staging'),
}),
],
})
  1. 添加 package.json 脚本
1
2
3
4
5
{
"scripts": {
"build:stage": "cross-env NODE_ENV=staging webpack --config config/webpack.stage.js"
}
}
*配置�? *开发环�? 生产环境 测试环境
mode development production production
devtool eval-source-map source-map source-map
devServer �?启用 �?不启�? �?不启�?
代码压缩 �?不压�? �?CSS/JS 压缩 �?CSS/JS 压缩
环境变量 API_URL=dev.api.com API_URL=prod.api.com API_URL=stage.api.com

:::

Webpack 的核心概念有哪些?请简单解释�?

参考答�?
::: details

Webpack 的核心概念是理解其工作原理和配置的基础,以下是它们的简要解释:

*1. 入口(Entry�?

  • 作用:定�?Webpack 构建依赖图的起点,通常为项目的主文件(�?index.js)�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    entry: './src/index.js', // 单入�?    entry: {
    app: './src/app.js',
    admin: './src/admin.js'
    }, // 多入�?```

    **2. 出口(Output�?*

    - **作用**:指定打包后的资�?*输出位置和命名规�?*�?
    ```javascript
    output: {
    filename: '[name].bundle.js', // 输出文件名([name] 为入口名称)
    path: path.resolve(__dirname, 'dist'), // 输出目录(绝对路径)
    clean: true, // 自动清理旧文件(Webpack 5+�?}

*3. 加载器(Loaders�?

  • 作用:让 Webpack 处理�?JavaScript 文件(如 CSS、图片、字体等),将其转换为有效模块�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    module: {
    rules: [{
    test: /\.css$/,
    use: ['style-loader', 'css-loader']
    }, // 处理 CSS
    {
    test: /\.(png|svg)$/,
    type: 'asset/resource'
    }, // 处理图片(Webpack 5+�? ],
    }

*4. 插件(Plugins�?

  • 作用:扩�?Webpack 功能,干�?*整个构建流程**(如生成 HTML、压缩代码、提�?CSS)�?
    1
    2
    3
    4
    5
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
    }), // 生成 HTML
    new MiniCssExtractPlugin(), // 提取 CSS 为独立文�?]

*5. 模式(Mode�?

  • 作用:预设优化策略,区分*开发环�?*(development)和**生产环境*(production)�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    mode: 'production', // 启用代码压缩、Tree Shaking 等优�?```

    **6. 模块(Modules�?*

    - **作用**:Webpack 将每个文件视�?*模块**(如 JSCSS、图片),通过依赖关系构建依赖图�?- **特点**:支�?ESMCommonJSAMD 等模块化语法�?
    **7. 代码分割(Code Splitting�?*

    - **作用**:将代码拆分为多个文件(chunks),实现**按需加载**�?*并行加载**,优化性能�?- **实现方式**�? - 动态导入(`import()`�? - 配置 `optimization.splitChunks`

    **8. Tree Shaking**

    - **作用**:通过静态分�?*移除未使用的代码**,减小打包体积�?- **前提**:使�?ES Module`import/export`),并启用生产模式(`mode: 'production'`)�?
    :::

    ## 如何�?Webpack 中实�?CSS �?Sass 的处理?

    参考答�?
    ::: details

    �?Webpack 中处�?CSS �?SassSCSS)需要配置相应的加载器(loaders)和插件(plugins)�?
    **1. 安装所需依赖**

    ```bash
    npm install --save-dev \
    style-loader \
    css-loader \
    sass-loader \
    sass \
    postcss-loader \
    autoprefixer \
    mini-css-extract-plugin \
    css-minimizer-webpack-plugin
  • 核心依赖�? - style-loader:将 CSS 注入 DOM�? - css-loader:解�?CSS 文件中的 @import �?url()�? - sass-loader:将 Sass/SCSS 编译�?CSS�? - sass:Sass 编译器(Dart Sass 实现)�?- **可选工�?*�? - postcss-loader �?autoprefixer:自动添加浏览器前缀�? - mini-css-extract-plugin:提�?CSS 为独立文件(生产环境推荐)�? - css-minimizer-webpack-plugin:压�?CSS(生产环境推荐)�?
    2. 基础 Webpack 配置
    �?webpack.config.js 中添加以下规则和插件�?
    配置 CSS �?SCSS 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
module: {
rules: [
// 处理 CSS 文件
{
test: /\.css$/,
use: [
// 开发环境用 style-loader,生产环境用 MiniCssExtractPlugin.loader
process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader', // 可选:添加浏览器前缀
],
},
// 处理 SCSS/Sass 文件
{
test: /\.(scss|sass)$/,
use: [
process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader', // 可选:添加浏览器前缀
'sass-loader',
],
},
],
},
plugins: [
// 提取 CSS 为独立文件(生产环境�? new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
optimization: {
minimizer: [
// 压缩 CSS(生产环境)
new CssMinimizerPlugin(),
],
},
}

3. 配置 PostCSS(可选)
创建 postcss.config.js 文件以启�?autoprefixer �?

1
2
3
4
5
6
7
module.exports = {
plugins: [
require('autoprefixer')({
// 指定浏览器兼容范�? overrideBrowserslist: ['last 2 versions', '>1%', 'not dead'],
}),
],
}

通过配置 css-loader �?sass-loader �?MiniCssExtractPlugin ,Webpack 可以高效处理 CSS �?Sass。关键点包括�?

  1. 加载器顺序:从右到左(如 [sass-loader, css-loader, style-loader])�?2. 生产环境提取 CSS:使�?MiniCssExtractPlugin�?3. 浏览器兼容性:通过 postcss-loader �?autoprefixer 自动处理�?
    :::

Webpack 中的入口和出口是什么?

参考答�?
::: details

�?Webpack 中,*入口(Entry�? �?*出口(Output�? 是配置文件中的核心概念,决定了打包的起点和终点。它们共同定义了 Webpack 如何处理代码以及最终生成的资源�?

  1. *入口(Entry�?
    入口�?Webpack 构建依赖图的起点,它告诉 Webpack�?“从哪个文件开始分析代码的依赖关系?�?

作用

  • 指定应用程序的起始文件�?- 根据入口文件递归构建依赖关系树�?- 支持单入口(单页面应用)或多入口(多页面应用)�?
    配置方式
    �?webpack.config.js 中通过 entry 属性配置:
1
2
3
4
5
6
7
8
module.exports = {
entry: './src/index.js', // 单入口(默认配置�?
// 多入口(多页面应用)
entry: {
home: './src/home.js',
about: './src/about.js',
},
}

默认行为

  • 如果未手动配�?entry,Webpack 默认使用 ./src/index.js 作为入口�?
  1. *出口(Output�?
    出口�?Webpack 打包后的资源输出位置,它告诉 Webpack�?“打包后的文件放在哪里?如何命名?�?

作用

  • 定义打包文件的输出目录和命名规则�?- 处理静态资源的路径(如 CSS、图片等)�?
    配置方式
    �?webpack.config.js 中通过 output 属性配置:
1
2
3
4
5
6
7
8
9
const path = require('path')

module.exports = {
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录(必须为绝对路径�? filename: 'bundle.js', // 单入口输出文件名

// 多入口时,使用占位符确保唯一�? filename: '[name].[contenthash].js',
clean: true, // 自动清理旧文件(Webpack 5+�? },
}

*常用占位�?

占位�? 说明
[name] 入口名称(如多入口的 home �?
[hash] 根据构建生成的唯一哈希�?
[contenthash] 根据文件内容生成的哈希�?
[chunkhash] 根据代码块生成的哈希�?

:::

Webpack 中的 Loaders �?Plugins 有什么区�?

参考答�?
::: details

�?Webpack 中,*Loaders(加载器�? �?Plugins(插件) 是构建流程中的两大核心概念,它们的作用和职责有明显区别�?
1. 核心区别总结

*特�? Loaders Plugins
主要作用 转换文件内容(如转译、预处理�? 扩展构建流程(优化、资源管理、注入环境变量等�?
执行时机 在模块加载时(文件转换为模块时) 在整个构建生命周期(从初始化到输出)的各个阶�?
配置方式 通过 module.rules 数组配置 通过 plugins 数组配置(需�?new 实例化)
典型场景 处理 JS/CSS/图片等文件转�? 生成 HTML、压缩代码、提�?CSS 等全局操作
依赖关系 针对特定文件类型(如 .scss �? 不依赖文件类型,可干预整个构建流�?

2. Loaders 的作用与使用
核心功能

  • 将非 JavaScript 文件(如 CSS、图片、字体等�?*转换�?Webpack 能处理的模块**�?- 对代码进行预处理(如 Babel 转译、Sass 编译)�?
    配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// webpack.config.js
module.exports = {
module: {
rules: [
// 处理 CSS 文件
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
// 处理 TypeScript 文件
{
test: /\.tsx?$/,
use: 'ts-loader',
},
// 处理图片文件
{
test: /\.(png|jpg|gif)$/,
type: 'asset/resource', // Webpack 5 内置方式(替�?file-loader�? },
],
},
}

常见 Loaders

  • babel-loader: �?ES6+ 代码转译�?ES5�?- css-loader: 解析 CSS 中的 @import �?url()�?- sass-loader: �?Sass/SCSS 编译�?CSS�?- file-loader: 处理文件(如图片)的导入路径�?
    3. Plugins 的作用与使用
    核心功能

  • 扩展 Webpack 的能力,干预构建流程�?*任意阶段**�?- 执行更复杂的任务,如代码压缩、资源优化、环境变量注入等�?
    配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
plugins: [
// 自动生成 HTML 文件,并注入打包后的资源
new HtmlWebpackPlugin({
template: './src/index.html',
}),
// 提取 CSS 为独立文�? new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
}

常见 Plugins

  • HtmlWebpackPlugin: 生成 HTML 文件并自动引入打包后的资源�?- MiniCssExtractPlugin: �?CSS 提取为独立文件(替代 style-loader)�?- CleanWebpackPlugin: 清理构建目录(Webpack 5 中可�?output.clean: true 替代)�?- DefinePlugin: 注入全局常量(如 process.env.NODE_ENV)�?
    4. 执行流程对比
    *Loaders 的执行流�?
1
文件资源 (�?.scss) �?匹配 Loader 规则 �?按顺序应�?Loaders �?转换�?JS 模块
  • 顺序关键:Loaders 从右到左(或从下到上)执行�? 例如�?use: ['style-loader', 'css-loader', 'sass-loader'] 的执行顺序为�? sass-loader �?css-loader �?style-loader �?
    *Plugins 的执行流�?
1
初始�?�?读取配置 �?创建 Compiler �?挂载 Plugins �?编译模块 �?优化 �?输出
  • 生命周期钩子:Plugins 通过监听 Webpack 的生命周期钩子(如 emitdone)干预构建流程�?
    5. 协作示例
    一个同时使�?Loaders �?Plugins 的典型场景:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// webpack.config.js
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{
test: /\.scss$/,
// Loaders 处理链:sass �?css �?MiniCssExtractPlugin
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
plugins: [
// Plugin:提�?CSS 为文�? new MiniCssExtractPlugin(),
// Plugin:生�?HTML
new HtmlWebpackPlugin(),
],
}

:::

Webpack�? 如何实现按需加载�?

参考答�?
::: details

�?Webpack 中实现按需加载(代码分�?懒加载)的核心思路�?**将代码拆分为独立 chunk,在需要时动态加�?*�?
*一、基础方法:动态导入(Dynamic Import�?
通过 import() 语法实现按需加载,Webpack 会自动将其拆分为独立 chunk�?
*1. 代码中使用动态导�?

1
2
3
4
5
// 示例:点击按钮后加载模块
document.getElementById('btn').addEventListener('click', async () => {
const module = await import('./module.js')
module.doSomething()
})

2. 配置 Webpack
确保 webpack.config.js �?output 配置中包�?chunkFilename �?

1
2
3
4
5
6
7
module.exports = {
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].[contenthash].chunk.js', // 动态导入的 chunk 命名规则
path: path.resolve(__dirname, 'dist'),
publicPath: '/', // 确保 chunk 的公共路径正�? },
}

二、框架集成:React/Vue 路由级按需加载
结合前端框架的路由系统实现组件级懒加载�?
React 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Suspense, lazy } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

const Home = lazy(() => import('./routes/Home'))
const About = lazy(() => import('./routes/About'))

function App() {
return (
<Router>
<Suspense fallback={<div> Loading... </div>}>
{' '}
<Switch>
<Route exact path="/" component={Home} />{' '}
<Route
path="/about
"
component={About}
/>{' '}
</Switch>{' '}
</Suspense>{' '}
</Router>
)
}

Vue 示例

1
2
3
4
5
6
7
8
9
10
const routes = [
{
path: '/',
component: () => import('./views/Home.vue'),
},
{
path: '/about',
component: () => import('./views/About.vue'),
},
]

三、优化配置:代码分割策略
通过 SplitChunksPlugin 优化公共代码提取�?
Webpack 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 对所有模块进行分割(包括异步和非异步�? cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors', // 提取 node_modules 代码�?vendors �? priority: 10, // 优先�? reuseExistingChunk: true,
},
common: {
minChunks: 2, // 被至少两�?chunk 引用的代�? name: 'common',
priority: 5,
reuseExistingChunk: true,
},
},
},
},
}

*四、Babel 配置(如需支持旧浏览器�?
安装 Babel 插件解析动态导入语法:

1
npm install @babel/plugin-syntax-dynamic-import --save-dev

�?.babelrc �?babel.config.json 中添加插件:

1
2
3
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}

五、预加载与预取(可选优化)
通过注释提示浏览器提前加载资源(需结合框架使用)�?
React 示例

1
2
3
4
5
6
const About = lazy(
() =>
import(
/* webpackPrefetch: true */ // 预取(空闲时加载�? /* webpackPreload: true */ // 预加载(与父 chunk 并行加载�? './routes/About'
)
)

*六、验证效�?

  1. 构建产物分析�?
    • 运行 npx webpack --profile --json=stats.json 生成构建报告�? - 使用 Webpack Bundle Analyzer 可视化分�?chunk 分布�?
  2. 网络请求验证�? - 打开浏览器开发者工具,观察触发动态导入时是否加载�?chunk�?
    :::

什么是 Tree Shaking?如何在 Webpack 中启用它�?

参考答�?
::: details

Tree Shaking(摇树优化) 是一种在打包过程�?*移除 JavaScript 项目中未使用代码(Dead Code�? 的优化技术。它的名字形象地比喻为“摇动树以掉落枯叶”,即通过静态代码分析,识别并删除未被引用的模块或函数,从而减小最终打包体积�?
*Tree Shaking 的工作原�?

  1. *基于 ES Module(ESM)的静态结�?
    ESM �?import/export 是静态声明(代码执行前可确定依赖关系),�?CommonJS �?require 是动态的。只�?ESM 能被 Tree Shaking 分析�?2. 标记未使用的导出
    打包工具(如 Webpack)通过分析代码,标记未被任何模块导入的导出�?3. 压缩阶段删除
    结合代码压缩工具(如 Terser)删除这些标记的未使用代码�?
    *�?Webpack 中启�?Tree Shaking 的步�?
    1. 使用 ES Module 语法
    确保项目代码使用 import/export ,而非 CommonJS �?require �?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // �?正确:ESM 导出
    export function add(a, b) {
    return a + b
    }
    export function subtract(a, b) {
    return a - b
    }

    // �?正确:ESM 导入
    import { add } from './math'

    // �?错误:CommonJS 导出
    module.exports = {
    add,
    subtract,
    }

**2. 配置 Webpack �?mode �?production **
�?webpack.config.js 中设�?mode: 'production' ,这会自动启�?Tree Shaking 和代码压缩�?

1
2
3
4
module.exports = {
mode: 'production', // 启用生产模式优化
// ...
}

*3. 禁用模块转换(Babel 配置�?
确保 Babel 不会�?ESM 转换�?CommonJS。在 .babelrc �?babel.config.json 中设置:

1
2
3
4
5
{
"presets": [
["@babel/preset-env", { "modules": false }] // 保留 ESM 语法
]
}

4. 标记副作用文件(可选)
�?package.json 中声明哪些文件有副作用(如全局 CSS、Polyfill),避免被错误删除:

1
2
3
4
5
6
{
"sideEffects": [
"**/*.css", // CSS 文件有副作用(影响样式)
"src/polyfill.js" // Polyfill 有副作用
]
}

若项目无副作用文件,直接设为 false �?

1
2
3
{
"sideEffects": false
}

**5. 显式配置 optimization.usedExports **
�?webpack.config.js 中启�?usedExports ,让 Webpack 标记未使用的导出�?

1
2
3
4
5
module.exports = {
optimization: {
usedExports: true, // 标记未使用的导出
minimize: true, // 启用压缩(删除未使用代码�? },
}

验证 Tree Shaking 是否生效
*方法 1:检查打包后的代�?
若未使用的函数(�?subtract )被删除,说�?Tree Shaking 生效�?

1
2
3
4
5
6
7
8
9
10
11
// 打包�?math.js
export function add(a, b) {
return a + b
}
export function subtract(a, b) {
return a - b
}

// 打包后(仅保�?add�?function add(a, b) {
return a + b
}

*方法 2:使用分析工�?
通过 Webpack Bundle Analyzer 可视化分析打包结果:

1
npm install --save-dev webpack-bundle-analyzer

配置 webpack.config.js �?

1
2
3
4
5
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
plugins: [new BundleAnalyzerPlugin()],
}

运行构建后,浏览器将自动打开分析页面,检查未使用的模块是否被移除�?

步骤 关键配置 作用
使用 ESM 语法 import/export 提供静态分析基础
设置生产模式 mode: 'production' 自动启用 Tree Shaking 和压�?
配置 Babel "modules": false 保留 ESM 结构
标记副作用文�? package.json �?sideEffects 字段 防止误删有副作用的文�?
显式启用 usedExports optimization.usedExports: true 标记未使用的导出

:::

小程�?

小程序是很容易入门和掌握的技术栈,如果你技术栈偏窄,可以考虑补充一下小程序的知识�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

小程序双线程架构

参考答�?
::: details

1. 架构组成

*�?)逻辑层(Service�?

  • 运行环境:独立的 JavaScript 线程(如 JavaScriptCore �?V8 引擎)�?- 职责�? - 处理业务逻辑(数据请求、事件响应、状态管理)�? - 调用小程�?API(如网络请求、本地存储)�? - 通过 setData 向渲染层传递数据�?- 特点�? - 无法直接操作 DOM:与渲染层隔离,避免恶意脚本攻击�? - 单例运行:全局状态统一管理(如 App �?Page 对象)�?
    *�?)渲染层(View�?

  • 运行环境:WebView 线程(每个页面独立实例)�?- 职责�? - 解析 WXML/WXSS,渲染页面结构�? - 处理用户交互事件(点击、滑动),触发逻辑层响应�?- 特点�? - 数据驱动更新:根据逻辑层传递的数据动态渲染�? - **轻量�?*:不执行复杂逻辑,保障渲染流畅性�?
    *�?)系统层(Native�?

  • 作用:作为逻辑层与渲染层的通信桥梁,提供原生能力支持�?- 核心功能�? - JSBridge:序列化传递数据(JSON 格式)�? - 安全管控:拦截非法操作(如直接访�?DOM)�? - 原生 API:调用摄像头、地理位置等硬件功能�?
    wxyl

:::

直接修改 this.data 为何不会触发视图更新�?

参考答�?
::: details

小程序中直接修改 this.data 不会触发视图更新的原因如下:

*1. 数据更新机制的设�?

小程序采�?显式更新 策略,只有通过 this.setData() 方法修改数据时,才会触发以下流程�?

  • 数据变更通知:将修改的数据标记为“脏数据”(需更新)�?- 通信到渲染层:通过 JSBridge 将数据序列化后传递到 WebView 线程�?- **视图差异化更�?*:渲染层对比新旧数据差异,仅更新变化�?DOM 节点�?
    直接修改 this.data 仅改变逻辑层的数据,但 **未触发上述流�?*,因此渲染层无法感知数据变化�?
    2. 双线程架构的限制

小程序的逻辑层(Service)与渲染层(View)运行在独立线程中:

  • **逻辑�?*:通过 JavaScriptCore �?V8 引擎运行�?- **渲染�?*:在 WebView 中解�?WXML/WXSS�?
    两者通过 异步通信(JSBridge)传递数据�?
    直接修改 this.data 不会触发系统层的数据传递,导致渲染层无法同步更新�?
    3. 性能优化考量

若每次数据修改都自动触发更新�?

  • 频繁通信开销:高频数据变更(如循环中修改数据)会导致线程间通信阻塞�?- 不必要的渲染:中间状态的数据变更可能引发多次无效渲染�?
    通过 this.setData() �?批量合并更新机制,可优化性能�?
    1
    2
    3
    // 合并多次更新,仅触发一次通信和渲�?this.setData({ a: 1 })
    this.setData({ b: 2 })
    // 等效�?this.setData({ a: 1, b: 2 })

*4. 数据一致性与安全�?

  • **脏数据风�?*:直接修�?this.data 可能导致逻辑层与渲染层数据不一致�?- **状态管理规�?*:强制使�?this.setData() 确保数据变更可追踪,符合单向数据流原则�?
    :::

setData 底层做了哪些性能优化处理�?

参考答�?
::: details

1. 核心优化机制

(1) 数据通信优化

  • *差异化更新(Diff 算法�?
    对比新旧数据树,仅序列化并传输变化的部分。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      // 旧数据:{ a: 1, list: [{ id: 1 }, { id: 2 }] }
    this.setData({ 'list[1].id': 3 })
    // 实际传输:{ 'list[1].id': 3 }(而非整个 list 数组�? ```

    **优化效果**:减�?60%~80% 的数据传输量�?
    - **序列化过�?*
    自动过滤 `undefined``Function``Symbol` 等不可序列化数据,避免无效通信�?
    **(2) 更新调度优化**

    - **批量合并(Batching�?*
    同一事件循环内的多次 `setData` 调用合并为一次更新:

    ```javascript
    this.setData({ a: 1 })
    this.setData({ b: 2 })
    // 合并�?{ a: 1, b: 2 },触发单次通信

    优化场景:高频操作(如动画帧更新、滚动事件)�?

  • 异步队列与优先级调度
    用户交互触发的更新优先级高于数据请求,优先保障交互流畅性�?
    **(3) 渲染层优�?*

  • *虚拟 DOM 对比(Virtual DOM Diff�?
    生成最小化�?DOM 操作指令,避免全量渲染:

  • *WXS 脚本加�?
    在渲染层直接处理轻量逻辑(如数据格式化),减少逻辑层通信�? ```wxml

    function formatPrice(price) { return '¥' + price; } module.exports = { formatPrice }; {{utils.formatPrice(100)}}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43

    **(4) 通信协议优化**

    - **二进制传输(�?Protocol Buffers�?*
    替代 JSON 序列化,体积减少 30%~50%,解析速度提升 2~5 倍�?- **通道复用与流量控�?*
    复用 JSBridge 通道,避免频繁建立连接,满负荷时自动排队�?
    :::

    ## this.setData({ list: largeDataArray }) 有问题吗�?
    参考答�?
    ::: details

    在小程序开发中,使�?`this.setData({ list: largeDataArray })` 传递一个大型数据数组(尤其是包含成千上万条数据时)**确实存在明显的性能问题**�?
    **1. 核心问题分析**

    **(1) 数据传输瓶颈**

    - **JSBridge 序列化开销**:数据需从逻辑层(Service)序列化�?JSON 字符串,通过 JSBridge 传递到渲染层(View),数据量越大,序列化和传输时间越长�?- **典型耗时**:传�?10,000 条数据(每条 100B)约耗时 **100~300ms**(中低端手机更久)�?
    **(2) 渲染性能问题**

    - **DOM 节点爆炸**:渲染层需解析数据并生成大�?DOM 节点,导致:
    - **内存占用�?*:每�?DOM 节点消�?0.1~1KB 内存�?0,000 条数据可能占�?**1~10MB**�? - **渲染卡顿**:首次渲染或滚动时出现明显卡顿(帧率低于 30fps)�?
    **(3) 频繁 GC(垃圾回收)**

    - **内存抖动**:频繁创建和销毁大型临时对象,触发 JavaScript 引擎垃圾回收,导致间歇性卡顿�?
    **2. 优化方案**

    **(1) 分页加载(懒加载�?*

    - **实现方式**�? ```javascript
    Page({
    data: { list: [], page: 0 },
    onReachBottom() {
    // 滚动到底部加载下一�? this.loadNextPage()
    },
    loadNextPage() {
    const nextPageData = fetchData(this.data.page + 1)
    this.setData({
    list: this.data.list.concat(nextPageData),
    page: this.data.page + 1,
    })
    },
    })
  • 优点:减少单次传输数据量,避免内存峰值�?
    **(2) 虚拟列表(按需渲染�?*

  • 原理:仅渲染可视区域内的元素�?- **实现�?*:使�?wx-component 或第三方库(�?recycle-view)�? ```xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - **优点**:渲�?100 万条数据时,内存占用�?**1~2MB**�?
    **(3) 纯数据字段(Pure Data�?*

    - **适用场景**:需要存储数据但无需渲染的字段�? ```javascript
    Component({
    options: { pureDataPattern: /^_/ },
    data: {
    _fullList: largeDataArray, // 不触发渲�? visibleList: largeDataArray.slice(0, 20),
    },
    })

(4) 数据压缩

  • 精简字段�?

    1
    2
    3
    4
    5
    // 原始数据
    const rawData = [{ id: 1, title: '...', desc: '...' /* 10+ 字段 */ }]

    // 优化�? const optimizedData = rawData.map(({ id, title }) => ({ id, title }))
    this.setData({ list: optimizedData })
  • **压缩�?*:减�?50%~80% 数据体积�?
    (5) WebWorker 计算

1
2
3
4
5
// �?Worker 中处理数�?const worker = wx.createWorker('workers/data-handler.js')
worker.postMessage({ action: 'filter', data: largeDataArray })
worker.onMessage((res) => {
this.setData({ list: res.filteredData })
})

(6) 原生组件替代

  • 使用 <canvas> �?<web-view> 渲染超大数据(如地图、可视化图表)�?
    :::

小程序登录流�?

参考答�?
::: details

wxlogin

说明

  • 调用 wx.login() 获取 临时登录凭证 code,并回传到开发者服务器�?- 调用 auth.code2Session 接口,换�?用户唯一标识 OpenID �?用户在微信开放平台账号下的唯一标识 UnionID(若当前小程序已绑定到微信开放平台账号) �?会话密钥 session_key�?
    之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份�?
    注意事项

  • 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥�?- 临时登录凭证 code 只能使用一次�?
    特殊字段

  • openid:openid 是用来唯一标识用户的一个字符串。在微信小程序中,每个用户的 openid 都是唯一的。通过 openid,小程序可以获取用户的基本信息,如头像、昵称等�?

    【注意】同一个用户在不同的小程序中拥有不同的openid。因此,在开发小程序时,不能使用openid来进行用户的唯一性判断�?

  • unionid:unionid 是在用户绑定同一微信开放平台账号下的多个应用时,用来唯一标识用户的一个字符串。如果用户在多个小程序中使用同一个微信号进行登录授权,那么这些小程序中的 unionid 都是相同的�?

    【注意】用户的 unionid 只有在用户将多个应用绑定到同一个微信开放平台账号下时才会生成。因此,如果用户没有绑定多个应用,那么小程序将无法获取用户的 unionid�?

  • code:code 是用户登录凭证,由微信服务器颁发给小程序。在用户授权登录后,小程序可以通过调用微信登录接口获取用户�?code。然后,通过 code 向微信服务器请求用户�?openid �?session_key 等信息�?

    【注意】每�?code 只能使用一次,且有效期�?5 分钟。因此,在使�?code 进行登录时,需要及时将其转换成用户�?openid �?session_key 等信息,以免出现 code 过期的情况�?
    :::

如何在不发版的情况下实现小程序的 AB 测试�?

参考答�?
::: details

在小程序中实现无需发版�?AB 测试,可通过 动态配�?+ 数据驱动 的方案完成�?

  1. 云端配置管理
  • 创建 AB 测试规则�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 示例配置(存储在云数据库/Redis�?  {
    "experiment_id": "2023_button_color",
    "groups": [
    {
    "name": "group_a",
    "ratio": 50, // 50%流量
    "params": { "button_color": "#FF0000" }
    },
    {
    "name": "group_b",
    "ratio": 50,
    "params": { "button_color": "#00FF00" }
    }
    ],
    "salt": "user_id" // 分流依据
    }
  • **动态更�?*:通过管理后台随时调整分组比例和参�?

  1. 客户端分组逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 工具函数:一致性哈希分�?function getABGroup(experimentId, userId) {
const hash = crypto
.createHash('md5')
.update(experimentId + userId)
.digest('hex')
const value = parseInt(hash.slice(0, 8), 16) % 100
return value < 50 ? 'group_a' : 'group_b' // 按比例分�?}

// 小程序启动时获取配置
wx.cloud.callFunction({
name: 'getABConfig',
success: (res) => {
const userId = getApp().globalData.userId
const group = getABGroup(res.data.experiment_id, userId)
this.setData({ abParams: res.data.groups.find((g) => g.name === group).params })
},
})
  1. *界面动态渲�?
1
2
<!-- WXML 根据配置渲染 -->
<button style="background-color: {{abParams.button_color}};" bindtap="handleClick">立即购买</button>
  1. 数据埋点上报
1
2
3
4
5
6
7
8
// 点击事件处理
handleClick() {
wx.reportAnalytics('button_click', {
experiment_id: '2023_button_color',
group: this.data.abGroup,
button_color: this.data.abParams.button_color
});
}
  1. 数据分析阶段
  • 指标定义�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 示例:计算转化率差异
    SELECT
    group,
    COUNT(DISTINCT user_id) AS total_users,
    SUM(is_converted) / COUNT(DISTINCT user_id) AS conversion_rate
    FROM
    ab_test_events
    WHERE
    experiment_id = '2023_button_color'
    GROUP BY
    group;
  • **统计显著性检�?*:使�?T 检验或卡方检验验证结果可靠�?
    *关键技术细�?

  1. 流量分配算法
  • 分层采样:不同实验独立分流(避免流量干扰�?- Sticky Bucket:确保用户始终处于同一分组
    1
    2
    3
    // 本地缓存分组结果
    const storedGroup = wx.getStorageSync(experimentId)
    if (storedGroup) return storedGroup
  1. *动态更新策�?
  • 定时轮询:每 5 分钟检查配置更�?- **WebSocket 推�?*:实时生效新配置
  1. 灰度发布控制
1
2
3
4
5
6
7
# 云配置示例:分阶段放�?rollout:
- stage: 1
percentage: 10%
start_time: 2025-01-01
- stage: 2
percentage: 100%
start_time: 2025-01-03

:::

小程序的增量更新

参考答�?
::: details

小程序的增量更新机制主要依赖于小程序平台的设�?

  • 当小程序开发者发布新版本时,小程序平台会比较新旧两个版本的差异,并生成一个包含差异信息的补丁文件�?- 然后,当用户打开小程序时,小程序平台会检查用户设备上的小程序版本�?- 如果发现用户的版本落后于服务器上的版本,那么就会下载补丁文件,而不是整个新版本的代码包�?- 接着,小程序平台会应用补丁文件,将用户设备上的小程序更新到新版本�?
    :::

小程序性能优化

参考答�?
::: details

微信 IDE 的小程序评分功能位于调试�?-> Audits 面板�?
小程序性能优化的具体维度:

  1. 避免过大�?WXML 节点数目
  2. 避免执行脚本的耗时过长的情�?3. 避免首屏时间太长的情�?4. 避免渲染界面的耗时过长的情�?5. 对网络请求做必要的缓存以避免多余的请�?6. 所有请求的耗时不应太久
  3. 避免 setData 的调用过于频�?8. 避免 setData 的数据过�?9. 避免短时间内发起太多的图片请�?10. 避免短时间内发起太多的请�?
    :::

小程�?WXSS �?CSS 的区别?

参考答�?
::: details

  • wxss 背景图片只能引入外链,不能使用本地图�?- 小程序样式使�?@import 引入 外联样式文件,地址为相对路径�?- 尺寸单位�?rpx , rpx 是响应式像素,可以根据屏幕宽度进行自适应�?
    :::

小程序里拿不�?dom 相关�?api �?

参考答�?
::: details

微信小程序使用类�?Web �?WXML �?WXSS 语言来描述页面结构和样式,但并不提供直接操作 DOM �?API。这主要有两个原因:

  • 首先,小程序运行�?JavaScriptCore 引擎中,而非浏览器中常见�?V8 引擎�?
    • 由于两者在实现方式上存在较大差异,JavaScriptCore 的执行速度相对较慢�? - 直接操作 DOM 会增加耗时,从而降低性能和用户体验�?
  • 其次,小程序的设计初衷是提供一种轻量、快速启动的应用模式,其定位强调“去中心化、低门槛、高灵活性”�? - 如果允许开发者直接操�?DOM,可能会破坏这一设计理念,增加系统复杂度和开发难度�?
    :::

分包加载

参考答�?
::: details

小程序分包加载是一种优化技术,用于解决主包体积过大导致的首次加载性能问题。通过将非核心功能模块拆分为独立分包,实现按需加载和动态加载�?
一、分包加载核心概�?

  1. *包类�?
类型 说明 特点
主包 包含启动页面、核心公共组件和基础�? 用户首次打开小程序时必须下载
*普通分�? 依赖主包的功能模块,按需加载 可访问主包资�?
独立分包 不依赖主包的完整功能模块,可独立运行 无法访问主包资源
  1. 体积限制
*包类�? *最大体�? 总包体积限制
主包 2MB 20MB (所有分包总和)
单个普通分�? 2MB
单个独立分包 2MB

二、分包配置实�?

  1. 目录结构
1
2
3
4
5
6
7
8
9
├── app.js               # 主包入口
├── app.json # 分包配置
├── subpackages # 分包目录
�? ├── user-center # 普通分�?�? �? ├── pages
�? �? └── components
�? └── shop # 独立分包
�? ├── app.js # 独立分包入口
�? └── pages
└── common # 公共代码(主包)
  1. app.json 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"pages": ["pages/index/index"], // 主包页面
"subpackages": [
{
"root": "subpackages/user-center",
"name": "user",
"pages": ["profile", "settings"],
"independent": false // 普通分�? },
{
"root": "subpackages/shop",
"name": "shop",
"pages": ["home", "detail"],
"independent": true // 独立分包
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["user"] // 预加载普通分�? }
}
}

三、分包加载策�?

  1. 按需加载
  • **普通分�?*:当用户首次访问分包内页面时触发下载
  • 独立分包:通过 wx.navigateTo 指定 isIndependent 参数加载
    1
    2
    3
    4
    wx.navigateTo({
    url: '/subpackages/shop/pages/home',
    isIndependent: true,
    })
  1. *预加载优�?
1
2
3
4
5
6
// app.json 预加载配�?"preloadRule": {
"pages/index/index": {
"packages": ["user"], // 预加载分包名
"network": "wifi" // 仅WiFi下预加载
}
}

策略建议�?

  • 预加载不超过 *2�? 分包
  • 仅预加载用户可能访问的高频分�?
  1. *懒加载配�?
1
2
3
4
// 点击事件触发加载
onTapShop() {
require('../../subpackages/shop/shop.js'); // 动态导�? wx.navigateTo({ url: '/subpackages/shop/pages/home' });
}

:::

冷启动与热启动的区别

参考答�?
::: details

核心区别对比

对比维度 *冷启�? *热启�?
触发条件 首次打开或销毁后重新打开 后台存活状态下重新唤醒
资源加载 重新下载代码包、初始化页面 直接从内存恢复页�?
启动速度 较慢(需完整加载�? 较快(无需重新初始化)
生命周期流程 执行 App.onLaunch �?Page.onLoad 仅触�?App.onShow �?Page.onShow
内存占用 重新分配内存 复用原有内存
*数据状�? 全局数据需重新初始�? 保留之前的运行状�?
存活时间 无限�? 默认后台存活 5分钟
典型场景 用户首次打开或主动杀死进程后重启 切换回微信聊天后重新进入

*冷启动优化方�?

  1. **代码包瘦�?*�?

    • 主包控制�?2MB 以内
    • 使用分包加载(单个分�?�?MB�?
      1
      2
      3
      4
      5
      6
      7
      // app.json 分包配置
      {
      "subpackages": [{
      "root": "subpackage",
      "pages": ["pageA", "pageB"]
      }]
      }
  2. **预加载策�?*�?

    1
    2
    3
    4
    // 提前加载非首屏必要资�?   wx.loadSubpackage({
    name: 'subpackage',
    success: () => console.log('分包预加载完�?),
    })
  3. 缓存关键数据�? ```javascript
    // 冷启动时读取缓存
    App({
    onLaunch() {
    const cache = wx.getStorageSync(‘userInfo’)
    if (cache) this.globalData.userInfo = cache
    },
    })

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    **热启动优化方�?*

    1. **状态保�?*�?
    ```javascript
    // 页面隐藏时保存状�? Page({
    onHide() {
    wx.setStorageSync('pageState', this.data)
    },
    })
  4. 内存管理�?

    • 避免在全局对象中存储过大数�? - 及时清理无用定时�?事件监听
    1
    2
    3
    4
    5
    6
    // 页面卸载时清理资�?   Page({
    onUnload() {
    clearInterval(this.timer)
    this.eventListener.close()
    },
    })
  5. 后台保活策略�? ```javascript
    // 播放背景音频延长存活时间
    wx.playBackgroundAudio({
    dataUrl: ‘silent.mp3’, // 无声音频
    })

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    **异常场景处理**

    | **场景** | **冷启动表�?* | **热启动表�?* |
    | ---------------- | -------------- | ------------------------ |
    | **代码更新** | 强制下载新包 | 下次冷启动生�? |
    | **网络中断** | 可能导致白屏 | 已加载内容仍可操�? |
    | **内存不足** | 正常启动 | 可能被系统回收转为冷启动 |
    | **全局数据变更** | 重新初始�? | 保持最后一次修改�? |

    **调试技�?*

    1. **强制冷启�?*�?
    ```javascript
    // 开发阶段模拟冷启动
    wx.reLaunch({ url: '/pages/index' })
  6. **内存状态检�?*�?

    1
    2
    3
    // 查看当前内存占用
    console.log(wx.getPerformance())
    // 输出: { memory: 1024, ... }
  7. 生命周期追踪�? ```javascript
    // 监听所有生命周期事�? const originalOnShow = Page.prototype.onShow
    Page.prototype.onShow = function () {
    console.log(‘Page.onShow triggered’)
    originalOnShow.call(this)
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    通过理解�?热启动的差异,开发者可针对性优化小程序性能,建议将 **冷启动耗时控制�?1.5 秒内**�?*热启动耗时控制�?300 毫秒�?*,以达到最佳用户体验�?
    :::

    ## 组件通信方案

    参考答�?
    ::: details

    一�?*父子组件通信**

    1. �?�?子:Properties 传�?
    ```javascript
    // 父组�?;<child-comp prop-data="{{parentData}}" />

    // 子组�?properties 定义
    Component({
    properties: {
    propData: { type: Object, value: {} },
    },
    })
  8. �?�?父:自定义事�?

    1
    2
    3
    4
    5
    6
    7
    8
    // 子组件触发事�?this.triggerEvent('customEvent', { value: data })

    // 父组件监�?<child-comp bind:customEvent="handleEvent" />
    Page({
    handleEvent(e) {
    console.log(e.detail.value)
    }
    })

二�?逆向父组件访�?

  1. 获取父组件实�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 父组件设�?id
    ;<child-comp id="childRef" />

    // 父组件通过 selectComponent 获取
    Page({
    getChild() {
    const child = this.selectComponent('#childRef')
    child.childMethod() // 调用子组件方�? },
    })

三�?兄弟组件通信*

  1. 共同父组件中�?

    1
    2
    3
    4
    5
    graph LR
    A[父组件] --> B[子组件A]
    A --> C[子组件B]
    B -- 事件 --> A
    A -- 更新数据 --> C
  2. 全局事件总线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app.js 中创建事件中�?App({
eventBus: {
listeners: {},
on(event, fn) {
/* 监听 */
},
emit(event, data) {
/* 触发 */
},
},
})

// 组件 A 发�?const app = getApp()
app.eventBus.emit('update', data)

// 组件 B 接收
Component({
attached() {
app.eventBus.on('update', this.handleUpdate)
},
})

四�?跨层级通信*

  1. 全局状态管�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // app.js 定义共享数据
    App({
    globalData: {
    userInfo: null,
    },
    })

    // 任意组件读取/写入
    const app = getApp()
    app.globalData.userInfo = { name: 'John' }

    // 监听变化(需手动实现�?let observer = null
    Component({
    attached() {
    observer = setInterval(() => {
    console.log(app.globalData.userInfo)
    }, 500)
    },
    detached() {
    clearInterval(observer)
    },
    })
  2. 页面间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// PageA 跳转传参
wx.navigateTo({
url: '/pages/pageB?id=123',
})

// PageB 获取参数
Page({
onLoad(options) {
console.log(options.id) // 123
},
})

// 返回传参(需配合 getCurrentPages�?const pages = getCurrentPages()
const prevPage = pages[pages.length - 2]
prevPage.setData({ feedback: 'success' })

五�?高级通信模式*

  1. 组件关系 (relations)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// parent.json
{
"component": true,
"usingComponents": {
"child-comp": "../child/child"
},
"relations": {
"../child/child": {
"type": "child",
"linked(target) { /* 子组件插入时 */ }"
}
}
}

// parent.js
methods: {
broadcastToChildren(data) {
this.children.forEach(child => {
child.receiveData(data)
})
}
}

// child.js
methods: {
receiveData(data) {
this.setData({ received: data })
}
}

*六、其它第三方�?

  1. 对于超大型项目,建议结合 Vuex �?MobX 等状态管理库(需使用 uni-app/Taro 等框架)�?
    方案对比
方案 适用场景 优点 缺点
Properties 父子简单数据传�? 官方推荐,类型校�? 单向数据�?
自定义事�? 子向父传递操�? 解耦合 多层传递较复杂
selectComponent 父直接调用子方法 快速直�? 破坏封装�?
全局事件总线 任意组件间通信 灵活度高 需手动管理监听/卸载
全局状�? 跨页面共享数�? 集中管理 非响应式,需手动监听
页面路由传参 页面间简单数据传�? 官方支持 数据类型受限
relations 存在逻辑关联的组�? 官方关系管理 配置较复�?

*最佳实践建�?

  1. 优先选择官方方案:对于父子通信,务必使�?properties + triggerEvent
  2. 复杂场景组合使用:全局状态管�?+ 事件总线应对跨层级通信
  3. **性能优化关键�?*�? - 避免�?globalData 中存储过大数�? - 使用 debounce 控制高频事件触发
    1
    2
    3
    4
    5
    6
    7
    8
    // 防抖处理示例
    let timer
    function emitDebounced(event, data) {
    clearTimeout(timer)
    timer = setTimeout(() => {
    app.eventBus.emit(event, data)
    }, 300)
    }
  4. 内存泄漏防范�? ```javascript
    Component({
    detached() {
    // 必须移除全局监听
    app.eventBus.off(‘update’, this.handleUpdate)
    },
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
       :::

    ## wx:if �?hidden 的区�?
    参考答�?
    ::: details

    小程序中 `wx:if` �?`hidden` 的区别主要体现在 **渲染机制**�?*性能影响** �?**使用场景** 上�?
    **1. 核心区别**
    | **特�?* | **wx:if** | **hidden** |
    |-------------------------|-----------------------------------------------|---------------------------------------------|
    | **渲染机制** | 条件�?`true` 时渲染组件,否则 **不渲�?* | 始终渲染组件,通过 `display: none` **隐藏** |
    | **DOM 结构** | 条件不满足时 **移除组件节点** | 组件节点 **始终存在**,仅样式隐藏 |
    | **生命周期** | 切换时触�?`attached`/`detached` 生命周期 | 无生命周期触发,仅样式变�?|
    | **状态保�?* | 条件切换�?**状态重�?*(如输入框内容清空) | 隐藏�?**保留状�?*(如输入框内容不变) |
    | **性能开销** | 适合 **低频切换**(减少初始渲染节点) | 适合 **高频切换**(避免重复创�?销毁节点) |
    | **使用语法** | 支持 `wx:if`/`wx:elif`/`wx:else` 链式条件判断 | 仅接受布尔值(`hidden="{{condition}}"`�?|

    **2. 使用场景对比**

    **(1) 推荐使用 `wx:if` 的场�?*

    - **初始不渲�?*:页面加载时不需要显示的组件(减少首屏节点数)�?- **复杂条件判断**:需要多分支逻辑(如 `wx:elif`)�? ```html
    <view wx:if="{{score >= 90}}">优秀</view>
    <view wx:elif="{{score >= 60}}">及格</view>
    <view wx:else>不及�?/view>
  • 大数据量组件:如长列表,避免隐藏时占用内存�?
    **(2) 推荐使用 hidden 的场�?*

  • 高频切换:如 Tab 切换、模态框显示隐藏�? ```html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    - **状态保�?*:如表单输入中途需要临时隐藏�?- **动画控制**:通过 CSS 过渡实现显示/隐藏动画�?
    **3. 性能优化指南**
    | **场景** | **选择方案** | **理由** |
    |------------------------|-------------------|--------------------------------------------|
    | 首屏隐藏的大组件 | `wx:if` | 减少初始渲染节点,提升加载速度 |
    | 频繁切换的组�?| `hidden` | 避免重复创建/销毁,减少性能开销 |
    | 需要保留状态的表单 | `hidden` | 隐藏时保留输入内�?|
    | 多条件分支渲�?| `wx:if` | 语法支持更灵�?|

    **4. 底层原理**

    - **wx:if**�? 通过 Virtual DOM 动态添�?删除组件节点,触发完整生命周期�?
    - **hidden**�? 仅修�?CSS �?`display` 属性,不涉及节点操作�? ```javascript
    // 伪代码实�? component.setStyle({
    display: condition ? 'block' : 'none',
    })

    :::

Taro/Uni-app 跨端原理对比

参考答�?
::: details

框架 技术栈 微信小程�? H5 App 支付�?百度小程�?
Taro React/Vue �? �? �? �?
uni-app Vue �? �? �? �?
WePY Vue �? �? �? �?
mpvue Vue �? �? �? �?

1. Taro

京东凹凸实验�?
*优缺�?

  • Taro �?App 端使用的�?React Native 的渲染引擎,原生�?UI 体验较好,但据说在实时交互和高响应要求的操作方面不是很理想�? 微信小程序方面,结合度感觉没有那么顺滑,有一些常见功能还是需要自己去封装�?- 另外就是开发环境难度稍高,需要自己去搭建 iOS �?Android 的环境,对于想要一处开发到处应用的傻瓜式操作来讲,稍显繁琐�?- �?Taro 3 的出现,支持�?React �?Vue 两种 DSL,适合的人群会更多一点,并且对快应用的支持也更好�?
    2. uni-app

DCloud

*优缺�?

  • uni-app �?App 渲染方面,提供了原生渲染引擎和小程序引擎的双选方案,加上自身的一些技术优化(renderjs),对于高性能和响应要求的场景展现得更为流畅�?- 另外它整体的开发配套流程也做得很容易上手。比如有丰富的插件市场,使用简单,支持大量常用场景�?- 还比如它的定�?IDE——HBuilder,提供了强大的整合能力。在�?HBuilder 之前,我心想:“还要多装一个编辑器麻烦,再好用能有 VS Code 好用?”用过之后:“真香!�?- 虽然用惯�?VS Code 对比起来还是有一些痛点没有解决,但是对于跨平台开发太友好了,其他缺点都可以忍受。HBuilder 里支持直接跳转到微信开发者工具调试,支持真机实时预览,支持直接打包小程序和App,零门槛上手�?
    :::

Taro 的实现原�?

参考答�?
::: details

一、JSX 转换:Taro 通过 自定�?Babel/TypeScript 编译�?�?JSX 转换为通用虚拟 DOM。针对不同前端框架(React/Vue),在编译时生成对应框架的运行时代码,例如:

1
2
3
4
5
6
7
8
9
10
// 输入
;<View>Hello</View>

// React 输出
import { createElement } from 'react'
createElement('view', {}, 'Hello')

// Vue 输出
import { h } from 'vue'
h('view', {}, 'Hello')

二、多端适配:Taro 的核心架构分�?编译�?�?运行时�?

  1. 编译时:通过 AST 解析将代码按目标平台转换,生成平台专属模板(�?.wxml / .swan�?2. 运行时:
  • 实现 统一 API 层(�?Taro.request 映射�?wx.request / my.request�?- 提供 虚拟 DOM 渲染器,通过 React Reconciler 对接不同平台渲染引擎
  • 实现 事件系统桥接,统一各端事件差异

三、跨端样式处理:Taro 样式处理包含以下关键机制�?

  1. 条件编译:通过 CSS 注释实现多平台样式隔�?

    1
    2
    3
    4
    5
    /* #ifdef weapp */
    .title {
    color: red;
    }
    /* #endif */
  2. 单位转换:将 px 按比例转为目标平台单位(如小程序 rpx�?3. 作用域隔离:通过 CSS Modules 自动生成唯一类名

  3. JavaScript 样式:支�?styled-components �?CSS-in-JS 方案

四、构建系统:Taro 的构建系统特点:

  1. 插件化架构:通过 @tarojs/plugin- 前缀插件扩展功能
  2. 多编译引擎:
  • Web 端:仍使�?Webpack/Vite
  • 小程序:自研模板生成�?- 按需编译:通过 Tree-shaking 仅打包使用到的组�?
    五、运行时性能优化�?
  1. 数据通信优化�?
  • 自动合并 setData 调用
  • 使用 差异更新算法 减少数据传输�?
  1. 渲染优化�?
  • 虚拟 DOM 比对后批量更�?- 组件按平台实现懒加载
  1. 包体积优化:
  • 按目标平台裁剪无用代�?- 使用 分包加载 控制主包大小

:::

Nodejs 面试�?

�?Node.js 的理�?

参考答�?
::: details
Node.js 是一个基�?Chrome V8 引擎的开源、跨平台�?JavaScript 运行时环境。它具有以下核心特点�?

  1. 运行环境:让 JavaScript 可以在浏览器之外运行,使其成为一个服务器端的运行环境

  2. 非阻�?I/O�?

    • 采用非阻塞型 I/O 机制
    • 执行 I/O 操作时不会造成阻塞
    • 操作完成后通过事件通知执行回调函数
    • 例如:执行数据库操作时,不需要等待数据返回,而是继续执行后续代码,数据库返回结果后再通过回调函数处理
  3. 事件驱动�? - 基于事件循环(Event Loop�? - 新请求会被压入事件队�? - 通过循环检测队列中的事件状态变�? - 当检测到状态变化,执行对应的回调函�?
    :::

Node.js 的优缺点

参考答�?
::: details
*优点�?

  1. 高并发处理能力强

  2. 适合 I/O 密集型应�?3. 事件驱动非阻塞模式,程序执行效率�?4. 使用 JavaScript,前后端可以使用同一种语言

  3. npm 生态系统非常强�?
    *缺点�?

  4. 不适合 CPU 密集型应�?2. 单线程模式,无法充分利用多核 CPU

  5. 可靠性相对较低,一旦出现未捕获的异常,整个程序可能崩溃

  6. 回调函数嵌套多时可能产生回调地狱

:::

Node.js 应用场景

参考答�?
::: details
最适合的场景:

  1. I/O 密集型应�?2. 实时交互应用

  2. 高并发请求处�?
    *具体应用领域�?

  3. Web 应用系统

    • 后台管理系统
    • 用户表单收集系统
    • 考试系统
    • 高并�?Web 应用
  4. 实时通讯应用

    • 在线聊天�? - 实时通讯系统
    • 图文直播系统
    • WebSocket 应用
  5. 接口服务

    • RESTful API 服务
    • 数据库操作接�? - 前端/移动�?API 服务
  6. *工具类应�?

    • 构建工具(如 webpack�? - 开发工�? - 自动化脚�?
  7. *微服�?

    • 轻量级微服务
    • 中间层服务(BFF�?
      注意:虽�?Node.js 理论上可以开发各种应用,但在选择使用时应该考虑其是否适合特定场景,特别是需要避免在 CPU 密集型场景中使用�?
      :::

Node.js 的全局对象有哪些?

参考答�?
::: details

�?Node.js 中,全局对象与浏览器环境不同。浏览器中的全局对象�?window,�?Node.js 中的全局对象�?global。需要注意的是,�?Node.js 模块中使�?var 声明的变量并不会成为全局变量,它们只在当前模块生效�?
Node.js 的全局对象可以分为两类�?

  1. 真正的全局对象
  2. 模块级别的全局变量

真正的全局对象

  1. *Buffer �?

    • 用于处理二进制数�? - �?V8 堆外分配物理内存
    • 创建后大小固定,不可更改
    • 常用于文件操作、网络操作等场景
  2. process

    • 提供当前 Node.js 进程信息
    • 常用属性和方法�? - process.env:环境变�? - process.argv:命令行参数
      • process.cwd():当前工作目�? - process.pid:进�?ID
      • process.platform:运行平�?
  3. console

    • console.log():标准输�? - console.error():错误输�? - console.trace():打印调用栈
    • console.time()/timeEnd():计时器
    • console.clear():清空控制台
  4. *定时器函�?

    • setTimeout()/clearTimeout()
    • setInterval()/clearInterval()
    • setImmediate()/clearImmediate()
    • process.nextTick()
  5. global

    • 全局命名空间对象
    • 上述所有全局对象都是 global 的属�?
      模块级别的全局变量

这些变量虽然看起来是全局的,但实际上是每个模块独有的�?

  1. __dirname

    • 当前模块的目录名
    • 绝对路径
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       console.log(__dirname) // 输出�?当前目录的绝对路�?   ```

    2. **\_\_filename**

    - 当前模块的文件名
    - 包含绝对路径

    ```js
    console.log(__filename) // 输出�?当前文件的绝对路�?文件�? ```

    3. **exports**

    - 模块导出的快捷方�? - `module.exports` 的引�?
    ```js
    exports.myFunction = () => {}
  2. module

    • 当前模块的引�? - 包含模块的元数据
    1
    2
    module.exports = {
    // 导出的内�? }
  3. require

    • 用于导入模块
    • 可导入的内容�? - Node.js 核心模块
      • 第三方模�? - 本地文件
        1
        2
        const fs = require('fs')
        const myModule = require('./myModule')

注意事项

  1. 模块级全局变量�?REPL(命令行交互)环境中不可�?2. exports �?module.exports 的引用,不能直接赋�?3. Node.js 12 之后,还可以使用 globalThis 访问全局对象
  2. 某些全局对象在特定版本可能有变化,使用时需注意 Node.js 版本兼容�?
    :::

Node.js 事件循环机制

参考答�?
::: details

事件循环�?Node.js 实现异步操作的核心机制,它允�?Node.js 执行非阻�?I/O 操作。Node.js 是单线程的,但通过事件循环机制可以实现高并发�?
*事件循环的六个阶�?

事件循环按照固定的顺序,循环执行以下六个阶段�?

  1. *timers(定时器阶段�?

    • 执行 setTimeout �?setInterval 的回�? - 检查是否有到期的定时器
  2. pending callbacks(待定回调阶段)

    • 执行延迟到下一个循环迭代的 I/O 回调
    • 处理一些系统操作的回调(如 TCP 错误�?
  3. *idle, prepare(仅系统内部使用�?

    • 系统内部使用,不需要关�?
  4. poll(轮询阶段)

    • 检索新�?I/O 事件
    • 执行 I/O 相关的回�? - 如果有必要会阻塞在这个阶�?
  5. check(检查阶段)

    • 执行 setImmediate() 的回�? - �?poll 阶段结束后立即执�?
  6. close callbacks(关闭回调阶段)

    • 执行关闭事件的回�? - �?socket.on('close', ...)

*微任务和宏任�?

在事件循环的每个阶段之间,会检查并执行微任务:

微任务(Microtasks):

  • process.nextTick()(优先级最高)
  • Promise.then/catch/finally
  • queueMicrotask()

宏任务(Macrotasks):

  • setTimeout
  • setInterval
  • setImmediate
  • I/O 操作

执行顺序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
console.log('1: 同步代码')

setTimeout(() => {
console.log('2: setTimeout')
}, 0)

Promise.resolve().then(() => {
console.log('3: Promise')
})

process.nextTick(() => {
console.log('4: nextTick')
})

setImmediate(() => {
console.log('5: setImmediate')
})

// 输出顺序�?// 1: 同步代码
// 4: nextTick
// 3: Promise
// 2: setTimeout
// 5: setImmediate

注意事项

  1. *process.nextTick 的特殊�?

    • 不属于事件循环的任何阶段
    • 在每个阶段结束时优先执行
    • 过度使用可能导致 I/O 饥饿
  2. *定时器的精确�?

    • setTimeout �?setInterval 的延时不能保证精�? - 受进程繁忙程度影�?
  3. setImmediate vs setTimeout(fn, 0)

    • 主模块中执行顺序不确�? - I/O 回调�?setImmediate 优先级更�?
  4. 异步错误处理

    • 推荐使用 async/await �?try/catch
    • 避免回调地狱

*最佳实�?

  1. 避免在关键任务中依赖定时器的精确�?2. 合理使用 process.nextTick,避免阻塞事件循�?3. I/O 操作中优先使�?setImmediate 而不�?setTimeout
  2. 使用 Promise �?async/await 处理异步操作
  3. 注意内存泄漏,及时清理不需要的事件监听�?
    :::

Node.js 中的 process 对象

参考答�?
::: details

process �?Node.js 中的一个全局对象,它提供了当�?Node.js 进程的信息和控制能力。作为进程,它是计算机系统进行资源分配和调度的基本单位,具有以下特点�?

  • 每个进程都拥有独立的空间地址和数据栈
  • 进程间数据隔离,需通过进程间通信机制实现数据共享
  • Node.js 是单线程的,启动一个文件会创建一个主线程

常用属性和方法

  1. 系统信息相关
  • process.env:环境变量对�?

    1
    console.log(process.env.NODE_ENV) // 获取环境变量
  • process.platform:运行平�?

    1
    console.log(process.platform) // 'darwin' for macOS
  • process.version:Node.js 版本

    1
    console.log(process.version) // 'v16.x.x'
  1. 进程信息相关
  • process.pid:当前进�?ID
  • process.ppid:父进程 ID
  • **process.uptime()**:进程运行时�?- process.title:进程名�? ```js
    console.log(process.pid) // 进程ID
    process.title = ‘my-app’ // 设置进程标题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    3. 路径与命令行

    - **process.cwd()**:当前工作目�?
    ```js
    console.log(process.cwd()) // 返回当前工作目录的绝对路�? ```

    - **process.argv**:命令行参数
    ```js
    // node app.js --port 3000
    const args = process.argv.slice(2) // ['--port', '3000']
  1. 事件循环相关
  • **process.nextTick(callback)**:下一个事件循环触发回�? ```js
    process.nextTick(() => {
    console.log(‘下一个事件循环执�?)
    })
    1
    2
    3
    4

    5. 标准流操�?
    - **process.stdout**:标准输�?- **process.stdin**:标准输�?- **process.stderr**:标准错�? ```js
    process.stdout.write('Hello World\n')
  1. 事件监听
  • 进程异常处理

    1
    2
    3
    process.on('uncaughtException', (err) => {
    console.error('未捕获的异常�?, err)
    })
  • *进程退出监�?

    1
    2
    3
    process.on('exit', (code) => {
    console.log(`进程退出码�?{code}`)
    })

使用注意事项

  1. *process.nextTick �?setTimeout 的区�?

    • process.nextTick 在当前事件循环结束时执行
    • setTimeout(fn, 0) 在下一个事件循环开始时执行
    • nextTick 优先级更�?
  2. *环境变量的使�?

    1
    2
    // 推荐使用
    const NODE_ENV = process.env.NODE_ENV || 'development'
  3. 工作目录

    • process.cwd() 返回 Node.js 进程执行时的工作目录
    • �?__dirname 不同,process.cwd() 可能会随着工作目录的改变而改�?
  4. 异常处理

    • 建议使用 uncaughtException 捕获未处理的异常
    • 但不建议用它来代替正常的错误处理流程

:::

Express middleware(中间�? 工作原理

参考答�?
::: details

中间件(Middleware)是 Express 的核心概念,它是一个函数,可以访问请求对象(req)、响应对象(res)和应用程序请求-响应周期中的下一个中间件函数(next)�?
工作流程

  1. 请求处理流程

    • 请求从上到下依次经过中间�? - 每个中间件可以对请求进行处理和修�? - 通过 next() 将请求传递给下一个中间件
    • 如果不调�?next(),请求将终止
  2. 基本结构

1
2
3
4
5
function middleware(req, res, next) {
// 1. 处理请求
// 2. 修改请求或响应对�? // 3. 调用 next() 传递给下一个中间件
next()
}

*中间件分�?

  1. 应用级中间件
1
2
3
4
5
6
7
8
9
10
11
const app = express()

// 全局中间�?app.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})

// 路由特定中间�?app.use('/user', (req, res, next) => {
console.log('Request Type:', req.method)
next()
})
  1. 路由级中间件
1
2
3
4
5
6
const router = express.Router()

router.use((req, res, next) => {
console.log('Router Specific Middleware')
next()
})
  1. *错误处理中间�?
1
2
3
4
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})

执行顺序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.use((req, res, next) => {
console.log('1. First Middleware')
next()
})

app.use((req, res, next) => {
console.log('2. Second Middleware')
next()
})

app.get('/api', (req, res) => {
console.log('3. Route Handler')
res.send('Hello')
})

// 访问 /api 时的输出�?// 1. First Middleware
// 2. Second Middleware
// 3. Route Handler

*中间件特�?

  1. *顺序重要�?

    • 中间件的注册顺序决定了执行顺�? - 错误处理中间件应该放在最�?
  2. *功能独立�?

    • 每个中间件负责特定功�? - 可以组合使用多个中间�?
  3. 请求响应周期

    • 可以修改请求和响应对�? - 可以终止请求-响应周期
    • 可以调用下一个中间件

常见使用场景

  1. 请求日志记录
1
2
3
4
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`)
next()
})
  1. 身份验证
1
2
3
4
5
6
7
function authenticate(req, res, next) {
if (req.headers.authorization) {
next()
} else {
res.status(401).send('Unauthorized')
}
}
  1. 数据处理
1
app.use(express.json()) // 解析 JSON 请求�?app.use(express.urlencoded({ extended: true })) // 解析 URL 编码的请求体

*最佳实�?

  1. 合理使用 next()

    • 除非终止请求,否则总是调用 next()
    • 在异步操作中正确处理 next()
  2. 错误处理

    • 使用 try-catch 捕获同步错误
    • 使用 Promise 处理异步错误
    • 通过 next(error) 传递错�?
  3. *中间件设�?

    • 保持中间件功能单一
    • 适当使用路由级中间件
    • 避免中间件中的副作用

:::

Koa 洋葱模型

参考答�?
::: details

Koa 的中间件模型被称为"洋葱模型",这是因为请求和响应像洋葱一样,需要经过多�?表皮”(中间件)的处理。这个过程是�?

  • 请求从外到内依次经过中间件的前置处理
  • 到达最里层�?- 响应从内到外依次经过中间件的后置处理

工作原理

  1. 执行流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const Koa = require('koa')
const app = new Koa()

// 中间�?
app.use(async (ctx, next) => {
console.log('1. 进入中间�?')
await next()
console.log('5. 离开中间�?')
})

// 中间�?
app.use(async (ctx, next) => {
console.log('2. 进入中间�?')
await next()
console.log('4. 离开中间�?')
})

// 中间�?
app.use(async (ctx) => {
console.log('3. 到达中间�?')
ctx.body = 'Hello World'
})

// 输出顺序�?// 1. 进入中间�?
// 2. 进入中间�?
// 3. 到达中间�?
// 4. 离开中间�?
// 5. 离开中间�?

特点说明

  1. 异步处理

    • 通过 async/await 实现异步操作的同步写�? - 每个中间件都可以等待下一个中间件执行完成
  2. 双向流动

    • 请求阶段:从外到�? - 响应阶段:从内到�? - 可以在响应阶段对数据进行再处�?
  3. 错误处理

1
2
3
4
5
6
7
8
9
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.status || 500
ctx.body = err.message
ctx.app.emit('error', err, ctx)
}
})

实际应用示例

  1. 日志记录
1
2
3
4
5
6
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
  1. 响应处理
1
2
3
4
5
6
7
8
9
10
11
app.use(async (ctx, next) => {
await next()
// 响应阶段可以修改返回数据
if (ctx.body) {
ctx.body = {
code: 0,
data: ctx.body,
message: 'success',
}
}
})

:::

Koa �?Express 的区�?

参考答�?
::: details

  1. *中间件机�?

    • Express:单向流动,中间件通过 next() 线性执行,一旦响应结束就不能修改
    • Koa:洋葱模型,中间件既可以处理请求也可以处理响应,支持统一的错误处�?
  2. 异步处理

    • Express:基于回调函数,容易陷入回调地狱,异步错误处理相对复�? - Koa:基�?Promise �?async/await,代码更简洁,异步流程控制更直�?
  3. *上下文对�?

    • Express:req �?res 是分离的对象,功能相对分�? - Koa:ctx 统一上下文,封装�?request �?response,API 设计更简洁优�?
  4. 功能内置

    • Express:内置了很多中间件,功能齐全,开箱即�? - Koa:核心功能精简,需要通过第三方中间件扩展,更加灵�?
  5. 路由系统

    • Express:内置了强大的路由系统,支持链式调用
    • Koa:路由需要通过第三方中间件实现(如 koa-router�?
  6. *社区生�?

    • Express:历史更悠久,社区更成熟,资源更丰富
    • Koa:较新但发展迅速,设计更现代,适合新项�?
  7. 错误处理

    • Express:通过特殊的错误处理中间件,需要手动传递错�? - Koa:通过 try/catch 优雅地处理错误,统一的错误处理更方便
  8. 适用场景

    • Express:适合快速开发,现有项目迁移,团队熟悉度�? - Koa:适合追求优雅代码,需要更好的异步流程控制的场�?
      :::

未完待续…

React 原理

国内面试,大厂必考原理�?
::: tip

  1. 目标不在中大厂的同学,可以略过这一节�?2. �?React 使用尚不熟练的同学,不要在此花费太多精力,先熟悉使用再说�?
    :::

::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

JSX 的本质是什么?

参考答�?
::: details

*JSX(JavaScript XML�? 是一�?JavaScript 的语法扩展,允许�?JavaScript 代码中通过�?HTML 语法创建 React 元素。它需要通过 Babel 等工具编译为标准�?JavaScript 代码,最终生�?React 元素对象(React Element),这些元素共同构成虚拟 DOM(Virtual DOM)树�?
核心原理

  1. JSX 编译�?React 元素
    JSX 会被转换�?React.createElement() 调用(或 React 17+ �?_jsx 函数),生成描述 UI 结构的对象(React 元素),而非直接操作真实 DOM�?

    1
    2
    3
    4
    5
    6
    7
    // JSX
    const element = <h1 className="title">Hello, world!</h1>

    // 编译后(React 17 之前�? const element = React.createElement('h1', { className: 'title' }, 'Hello, world!')

    // 编译后(React 17+,自动引�?_jsx�? import { jsx as _jsx } from 'react/jsx-runtime'
    const element = _jsx('h1', { className: 'title', children: 'Hello, world!' })
  2. *虚拟 DOM 的运�?

    • React 元素组成虚拟 DOM 树,通过 Diff 算法对比新旧树差异,最终高效更新真�?DOM�? - 虚拟 DOM 是内存中的轻量对象,避免频繁操作真实 DOM 的性能损耗�?
      *JSX 的核心特�?
  3. *�?HTML 语法�?JavaScript 的融�?

    • **表达式嵌�?*:通过 {} 嵌入 JavaScript 表达式(如变量、函数调用、三元运算符):
      1
      2
      const userName = 'Alice'
      const element = <p>Hello, {userName.toUpperCase()}</p>
    • 禁止语句{} 内不支持 if/for 等语句,需改用表达式(如三元运算符或逻辑与)�? ```jsx
      {isLoggedIn ? 'Welcome' : 'Please Login'}
      1
      2
      3
      4
      5
      6
      7
      8

      2. **语法规则**

      - **属性命�?*:使用驼峰命名(�?`className` 代替 `class`,`htmlFor` 代替 `for`)�? - **闭合标签**:所有标签必须显式闭合(�?`<img />`)�? - **单一根元�?*:JSX 必须有唯一根元素(或用 `<></>` 空标签包裹)�?
      3. **安全�?*
      - **默认 XSS 防护**:JSX 自动转义嵌入内容中的特殊字符(如 `<` 转为 `&lt;`)�? - **例外场景**:如需渲染原始 HTML,需显式使用 `dangerouslySetInnerHTML`(需谨慎):
      ```jsx
      <div dangerouslySetInnerHTML={{ __html: userContent }} />

编译与工具链

  1. 编译流程
    JSX 需通过 Babel 编译为浏览器可执行的 JavaScript。典型配置如下:

    1
    2
    3
    4
    // .babelrc
    {
    "presets": ["@babel/preset-react"]
    }
  2. *React 17+ 的优�?

    • 无需手动导入 React:编译器自动引入 _jsx 函数�? - 更简洁的编译输出:减少代码体积,提升可读性�?
      :::

参考资�?
::: details

:::

如何理解 React Fiber 架构�?

参考答�?
::: details

  1. Fiber 架构的本质与设计目标

Fiber �?React 16+ �?*核心算法重写,本质是基于链表的增量式协调模型**。其核心目标并非单纯提升性能,而是重构架构以实现:

  • 可中断的异步渲染:将同步递归的调和过程拆解为可暂�?恢复的异步任务�?- **优先级调�?*:高优先级任务(如用户输入)可打断低优先级任务(如数据更新)�?- 并发模式基础:为 SuspenseuseTransition 等特性提供底层支持�?
  1. *Fiber 节点的核心设�?

每个组件对应一�?Fiber 节点,构�?*双向链表树结�?*,包含以下关键信息:

  • 组件类型:函数组件、类组件或原生标签�?- **状态与副作�?*:Hooks 状态(�?useState)、生命周期标记(�?useEffect)�?- 调度信息:任务优先级(lane 模型)、到期时间(expirationTime)�?- 链表指针child(子节点)、sibling(兄弟节点)、return(父节点)�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Fiber 节点结构简化示�?const fiberNode = {
    tag: FunctionComponent, // 组件类型
    stateNode: ComponentFunc, // 组件实例�?DOM 节点
    memoizedState: {
    /* Hooks 链表 */
    },
    pendingProps: {
    /* 待处�?props */
    },
    lanes: Lanes.HighPriority, // 任务优先�? child: nextFiber, // 子节�? sibling: null, // 兄弟节点
    return: parentFiber, // 父节�?}
  1. *Fiber 协调流程(两阶段提交�?

*阶段 1:Reconciliation(协�?渲染阶段�?

  • 可中断的增量计算�? React 将组件树遍历拆解为多�?Fiber 工作单元,通过循环(而非递归)逐个处理�? - 每次循环执行一�?Fiber 节点,生成子 Fiber 并连接成树�? - 通过 requestIdleCallback(或 Scheduler 包)在浏览器空闲时段执行,避免阻塞主线程�?- 对比策略�? 根据 key �?type 复用节点,标�?Placement(新增)、Update(更新)、Deletion(删除)等副作用�?
    阶段 2:Commit(提交阶段)

  • 不可中断�?DOM 更新�? 同步执行所有标记的副作用(�?DOM 操作、生命周期调用),确�?UI 一致性�?- **副作用分�?*�? - BeforeMutationgetSnapshotBeforeUpdate�? - Mutation:DOM 插入/更新/删除�? - LayoutuseLayoutEffectcomponentDidMount/Update�?

  1. *优先级调度机�?

React 通过 Lane 模型 管理任务优先级(�?31 个优先级车道):

  • **事件优先�?*�? javascript // 优先级从高到�? ImmediatePriority(用户输入) UserBlockingPriority(悬停、点击) NormalPriority(数据请求) LowPriority(分析日志) IdlePriority(非必要任务�?
  • 调度策略�? - 高优先级任务可抢占低优先级任务的执行权�? - 过期任务(如 Suspense 回退)会被强制同步执行�?
  1. *Fiber 架构的优势与局限�?

优势

  • **流畅的用户体�?*:异步渲染避免主线程阻塞,保障高优先级任务即时响应�?- 复杂场景优化:支持大规模组件树的高效更新(如虚拟滚动、动画串联)�?- 未来特性基础:为并发模式(Concurrent Mode)、离线渲染(SSR)提供底层支持�?
    *局限�?

  • **学习成本�?*:开发者需理解底层调度逻辑以优化性能�?- 内存开销:Fiber 树的双向链表结构比传统虚�?DOM 占用更多内存�?

  1. *与旧架构的关键差�?
特�? Stack Reconciler(React 15-�? Fiber Reconciler(React 16+�?
遍历方式 递归(不可中断) 循环(可中断 + 恢复�?
任务调度 同步执行,阻塞主线程 异步分片,空闲时段执�?
*优先级控�? �? 基于 Lane 模型的优先级抢占
数据结构 虚拟 DOM �? Fiber 链表树(含调度信息)

:::

Fiber 结构和普�?VNode 区别

参考答�?
::: details

  1. 本质差异
维度 普�?VNode(虚�?DOM�? Fiber 结构
设计目标 减少真实 DOM 操作,提升渲染性能 实现可中断的异步渲染 + 优先级调�?
数据结构 树形结构(递归遍历�? 双向链表树(循环遍历�?
功能范畴 仅描�?UI 结构 描述 UI 结构 + 调度任务 + 副作用管�?
  1. 数据结构对比

普�?VNode(React 15 及之前)

1
2
3
4
const vNode = {
type: 'div', // 节点类型(组�?原生标签�? props: { className: 'container' }, // 属�? children: [vNode1, vNode2], // 子节点(树形结构�? key: 'unique-id', // 优化 Diff 性能
// 无状态、调度、副作用信息
}
  • 核心字段:仅包含 UI 描述相关属性(type、props、children)�?
    *Fiber 节点(React 16+�?
1
2
3
4
5
6
7
8
9
10
11
const fiberNode = {
tag: HostComponent, // 节点类型(函数组�?类组�?DOM元素�? type: 'div', // 原生标签或组件构造函�? key: 'unique-id', // Diff 优化标识
stateNode: domNode, // 关联的真�?DOM 节点
pendingProps: { className: 'container' }, // 待处理的 props
memoizedProps: {}, // 已生效的 props
memoizedState: {
// Hooks 状态(函数组件�? hooks: [state1, effectHook],
},
updateQueue: [], // 状态更新队列(类组件)
lanes: Lanes.HighPriority, // 调度优先级(Lane 模型�? child: childFiber, // 第一个子节点
sibling: siblingFiber, // 下一个兄弟节�? return: parentFiber, // 父节点(构成双向链表�? effectTag: Placement, // 副作用标记(插入/更新/删除�? nextEffect: nextEffectFiber, // 副作用链表指�?}
  • 核心扩展�? - 调度控制lanes 优先级、任务到期时间�? - **状态管�?*:Hooks 链表(函数组件)、类组件状态队列�? - **副作用追�?*:effectTag 标记和副作用链表�? - 遍历结构child/sibling/return 构成双向链表�?
  1. 协调机制对比
流程 VNode(Stack Reconciler�? Fiber Reconciler
遍历方式 递归遍历(不可中断) 循环遍历链表(可中断 + 恢复�?
任务调度 同步执行,阻塞主线程 异步分片,空闲时间执�?
*优先级控�? �? Lane 模型�?1 个优先级车道�?
*副作用处�? 统一提交 DOM 更新 构建副作用链表,分阶段提�?
  • **Fiber 两阶段提�?*�? 1. 协调阶段(可中断):
    • 增量构建 Fiber 树,标记副作用(effectTag)�? - 通过 requestIdleCallback �?Scheduler 包分片执行�? 2. 提交阶段(同步不可中断)�? - 遍历副作用链表,执行 DOM 操作和生命周期方法�?
  1. 能力扩展示例

    *a. 支持 Hooks 状态管�?

  • Fiber 节点通过 memoizedState 字段存储 Hooks 链表�?

    1
    2
    3
    4
    5
    6
    7
    // 函数组件�?Hooks 链表
    fiberNode.memoizedState = {
    memoizedState: 'state value', // useState 的状�? next: {
    // 下一�?Hook(如 useEffect�? memoizedState: { cleanup: fn },
    next: null,
    },
    }
  • VNode 无状态管理能力,仅描�?UI�?
    *b. 优先级调度实�?

  • 高优先级任务抢占�? ```javascript
    // 用户输入触发高优先级更新
    input.addEventListener(‘input’, () => {
    React.startTransition(() => {
    setInputValue(e.target.value) // 低优先级
    })
    // 高优先级更新立即执行
    })

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - VNode 架构无法实现任务中断和优先级插队�?
    **c. 副作用批处理**

    - Fiber 通过 `effectList` 链表收集所有变更,统一提交�? ```javascript
    // 提交阶段遍历 effectList
    let nextEffect = fiberRoot.firstEffect
    while (nextEffect) {
    commitWork(nextEffect)
    nextEffect = nextEffect.nextEffect
    }
  • VNode 架构�?Diff 后直接操�?DOM,无批处理优化�?

  1. 性能影响对比
场景 VNode 架构 Fiber 架构
*大型组件树渲�? 主线程阻塞导致掉�? 分片渲染,保�?UI 响应
*高频更新(如动画�? 多次渲染合并困难 基于优先级合并或跳过中间状�?
*SSR 水合(Hydration�? 全量同步处理 增量水合,优先交互部�?

:::

简�?React diff 算法过程

参考答�?
::: details

React Diff 算法通过 分层对比策略 �?*启发式规�? 减少树对比的时间复杂度(�?O(n³) 优化�?O(n))。其核心流程如下�?
1. 分层对比策略

React 仅对 *同一层级的兄弟节�? 进行对比,若节点跨层级移动(如从父节�?A 移动到父节点 B),则直�?销毁并重建,而非移动�?原因:跨层操作在真实 DOM 中成本极高(需递归遍历子树),而实际开发中跨层移动场景极少,此策略以概率换性能�?
2. 节点类型比对规则

a. 元素类型不同

若新旧节点类型不同(�?<div> �?<span> �?ComponentA �?ComponentB),则:

  1. 销毁旧节点及其子树�?2. 创建新节点及子树,并插入 DOM�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 旧树
    <div>
    <ComponentA />
    </div>

    // 新树 �?直接替换
    <span>
    <ComponentB />
    </span>

b. 元素类型相同

若类型相同,则复�?DOM 节点并更新属性:

  • 原生标签:更�?classNamestyle 等属性�?- 组件类型�? - 类组件:保留实例,触�?componentWillReceiveProps �?shouldComponentUpdate 等生命周期�? - 函数组件:重新执行函数,通过 Hooks 状态判断是否需更新�?
    1
    2
    3
    4
    // 旧组件(保留实例并更�?props�?<Button className="old" onClick={handleClick} />

    // 新组�?�?复用 DOM,更�?className �?onClick
    <Button className="new" onClick={newClick} />

3. 列表节点�?Key 优化

处理子节点列表时,React 依赖 key 进行最小化更新�?
a. �?key 时的默认行为

默认使用 索引匹配(index-based diff),可能导致性能问题�?

1
2
3
// 旧列�?;[<div>A</div>, <div>B</div>][
// 新列表(首部插入)→ 索引对比导致 B 被误判更�? ((<div>C</div>), (<div>A</div>), (<div>B</div>))
]

此时 React 会认为索�?0 �?A �?C(更新),索�?1 �?B �?A(更新),并新增索引 2 �?B,实际应仅插�?C�?
*b. 使用 key 的优化匹�?

通过唯一 key 标识节点身份,React 可精准识别移�?新增/删除�?

1
2
3
4
5
// 正确使用 key(如数据 ID�?<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>

匹配规则�?

  1. 遍历新列表,通过 key 查找旧节点:

    • 找到且类型相�?�?复用节点�? - 未找�?�?新建节点�?
  2. 记录旧节点中未被复用的节�?�?执行删除�?
    c. 节点移动优化

若新旧列表节点仅顺序变化,React 通过 key 匹配后,仅执�?DOM 移动操作(非重建),例如�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 旧列表:A (key=1), B (key=2)
// 新列表:B (key=2), A (key=1)
// React 仅交�?DOM 顺序,而非销毁重�?```

**4. 性能边界策略**

- **子树跳过**:若父节点类型变化,其子节点即使未变化也会被整体销毁�?- **相同组件提前终止**:若组件 `shouldComponentUpdate` 返回 `false`,则跳过其子�?Diff�?
:::

## React �?Vue diff 算法的区�?
参考答�?
::: details

React �?Vue �?Diff 算法均基于虚�?DOM,但在实现策略、优化手段和设计哲学上存在显著差异:

**1. 核心算法策略对比**

| **维度** | **React** | **Vue 2/3** |
| ------------ | ----------------------------- | ------------------------------------ |
| **遍历方式** | 单向递归(同层顺序对比) | 双端对比(头尾指针优化) |
| **节点复用** | 类型相同则复用,否则销毁重�? | 类型相同则尝试复用,优先移动而非重建 |
| **静态优�?* | 需手动优化(如 `React.memo`�?| 编译阶段自动标记静态节�? |
| **更新粒度** | 组件级更新(默认�? | 组件�?+ 块级(Vue3 Fragments�? |

**2. 列表 Diff 实现细节**

**a. React 的索引对比策�?*

- **�?key �?*:按索引顺序对比,可能导致无效更�? ```jsx
// 旧列表:[A, B, C]
// 新列表:[D, A, B, C](插入头部)
// React 对比结果:更新索�?0-3,性能低下
  • **�?key �?*:通过 key 匹配节点,减少移动操�? ```jsx
    // key 匹配后,仅插�?D,其他节点不更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    **b. Vue 的双端对比策�?*

    分四步优化对比效率(Vue2 核心逻辑,Vue3 优化为最长递增子序列)�?
    1. **头头对比**:新旧头指针节点相同则复用,指针后移
    2. **尾尾对比**:新旧尾指针节点相同则复用,指针前移
    3. **头尾交叉对比**:旧�?vs 新尾,旧�?vs 新头
    4. **中间乱序对比**:建�?key-index 映射表,复用可匹配节�?
    ```js
    // 旧列表:[A, B, C, D]
    // 新列表:[D, A, B, C]
    // Vue 通过步骤3头尾对比,仅移动 D 到头�?```

    **3. 静态优化机�?*

    **a. Vue 的编译时优化**

    - **静态节点标�?*�? 模板中的静态节点(无响应式绑定)会被编译为常量,跳�?Diff

    ```html
    <!-- 编译�?-->
    <div>Hello Vue</div>

    <!-- 编译�?-->
    _hoisted_1 = createVNode("div", null, "Hello Vue")
  • **Block Tree(Vue3�?*�? 动态节点按区块(Block)组织,Diff 时仅对比动态部�?
    b. React 的运行时优化

  • 手动控制更新�? 需通过 React.memoshouldComponentUpdate �?useMemo 避免无效渲染

    1
    const MemoComp = React.memo(() => <div>Static Content</div>)

*4. 响应式更新触�?

框架 机制 Diff 触发条件
React 状态变化触发组件重新渲�? 父组件渲�?�?子组件默认递归 Diff
Vue 响应式数据变更触发组件更�? 依赖收集 �?仅受影响组件触发 Diff
1
2
3
4
5
// Vue:只�?data.value 变化才会触发更新
const vm = new Vue({ data: { value: 1 } })

// React:需显式调用 setState
const [value, setValue] = useState(1)

5. 设计哲学差异

维度 React Vue
控制粒度 组件级控制(开发者主导) 细粒度依赖追踪(框架主导�?
优化方向 运行时优化(Fiber 调度�? 编译时优化(模板静态分析)
适用场景 大型动态应用(需精细控制�? 中小型应用(快速开发)

:::

React JSX 循环为何使用 key �?

参考答�?
::: details

  1. 元素的高效识别与复用

React 通过 key 唯一标识列表中的每个元素。当列表发生变化(增删改排序)时,React 会通过 key 快速判断:

  • 哪些元素是新增的(需要创建新 DOM 节点�?- 哪些元素是移除的(需要销毁旧 DOM 节点�?- 哪些元素是移动的(直接复用现�?DOM 节点,仅调整顺序�?
    如果没有 key,React 会默认使用数组索引(index)作为标识,这在动态列表中会导�?性能下降 �?**状态错�?*�?
  1. *避免状态混�?

如果列表项是 有状态的组件(比如输入框、勾选框等),错误的 key 会导致状态与错误的内容绑定。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 如果初始列表�?[A, B],用索引 index 作为 key�?<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>

// 在头部插入新元素变为 [C, A, B] 时:
// React 会认�?key=0 �?C(重新创建)
// key=1 �?A(复用原 key=0 �?DOM,但状态可能残留)
// 此时,原本属�?A 的输入框状态可能会错误地出现在 C 中�?```

3. **提升渲染性能**

通过唯一且稳定的 `key`(如数据 ID),React 可以精准判断如何复用 DOM 节点。如果使用随机数或索引,每次渲染都会强制重新创建所有元素,导致性能浪费�?
:::

## React 事件�?DOM 事件区别

参考答�?
::: details

1. **事件绑定方式**

- **React 事件**
使用**驼峰命名�?*(如 `onClick``onChange`),通过 JSX 属性直接绑定函数:

```jsx
<button onClick={handleClick}>点击</button>
  • DOM 事件
    使用**全小写命�?*(如 onclickonchange),通过字符串或 addEventListener 绑定�? ```html
    1
    2
    ```javascript
    button.addEventListener('click', handleClick)
  1. *事件对象(Event Object�?
  • React 事件
    使用**合成事件(SyntheticEvent�?*,是原生事件对象的跨浏览器包装�?

    • 通过 e.nativeEvent 访问原生事件�? - 事件对象会被复用(事件池机制),异步访问需调用 e.persist()�?
      1
      2
      3
      4
      const handleClick = (e) => {
      e.persist() // 保持事件对象引用
      setTimeout(() => console.log(e.target), 100)
      }
  • DOM 事件
    直接使用浏览器原生事件对象,无复用机制�? ```javascript
    button.addEventListener(‘click’, (e) => {
    console.log(e.target) // 直接访问
    })

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    3. **事件传播与默认行�?*

    - **React 事件**

    - **阻止默认行为**:必须显式调�?`e.preventDefault()`�? - **阻止冒泡**:调�?`e.stopPropagation()`�?
    ```jsx
    const handleSubmit = (e) => {
    e.preventDefault() // 阻止表单默认提交
    e.stopPropagation() // 阻止事件冒泡
    }
  • DOM 事件

    • 阻止默认行为:可调用 e.preventDefault() �?return false(在 HTML 属性中)�? - 阻止冒泡:调�?e.stopPropagation() �?return false(仅部分情况)�? ```html
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29

      4. **性能优化**

      - **React 事件**
      采用**事件委托**机制�?
      - React 17 之前将事件委托到 `document` 层级�? - React 17+ 改为委托到渲染的根容器(�?`ReactDOM.render` 挂载的节点)�? - 减少内存占用,动态添加元素无需重新绑定事件�?
      - **DOM 事件**
      直接绑定到元素,大量事件监听时可能导致性能问题�?
      5. **跨浏览器兼容�?*

      - **React 事件**
      合成事件抹平了浏览器差异(如 `event.target` 的一致性),无需处理兼容性问题�?
      - **DOM 事件**
      需手动处理浏览器兼容性(�?IE �?`attachEvent` vs 标准 `addEventListener`)�?
      6. **`this` 绑定**

      - **React 事件**
      类组件中需手动绑定 `this` 或使用箭头函数:

      ```jsx
      class MyComponent extends React.Component {
      handleClick() {
      console.log(this) // 需绑定,否则为 undefined
      }

      render() {
      return <button onClick={this.handleClick.bind(this)}>点击</button>
      }
      }
  • DOM 事件
    事件处理函数中的 this 默认指向触发事件的元素:

    1
    2
    3
    button.addEventListener('click', function () {
    console.log(this) // 指向 button 元素
    })
特�? React 事件 DOM 事件
命名规则 驼峰命名(onClick�? 全小写(onclick�?
事件对象 合成事件(SyntheticEvent�? 原生事件对象
默认行为阻止 e.preventDefault() e.preventDefault() �?return false
事件委托 自动委托到根容器 需手动实现
跨浏览器兼容 内置处理 需手动适配
this 指向 类组件中需手动绑定 默认指向触发元素

React 事件系统通过抽象和优化,提供了更高效、一致的事件处理方式,避免了直接操作 DOM 的繁琐和兼容性问题�?
:::

简�?React batchUpdate 机制

参考答�?
::: details

React �?*batchUpdate(批处理更新)机�? 是一种优化策略,旨在将多个状态更新合并为一次渲染,减少不必要的组件重新渲染次数,从而提高性能�?
核心机制

  1. 异步合并更新
    当在 **同一执行上下�?*(如同一个事件处理函数、生命周期方法或 React 合成事件)中多次调用状态更新(�?setStateuseState �?setter 函数),React 不会立即触发渲染,而是将多个更新收集到一个队列中,最终合并为一次更新,统一计算新状态并渲染�?

  2. 更新队列
    React 内部维护一个更新队列。在触发更新的代码块中,所有状态变更会被暂存到队列,直到代码执行完毕,React 才会一次性处理队列中的所有更新,生成新的虚拟 DOM,并通过 Diff 算法高效更新真实 DOM�?
    触发批处理的场景

  3. React 合成事件
    �?onClickonChange 等事件处理函数中的多次状态更新会自动批处理�?

    1
    2
    3
    4
    const handleClick = () => {
    setCount(1) // 更新入队
    setName('Alice') // 更新入队
    // 最终合并为一次渲�? }
  4. React 生命周期函数
    �?componentDidMountcomponentDidUpdate 等生命周期方法中的更新会被批处理�?

  5. React 18+ 的自动批处理增强
    React 18 引入 createRoot 后,即使在异步操作(�?setTimeoutPromise、原生事件回调)中的更新也会自动批处理:

    1
    2
    3
    setTimeout(() => {
    setCount(1) // React 18 中自动批处理
    setName('Alice') // 合并为一次渲�? }, 1000)

绕过批处理的场景

  1. React 17 及之前的异步代码
    �?setTimeoutPromise 或原生事件回调中的更新默�?*不会**批处理,每次 setState 触发一次渲染:

    1
    2
    3
    // React 17 中会触发两次渲染
    setTimeout(() => {
    setCount(1) // 渲染一�? setName('Alice') // 渲染第二�? }, 1000)
  2. 手动强制同步更新
    使用 flushSync(React 18+)可强制立即更新,绕过批处理�?

    1
    2
    3
    4
    5
    6
    import { flushSync } from 'react-dom'

    flushSync(() => {
    setCount(1) // 立即渲染
    })
    setName('Alice') // 再次渲染

设计目的

  1. 性能优化
    避免频繁�?DOM 操作,减少浏览器重绘和回流,提升应用性能�?
  2. *状态一致�?
    确保在同一个上下文中多次状态变更后,组件最终基于最新的状态值渲染,避免中间状态导致的 UI 不一致�?
    示例对比
  • *自动批处理(React 18+�?

    1
    2
    3
    4
    const handleClick = () => {
    setCount((prev) => prev + 1) // 更新入队
    setCount((prev) => prev + 1) // 更新入队
    // 最�?count 增加 2,仅一次渲�? }
  • *非批处理(React 17 异步代码�?

    1
    2
    3
    setTimeout(() => {
    setCount((prev) => prev + 1) // 渲染一�? setCount((prev) => prev + 1) // 再渲染一�? // React 17 中触发两次渲染,count 仍为 2
    }, 1000)
场景 React 17 及之�? React 18+(使�?createRoot�?
合成事件/生命周期 自动批处�? 自动批处�?
异步操作 不批处理 自动批处�?
原生事件回调 不批处理 自动批处�?

React 的批处理机制通过合并更新减少了渲染次数,但在需要即时反馈的场景(如动画)中,可通过 flushSync 强制同步更新�?
:::

简�?React 事务机制

参考答�?
::: details

React �?*事务机制(Transaction�? 是早期版本(React 16 之前)中用于 批量处理更新 �?*管理副作�? 的核心设计模式,其核心思想是通过“包装”操作流程,确保在更新过程中执行特定的前置和后置逻辑(如生命周期钩子、事件监听等)。随着 React Fiber 架构的引入,事务机制逐渐被更灵活的调度系统取代�?
核心概念

  1. *事务的定�?
    事务是一个包�?**初始化阶�?�?执行阶段 �?收尾阶段 的流程控制单元。每个事务通过 Transaction 类实现,提供 initialize �?close 方法,用于在操作前后插入逻辑。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    const MyTransaction = {
    initialize() {
    /* 前置操作(如记录状态) */
    },
    close() {
    /* 后置操作(如触发更新�?*/
    },
    }
  2. 包装函数
    事务通过 perform 方法执行目标函数,将其包裹在事务的生命周期中�? ```javascript
    function myAction() {
    /* 核心逻辑(如调用 setState�?*/
    }
    MyTransaction.perform(myAction)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    **�?React 中的应用场景**

    1. **批量更新(Batching Updates�?*
    在事件处理或生命周期方法中,多次调用 `setState` 会被事务合并为一次更新。例如:

    ```javascript
    class Component {
    onClick() {
    // 事务包裹下的多次 setState 合并为一次渲�? this.setState({ a: 1 })
    this.setState({ b: 2 })
    }
    }
  3. *生命周期钩子的触�?
    在组件挂载或更新时,事务确保 componentWillMountcomponentDidMount 等钩子在正确时机执行�?

  4. *事件系统的委�?
    合成事件(如 onClick)的处理逻辑通过事务绑定和解绑,确保事件监听的一致性和性能优化�?
    *事务的工作流�?

  5. *初始化阶�?
    执行所有事务的 initialize 方法(如记录当前 DOM 状态、锁定事件监听)�?2. 执行目标函数
    运行核心逻辑(如用户定义�?setState 或事件处理函数)�?3. 收尾阶段
    执行所有事务的 close 方法(如对比 DOM 变化、触发更新、解锁事件)�?
    *事务机制的局限�?

  6. 同步阻塞
    事务的执行是同步且不可中断的,无法支持异步优先级调度(如 Concurrent Mode 的时间切片)�?2. 复杂性高
    事务的嵌套和组合逻辑复杂,难以维护和扩展�?
    *Fiber 架构的演�?
    React 16 引入�?Fiber 架构 替代了事务机制,核心改进包括�?

  7. *异步可中断更�?
    通过 Fiber 节点的链表结构,支持暂停、恢复和优先级调度�?2. *更细粒度的控�?
    将渲染拆分为多个阶段(如 render �?commit),副作用管理更灵活�?3. 替代批量更新策略
    使用调度器(Scheduler)和优先级队列实现更高效的批处理(如 React 18 的自动批处理)�?

    特�? 事务机制(React <16�? Fiber 架构(React 16+�?
    更新方式 同步批量更新 异步可中断、优先级调度
    *副作用管�? 通过事务生命周期控制 通过 Effect Hook、提交阶段处�?
    *复杂�? 高(嵌套事务逻辑复杂�? 高(但更模块化和可扩展)
    适用场景 简单同步更�? 复杂异步渲染(如动画、懒加载�?

事务机制�?React 早期实现批量更新的基石,但其同步设计无法满足现代前端应用的复杂需求。Fiber 架构通过解耦渲染过程,�?Concurrent Mode �?Suspense 等特性奠定了基础,成�?React 高效渲染的核心�?:::

理解 React concurrency 并发机制

参考答�?
::: details

React 的并发机制(Concurrency)是 React 18 引入的一项重要特性,旨在提升应用的响应性和性能�?
1. 什么是 React 的并发机制?

React 的并发机制允�?React 在渲染过程中根据任务的优先级进行调度和中断,从而确保高优先级的更新能够及时渲染,而不会被低优先级的任务阻塞�?
2. 并发机制的工作原理:

  • 时间分片(Time Slicing): React 将渲染任务拆分为多个小片段,每个片段在主线程空闲时执行。这使得浏览器可以在渲染过程中处理用户输入和其他高优先级任务,避免长时间的渲染阻塞用户交互�?

  • 优先级调度(Priority Scheduling): React 为不同的更新分配不同的优先级。高优先级的更新(如用户输入)会被优先处理,而低优先级的更新(如数据预加载)可以在空闲时处理�?

  • 可中断渲染(Interruptible Rendering): 在并发模式下,React 可以中断当前的渲染任务,处理更高优先级的任务,然后再恢复之前的渲染。这确保了应用在长时间渲染过程中仍能保持响应性�?
    3. 并发机制的优势:

  • 提升响应性: 通过优先处理高优先级任务,React 能够更快地响应用户输入,提升用户体验�?

  • *优化性能�? 将渲染任务拆分为小片段,避免长时间的渲染阻塞,提升应用的整体性能�?

  • 更好的资源利用: 在主线程空闲时处理低优先级任务,充分利用系统资源�?
    *4. 如何启用并发模式�?

要在 React 应用中启用并发模式,需要使�?createRoot API�?

1
2
3
4
5
6
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

在并发模式下,React 会自动根据任务的优先级进行调度和渲染�?
:::

React reconciliation 协调的过�?

参考答�?
::: details

React �?*协调(Reconciliation�? 是用于高效更�?UI 的核心算法。当组件状态或属性变化时,React 会通过对比新旧虚拟 DOM(Virtual DOM)树,找出最小化的差异并应用更新。以下是协调过程的详细步骤:

  1. *生成虚拟 DOM �?
  • 当组件状态或属性变化时,React 会重新调用组件的 render 方法,生成新�?*虚拟 DOM �?*(一个轻量级�?JavaScript 对象,描�?UI 结构)�?- 虚拟 DOM 是实�?DOM 的抽象表示,操作成本远低于直接操作真�?DOM�?
  1. Diffing 算法(差异对比)
    React 使用 Diffing 算法 比较新旧两棵虚拟 DOM 树,找出需要更新的部分。对比规则如下:

规则一:不同类型的元素

  • 如果新旧元素�?type 不同(例如从 <div> 变为 <span>),React �?*销毁旧子树**�?*重建新子�?*�? - 旧组件的生命周期方法(如 componentWillUnmount)会被触发�? - 新组件的生命周期方法(如 constructorcomponentDidMount)会被触发�?
    *规则二:相同类型的元�?

  • 如果元素�?type 相同(例�?<div className="old"> �?<div className="new">),React �?*保留 DOM 节点**,仅更新变化的属性�? - 对比新旧属性,仅更新差异部分(例如 className)�? - 组件实例保持不变,生命周期方法(�?componentDidUpdate)会被触发�?
    *规则三:递归处理子节�?

  • 对于子节点的对比,React 默认使用逐层递归的方式�?- 列表对比优化�? - 当子元素是列表(例如通过 map 生成的元素)时,React 需要唯一 key 来标识元素,以高效复�?DOM 节点�? - 若未提供 key,React 会按顺序对比子节点,可能导致性能下降或状态错误(例如列表顺序变化时)�?

  1. 更新真实 DOM
  • 通过 Diffing 算法找出差异后,React 将生成一系列最小化�?DOM 操作指令(例�?updateTextContentreplaceChild)�?- 这些指令会被批量应用到真�?DOM 上,以减少重绘和重排的次数,提高性能�?
  1. *协调的优化策�?
  • **Key 的作�?*:为列表元素提供唯一�?key,帮�?React 识别元素的移动、添加或删除,避免不必要的重建�?- **批量更新(Batching�?*:React 会将多个状态更新合并为一次渲染,减少重复计算�?- Fiber 架构(React 16+):
    • 将协调过程拆分为可中断的“工作单元”(Fiber 节点),允许高优先级任务(如动画)优先处理�? - 支持异步渲染(Concurrent Mode),避免长时间阻塞主线程�?
      :::

React 组件渲染和更新的全过�?

参考答�?
::: details

React 组件的渲染和更新过程涉及多个阶段,包�?*初始化、渲染、协调、提交、清�? 等。以下是 React 组件渲染和更新的全过程,结合源码逻辑和关键步骤进行详细分析�?

1. 整体流程概述
React 的渲染和更新过程可以分为以下几个阶段�?

  1. **初始化阶�?*:创�?Fiber 树和 Hooks 链表�?2. 渲染阶段:生成新的虚�?DOM(Fiber 树)�?3. 协调阶段:对比新�?Fiber 树,找出需要更新的部分�?4. 提交阶段:将更新应用到真�?DOM�?5. 清理阶段:重置全局变量,准备下一次更新�?
    2. 详细流程分析

�?)初始化阶段

  • 触发条件:组件首次渲染或状�?属性更新�?- 关键函数rendercreateRootscheduleUpdateOnFiber�?- 逻辑�? 1. 通过 ReactDOM.render �?createRoot 初始化应用�? 2. 创建�?Fiber 节点(HostRoot)�? 3. 调用 scheduleUpdateOnFiber,将更新任务加入调度队列�?
    *�?)渲染阶�?

  • 触发条件:调度器开始执行任务�?- 关键函数performSyncWorkOnRootbeginWorkrenderWithHooks�?- 逻辑�? 1. 调用 performSyncWorkOnRoot,开始渲染任务�? 2. 调用 beginWork,递归处理 Fiber 节点�? 3. 对于函数组件,调�?renderWithHooks,执行组件函数并生成新的 Hooks 链表�? 4. 对于类组件,调用 instance.render,生成新的虚�?DOM�? 5. 对于 Host 组件(如 div),生成对应�?DOM 节点�?
    *�?)协调阶�?

  • 触发条件:新的虚�?DOM 生成后�?- 关键函数reconcileChildrendiff�?- 逻辑�? 1. 调用 reconcileChildren,对比新�?Fiber 节点�? 2. 根据 diff 算法,找出需要更新的节点�? 3. 为需要更新的节点打上 PlacementUpdateDeletion 等标记�?
    *�?)提交阶�?

  • 触发条件:协调阶段完成后�?- 关键函数commitRootcommitWork�?- 逻辑�? 1. 调用 commitRoot,开始提交更新�? 2. 调用 commitWork,递归处理 Fiber 节点�? 3. 根据节点的标记,执行 DOM 操作(如插入、更新、删除)�? 4. 调用生命周期钩子(如 componentDidMountcomponentDidUpdate)�?
    *�?)清理阶�?

  • 触发条件:提交阶段完成后�?- 关键函数resetHooksresetContext�?- 逻辑�? 1. 重置全局变量(如 currentlyRenderingFibercurrentHook)�? 2. 清理上下文和副作用�? 3. 准备下一次更新�?
    :::

为何 Hooks 不能放在条件或循环之内?

参考答�?
::: details

一个组件中�?hook 会以链表的形式串起来�?FiberNode �?memoizedState 中保存了 Hooks 链表中的第一�?Hook�?
在更新时,会复用之前�?Hook,如果通过了条件或循环语句,增加或者删�?hooks,在复用 hooks 过程中,会产生复�?hooks状态和当前 hooks 不一致的问题�?
:::

useEffect 的底层是如何实现的(美团�?

参考答�?
::: details

useEffect �?React 用于管理副作用的 Hook,它�?commit 阶段 统一执行,确保副作用不会影响渲染�?
�?React 源码中,useEffect 通过 Fiber 机制 �?commit 阶段 进行处理�?
**(1) useEffect 存储�?Fiber 节点�?*

React 组件是通过 Fiber 数据结构 组织的,每个 useEffect 都会存储�?fiber.updateQueue 中�?
(2) useEffect 何时执行

React 组件更新后,React �?commit 阶段 统一遍历 effect 队列,并执行 useEffect 副作用�?
React 使用 useEffectEvent() 注册 effect,在 commitLayoutEffect 之后,异步执�?useEffect,避免阻�?UI 渲染�?
**(3) useEffect 依赖变化的处�?*

依赖数组的比较使�?Object.is(),只有依赖变化时才重新执�?useEffect�?
在更新阶段,React 遍历�?effect,并先执行清理函数,然后再执行新�?effect�?
*简化的 useEffect 实现如下�?

1
2
3
4
5
6
7
8
function useEffect(callback, dependencies) {
const currentEffect = getCurrentEffect() // 获取当前 Fiber 节点�?Effect

if (dependenciesChanged(currentEffect.dependencies, dependencies)) {
cleanupPreviousEffect(currentEffect) // 先执行上�?effect 的清理函�? const cleanup = callback() // 执行 useEffect 传入的回�? currentEffect.dependencies = dependencies
currentEffect.cleanup = cleanup // 存储清理函数
}
}

相比 useLayoutEffect,useEffect �?异步执行,不会阻�?UI 渲染�?
:::

0%