HTML
script标签中的 async、defer
- async(异步) defer(推迟)都是异步加载js,只在script有src标签时候才有效
- async是js加载完立刻执行,可能会阻塞和HTML的解析
- defer要等到HTML解析完在执行
iframe
-
优点: iframe中的内容可以独立显示,可以复用,是异步加载,可以跨域
-
缺点: 搜索引擎可能无法正确解析iframe中的内容,会阻塞主页面中的onload事件,和主页面共享连接池,影响页面并行加载
HTML5 新特性
- 语义化标签
- 增强型表单(如可以通过 input 的 type 属性指定类型是 color 还是 date 或者 url 等)
- 媒体元素标签(video,audio)
- canvas,svg
- svg 绘图
- 地理等位(navigator.geolocation.getCurrentPosition(callback))
- 拖放 API(给标签元素设置属性 draggable 值为 true,能够实现对目标元素的拖动)
Web Worker(可以开启一个子线程运行脚本)Web Storage(即 sessionStorage 与 localStorage)Websocket(双向通信协议,可以让浏览器接收服务端的请求)- dom 查询(document.querySelector()和 document.querySelectorAll())
行内元素和块级元素
- 块级元素独占一行,从上到下排列,- 高度,行高,margin,padding都可以控制
- 块级元素有 div p form table ul ol li h1
- 行内元素不独占一行,宽高不能控制,行高有效,仅能设置左右的margin,padding
- 行内元素有 span img a input button select
css
display属性
- none 元素不显示,从文档流中移除
- block 块类型
- inline 行内类型
- inline-block 行内块
- table 块表格
- list-item 块列表
- inherit 继承
隐藏元素的方法有哪些
display:nonevisibility:hiddenopacity:0transform:scale(0,0)缩放
文本溢出省略
单行文本省略
- overflow:hidden 文字超出区域隐藏
- white-space:nowrap 文本不换行
- text-ovrflow:ellipsis 省略部分用...代替
多行文本省略
- 使用伪元素加绝对定位方式实现
- 元素设置position:relative
- overflow:hidden
- 行高控制显示行数,高度
- 设置::after position:absolute bottom,right为0
- content:'...'
- -webkit配合overflow,text-overflow多用于移动端
如何判断元素是否到达可视区域
以图片显示为例:
window.innerHeight是浏览器可视区的高度;document.body.scrollTop || document.documentElement.scrollTop是浏览器滚动的过的距离;imgs.offsetTop是元素顶部距离文档顶部的高度(包括滚动条的距离);- 内容达到显示区域的:
img.offsetTop < window.innerHeight + document.body.scrollTop;
两栏布局
- 固定栏
float:left自适应栏设置margin/overflow:hidden触发bfc - 父
display:flex自适应栏flex:1 - 父
position:relative固定栏position:absolute自适应栏设置margin
水平垂直居中的实现
- 父相子绝 子设置
left:50%top:50%transform:translate(-50%,-50%) - 父相子绝 子设置
top: 0bottom: 0left: 0right: 0margin: auto - 父
display:flexjustify-content:centeralign-item:center
flex
以下6个属性设置在容器上:
- flex-direction属性决定主轴的方向(即项目的排列方向)。
- flex-wrap属性定义,如果一条轴线排不下,如何换行。
- flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
- justify-content属性定义了项目在主轴上的对齐方式。
- align-items属性定义项目在交叉轴上如何对齐。
- align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
以下6个属性设置在项目上:
- order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
- flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
- flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
- flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。
- align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
BFC 块级格式化上下文
-
bfc是一个独立的容器,他按照一定规则进行布局且容器内元素不会受到外部影响 创建BFC的条件:
-
根元素:body;
-
元素设置浮动:float 除 none 以外的值;
-
元素设置绝对定位:position (absolute、fixed);
-
display 值为:inline-block、table-cell、table-caption、flex等;
-
overflow 值为:hidden、auto、scroll;
BFC的特点:
- 垂直方向上,自上而下排列,和文档流的排列方式一致。
- 在BFC中上下相邻的两个容器的margin会重叠
- BFC高度跟浮动元素高度有关
- BFC区域不会与浮动的容器发生重叠
- BFC是独立的容器,容器内部元素不会影响外部元素
- 每个元素的左margin值和容器的左border相接触
BFC的作用:
- 解决margin的重叠问题 将两个元素变为两个BFC,就解决了margin重叠的问题。
- 解决高度塌陷的问题
- 创建自适应两栏布局
position属性
- static默认值 没有定位
- relative 相对浮动,相对自身位置定位
- absolute 绝对定位,相对于position默认值以外的一个父元素进行定位
- fixed 视窗定位,以浏览器窗口进行定位
- sticky 粘性定位,元素在超过特定值前为相对定位(
relative),之后为固定定位(fixed)
JS
js基本概念
js引擎 负责整个 JavaScript 程序的编译及执行过程(核心)
js编译器 负责语法分析及代码生成等(编译三步)
作用域 负责收集并维护由所有变量查询,并确定访问权限
在代码执行中 编译器通常会对作用域(词法作用域)进行查询操作 ,LHS查询 和RHS查询
LHS:赋值操作的左侧查询。这并不意味着LHS就是赋值符号左侧的操作。可以理解为 找到变量,对其赋值RHS:赋值操作的右侧查询。同样的道理,它也并不是赋值符号的右侧操作。可以理解为 取得某变量的值
!!!
在 逐层向上 的查找过程中,引擎会从变量当前作用域开始,一直查找到全局作用域。如果到全局作用域还如法查找到变量的话,那么就会抛出 ReferenceError 异常 。
ReferenceError 异常 表示 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量。
而对于 LHS 查询 而言,如果在非严格模式下(例 a = 2),编译器会在全局作用于下声明该变量,然后再为其赋值。
而如果在严格模式下,同样会抛出 ReferenceError 异常
js编译原理
实际上就是js代码在 parser 转换成AST的过程,主要有三步:
- 词法分析 :就是把一段代码解析成多个词法单元(token),并检查是否有错误
- 语法分析 :把token转换成AST树
- 代码生成 :把AST树转换成可执行代码
- 预编译 :引擎会进行变量提升等优化措施,确定作用域链等信息,为变量和函数的查找提供方便
- 即时编译:将频繁执行的代码编译成机器码,以加快执行速度
js执行过程
1、加载:
- 将生成的机器码或字节码加载到内存中。
2、初始化:
- 初始化程序的运行环境,包括分配内存、设置初始状态等。
3、执行:
- 逐行执行代码,进行变量赋值、函数调用等操作。(进行RHS与LHS)
js引擎其内部有一个执行上下文栈(Execution Context Stack,简称ECS),用于执行代码,
初始化全局作用域
在代码执行前js会创建一个全局作用域 Go
Go里面所有的作用域以及一些对象、方法都可以访问,还有一个指向自己的windows属性
变量执行过程
堆栈处理
在js中,内存分为栈 和堆内存,
1.栈内存主要存储,基本类型变量以及堆内存引用地址,执行上下文
2.堆内存引用类型变量,闭包变量,函数体
在执行栈底会有一个全局执行上下文GEC ,变量会存储在全局变量对象(G)VO中,
其赋给的基本类型值会存储在栈区中,赋给的引用类型值会存储在堆区中
!!! 在变量赋值处理中( . 操作符)优先于( = 操作符)执行
!!! 需要注意变量执行过程中变量提升的问题
函数执行过程
函数会存储在函数执行上下文FEC 的内部对象中,会将函数的内容提升到AO中
函数执行的步骤:
1、确定作用域链(当前与上级作用域所在的执行上下文)。
2、确定this。
3、初始化arguments(对象)。
4、形参赋值(相当于变量声明,将其放与AO再赋值)。
5、变量提升。
6、代码执行。注意函数执行完后就会对当前函数的EC进行出栈。
注意!!!
闭包中的变量属于被捕获变量,就是在函数内部被没有只执行完成的的作用域(函数、对象等)引用的变量
js垃圾回收机制
引用计数
它追踪每个对象被引用的次数,最大的缺点难以解决对象循环引用问题,会陷入无法回收的死循环中造成内存泄漏
标记清除
他分为标记和清除两个阶段
首先会设置一个根对象,垃圾回收会从这个根节点开始,找到所有从根开始有引用的对象,
每一个找到的对象被称为可达对象,不可达对象会被清除,占用的内存空间会加入空闲内存池从新分配使用
V8垃圾回收机制
在V8中内存空间被分成新生代、老生代两部分
新生代空间较小,用于存储存活时间较短的对象
新生代回收实现(回收过程采用空间复制+标记整理算法):
新生代内存区分为两个等大小空间。活动对象存储于“From空间”。
当From空间大小超过一定限制时触发GC,进行标记整理后拷贝到“To空间”。
当From与To交换空间完成后清空From内存(使用空间为From,空闲空间为To并在标记整理后交换空间及状态)。
!!! 拷贝过程中可能出现晋升。即将新生代对象移动到老生代中。一轮GC后还存活的新生代需要晋升。
To空间使用率超25% 需要晋升
老生代空间较大,存储存活时间较长的对象
老生代使用标记清除、标记整理、增量标记算法
闭包
内部函数能访问外部作用域的函数可以称作闭包
形成闭包原因有一点:
在词法解析阶段,能访问什么变量就已经确定了,他会一直在堆中不会被垃圾机制回回收,在深入点就是内部函数会一直保持对外部函数AO的引用,被引用的变量会保留其他会销毁(这是V8引擎做的优化)
闭包内存泄漏解决方案
1.赋值为null手动解除引用
2.使用WeakMap或WeakSet等弱引用储存对对象的引用,这些数据结构不会阻止键值中的对象被回收
!!! 为什么null可以解除引用,而undefined不行?
null代表空值,被赋予给变量的时候,内存指向空处,也就断开了引用,
undefined则是JS引擎给的默认值,默认值也是值,所以引用没有被断开
原型链
注:[[ ]] es规范规定用来表示对象的内部属性或方法
每一个对象都有一个内置属性 [[prototype]] 他的值是一个链接指向另一个对象(也就是对象的原型),
通常通过 _ _ prototype_ _ 或者getPrototypeeOf 去获取他
一般来说这个对象的原型是他的构造函数的prototype
每一个函数都有 prototype 属性 ,这个指向函数的显式原型,在js中函数也可以当做一个对象,所以他是有_ _ prototype_ _属性的
每个原型对象都一个constructor 属性,他一般指向实例的构造函数
Set Map
Set 是一种名为集合的数据结构,元素不可以重复,通过new Set(可迭代对象) 创建(其实所创建的可以理解为Set对象),可以使用Array.form()转换为数组
打印结构是这样滴 { XX ,XX ,XX }
add(value)向 Set 添加一个新元素Set 对象本身,允许链式调用delete(value)从 Set 中删除指定的元素Boolean,删除成功返回 true,否则返回 false
has(value)检查 Set 是否包含指定的元素Boolean,如果存在则返回 true,否则返回 false
clear()移除 Set 中的所有元素无返回值
forEach(callback, [, thisArg])遍历 Set 的每个元素并执行回调函数
WeakSet 只能存储对象类型(可以new的类型),对元素的引用是弱引用(既被WeakSet引用的元素可以被GC回收)
Map 用于存储映射关系,也就是键值对,Map的key值可以是对象( 普通对象作为key,这个时候键值对会自动将对象转成字符串来作为key ),通过new 创建
打印结构是这样滴 { XX => xx }
set(key, value)在Map中添加key、value返回整个Map对象
get(key)根据key获取Map中的value返回对应的value
has(key)判断是否包括某一个key返回Boolean类型
delete(key)根据key删除一个键值对返回Boolean类型
clear()清空所有的元素无返回值`
forEach(callback[, thisArg]) 通过forEach遍历Map,执行callback无返回值
WeakMap 的key只能存储对象类型(可以new的类型),对元素的引用是弱引用(既被WeakSet引用的元素可以被GC回收)
数据类型
js共有八种数据类型 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
- 其中基础类型是存储在栈中的简单数据段,大小固定,占据空间小
- 引用类型存储在堆中,大小不固定,占据空间大,其指针存储在栈中,指针指向堆中的实体地址
- 在数据结构中,栈是先进后出,堆是优先级队列,
- 在操作系统中,栈由编译器自动分配释放,堆由开发者分配释放
ES5的继承和ES6的继承有什么区别?
ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。
ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。
for in和for of的区别
for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
for…of遍历获取的是对象的键值,for…in获取的是对象的键名;for… in会遍历对象的整个原型链,性能非常差不推荐使用,而for … of只遍历当前对象不会遍历原型链;
new操作符的实现原理
使用new关键字创建的对象,可以访问构造函数中定义的属性和方法以及构造函数原型链中的属性和方法。
new操作符的执行过程:
- 创建一个新的空对象。
- 将新创建的对象的
__proto__属性指向构造函数的prototype属性。 - 将构造函数的
this关键字指向新创建的对象。 - 执行构造函数中的代码,给这个空对象添加属性和方法。
- 如果构造函数没有显式返回一个对象,则返回新创建的对象。
react
受控组件 非受控组件
-
受控组件:组件里的value受到state控制,用onChange事件处理函数处理value的变化并同步到state
-
非受控组件:value与state没有关联,需要用到的时候,用ref获取
setState是同步的还是异步的
-
legacy模式下:命中batchedUpdates时是异步 未命中batchedUpdates时是同步的
-
concurrent模式下:都是异步的。
concurrent模式:任何一个更新任务都可以被更高优先级中断插队,在高优先级任务执行之后再执行。
useCallback、useMemo 和 React.memo
useCallback
- 如果函数作为
props传递给子组件,使用useCallback可以避免子组件不必要的重新渲染。 - 适用于需要稳定函数引用的场景(例如,作为
useEffect的依赖项 )。
useMemo
- 适用于计算量较大的场景,避免重复计算。当计算结果用于渲染时,使用
useMemo可以避免不必要的重新渲染。 - 如果依赖项没有变化,
useMemo会返回缓存的值,减少不必要的计算。
React.memo
- 避免不必要的组件渲染,尤其是当父组件频繁渲染时。
- 适用于纯函数组件或
props变化较少的组件。 -
三者的区别
| 特性 | useCallback | useMemo | React.memo |
|---|---|---|---|
| 作用 | 缓存函数引用 | 缓存计算结果 | 缓存组件渲染结果 |
| 返回值 | 函数 | 任意值 | 组件 |
| 适用场景 | 避免函数重新创建 | 避免重复计算 | 避免组件重新渲染 |
| 性能优化目标 | 减少子组件的不必要渲染 | 减少重复计算的开销 | 减少组件渲染的开销 |
| 依赖项 | 依赖项变化时重新创建函数 | 依赖项变化时重新计算 | props 变化时重新渲染 |
react怎么区分Class组件和Function组件
- Class组件prototype上有isReactComponent属性
函数组件和类组件的相同点和不同点
相同点:
- 都可以接收props返回react元素
** 不同点**:
-
编程思想:类组件需要创建实例,面向对象,函数组件不需要创建实例,接收输入,返回输出,函数式编程
-
内存占用:类组建需要创建并保存实例,占用一定的内存
-
值捕获特性:函数组件具有值捕获的特性 下面的函数组件换成类组件打印的num一样吗
-
可测试性:函数组件方便测试
-
状态:类组件有自己的状态,函数组件没有只能通过useState
-
生命周期:类组件有完整生命周期,函数组件没有可以使用useEffect实现类似的生命周期
-
逻辑复用:类组件继承 Hoc(逻辑混乱 嵌套),组合优于继承,函数组件hook逻辑复用
-
更新:函数组件每次更新都会重新创建变量,类组件只需要实例化一次,后续更新都尽量复用实例上的状态、节点
-
跳过更新:shouldComponentUpdate PureComponent,React.memo
-
发展未来:函数组件将成为主流,屏蔽this、规范、复用,适合时间分片和渲染
什么是class可以实现 hooks实现不了的
- 错误边界问题 class有componentDidCatch捕获错误
- 解决:
- 使用 throw new Error()
- 第三方库react-error-boundary
- 手动捕获
- 没有全局异常捕获,出现问题导致白屏怎么办
- 使用
window.onerror捕获全局错误 - 使用
window.addEventListener('error')捕获全局错误
合成事件
| 类型 | 原生事件 | 合成事件 |
|---|---|---|
| 命名方式 | 全小写 | 小驼峰 |
| 事件处理函数 | 字符串 | 函数对象 |
| 阻止默认行为 | 返回false | event.preventDefault() |
理解
- React把事件委托到document上(v17是container节点上)
- 先处理原生事件 冒泡到document上在处理react事件
- React事件绑定发生在reconcile阶段 会在原生事件绑定前执行
原理
-
首先会把原方法,通过react事件插件映射表,转成react事件
-
在渲染过程中,fiber向上收集事件依赖,形成事件列表,放在fiber的memorizeState中,再向container容器注册事件监听器(document-v17+container),并bind fiber对象
-
最后dispatchEvent触发事件执行队列,模拟捕获、执行、冒泡的过程
优势
- 进行了浏览器兼容。顶层事件代理,能保证冒泡一致性(混合使用会出现混乱)
- 默认批量更新
- 避免事件对象频繁创建和回收,react引入事件池,在事件池中获取和释放对象(react17中废弃) react17事件绑定在容器上了
我们写的事件是绑定在dom上么,如果不是绑定在哪里?
v16绑定在document上,v17绑定在container上
为什么我们的事件手动绑定this(不是箭头函数的情况)
合成事件监听函数在执行的时候会丢失上下文
为什么不能用 return false 来阻止事件的默认行为?
说到底还是合成事件和原生事件触发时机不一样
react怎么通过dom元素,找到与之对应的 fiber对象的?
通过internalInstanceKey对应
componentWillMount、componentWillMount、componentWillUpdate为什么标记UNSAFE
- 新的Fiber架构能在scheduler的调度下实现暂停继续,排列优先级,Lane模型能使Fiber节点具有优先级,在高优先级的任务打断低优先级的任务时,低优先级的更新可能会被跳过,所有以上生命周期可能会被执行多次,和之前版本的行为不一致。
ref
-
用createRef、useRef可以创建出一个ref原始对象,可以把一些没必要更新视图的数据储存到ref对象中,只要组件没有销毁,ref对象就一直存在,这样能够直接修改数据,不会触发重新渲染。
-
useEffect,useMemo引用useRef对象中的数据,无须将ref对象添加成dep依赖项,因为ref指向一个内存空间,可以 随时访问到变化后的值
ref 挂载位置
- 类组件有一个实例 instance 能够维护像 ref 的各种信息
- 函数组件 useRef 产生的ref对象挂到函数组件对应的 fiber 上 (没有实例对象)
ref 执行时机
- 时机:对Ref的处理,都是在commit阶段发生的。
- commit阶段会进行真正的Dom操作,此时ref就是用来获取真实的DOM以及组件实例的。
逻辑:
- DOM更新之前 commitDetachRef,
- DOM 更新之后 commitAttachRef
hooks
为什么hooks不能写在条件判断中
- hook会按顺序存储在链表中,如果写在条件判断中,就没法保持链表的顺序
useState与setState对比
相同点:
- setState和useState底层都是调用了scheduleUpdateOnFiber方法,都是批量更新
不同点:
-
useState会默认浅比较两次state是否相同,然后决定是否更新组件
-
setState有专门监听state变化的callback,可以获取最新state,
但在函数组件中,改变的state只有在下一次函数组件执行时才能拿到,可以通过useEffect来监听state变化引起的副作用 -
setState是新老state进行合并使用,而useState是重新创建再赋值
-
Hooks 保存状态
-
函数组件的每个hooks都对应着一个workInProgressHook对象
-
hooks信息保存在对应fiber的memoizedState中
memorizedState链表。这个链表中的节点是React内部用于存储和管理Hooks状态的数据结构
memoizedState是该节点对应的状态值,而next是指向下一个节点的指针。这样就形成了一个链表,其中的节点对应于组件中的不同状态。
- 每个hooks通过next链表关联,形成一个Effect副作用链表
useState dispatchAction 原理
-
首先:每次调用dispatchAction都会先创建一个update,然后把它放入待更新pending队列中
-
判断如果当前的fiber是否正在更新,如果是,则停止更新流程
-
如果fiber没有更新任务,则继续更新,会浅比较 state,如果相同,那么直接退出更新;如果不相同,那么发起更新调度任务。
当再次执行useState的时候,会触发更新hooks逻辑,本质上调用的就是 updateReducer,会把待更新的队列 pendingQueue 拿出来,合并到 baseQueue,循环进行更新。
useEffect为什么会执行两次
因为严格模式,旨在帮助开发者更好发现useEffect逻辑错误
useEffect和useLayoutEffect异同
useEffect 是异步执行 ,对于每一个effect的callback,React会向 setTimeout 回调函数一样,放入任务队列,等到主线程任务完成,DOM更新后,才执行
所以useEffect回调函数不会阻塞浏览器绘制视图
useLayoutEffect是在DOM绘制之前,同步执行,会阻塞浏览器绘制。
可以方便修改DOM ,修改后浏览器只会绘制一次
修改DOM,改变布局就用useLayoutEffect,其他情况都用 useEffect
对于函数组件fiber,updateQueue存放每个useEffect/useLayoutEffect产生的副作用组成的链表。在commit阶段更新这些副作用
Fiber
fiber 是什么
- 架构
- 在旧的架构中,Reconciler(协调器)采用递归的方式执行,无法中断,节点数据保存在递归的调用栈中,被称为Stack Reconciler栈调和, 循环递归遍历VDOM,可能会造成页面卡顿;在新的架构中,Reconciler(协调器)是基于fiber实现的,节点数据保存在fiber中,所以被称为 fiber Reconciler。
- 静态数据结构
- fiber是 React element对象和真实DOM之间的桥梁,每个fiber就是一个节点,保存了这个节点的基本信息,这个时候,fiber节点就是虚拟DOM。
- 动态工作单元
- fiber节点保存了该节点需要更新的状态,以及需要执行的副作用,fiber在React中就是最小粒度的执行单元。
- 它可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的Fiber,使异步可中断的更新成为了可能
- Fiber树是一个双链表结构,next链接
- Fiber双缓存可以在构建好wip Fiber树之后切换成current Fiber,内存中直接一次性切换,提高了性能
- Fiber可以在reconcile的时候进行相应的diff更新,让最后的更新应用在真实节点上
diff算法
-
React的diff算法是React用于比较新旧虚拟DOM树并计算最小DOM操作集合的关键算法。
-
它采用了一种启发式的方法,基于三个主要策略:按层次比较、按类型比较和按key比较。
-
首先,React会按层次遍历新旧虚拟DOM树,并比较对应节点的类型。如果类型相同,则进一步比较属性;如果类型不同,则删除旧节点并创建新节点。
-
对于列表渲染,React使用key属性来识别列表项的身份,以便在列表发生变化时能够准确地移动、添加或删除节点。
源码解读
调用 reconcileChildrenArray 来调和子代 fiber,进行新老fiber的对比。
-
同层遍历,同层节点 sibling 指针移动遍历,判断fiber节点上的tag和key是否匹配,没有变化的节点直接复用oldFiber
-
当分层遍历结束时,统一删除没有复用的 oldfiber
-
当oldFiberMap为null,如果还有新的children,统一创建对应的 newFiber
-
当新旧节点都还有剩余,针对 发生移动 的情况, mapRemainingChildren 数组中查找有没有可以复用的 oldFiber
-
最后删除剩余没有复用的 oldFiber
react18
V18 新增了哪些特性
-
createRoot renderer api: Concurrent Mode(并发模式的渲染) 可以同时更新多个状态(从(V17)同步不可中断更新变成了(V18)异步可中断更新)
-
setState 自动批处理(多次更新合并成一次)
-
flushSync 提高state更新的优先级
-
React 组件的返回值可以为undefined、null
-
严格模式下,第二次渲染在控制台的日志将显示为灰色
-
Suspense不再需要fallback来捕获 (展示null)
-
新的Hook:useId(组件ID)、useSyncExternalStore(状态)、useInsertionEffect(注入css)
并发模式
-
是一种新的渲染策略,它允许React在渲染过程中中断和恢复工作,以便更好地响应用户输入和其他高优先级任务。
-
通过并发模式,React可以更好地管理资源的分配和使用,提高应用的响应性和性能。
-
并发模式还为React的未来扩展提供了基础,比如支持更复杂的动画和交互效果。
startTransition API
-
用于标记那些可能需要一段时间才能完成的更新(例如,从服务器获取数据)。
-
这个API允许React将UI的渲染划分为优先级较低的任务和优先级较高的任务。通过调用startTransition并传入一个回调函数,我们可以告诉React:“这个更新可以稍后完成,先处理其他更紧急的事情。”
-
使得React能够在等待数据加载时保持响应性,提供更好的用户体验。
虚拟列表
原理: 虚拟列表是一种长列表的解决方案, 在长列表滚动过程中,只有视图区域显示的是真实 DOM ,滚动过程中,不断截取视图的有效区域,让人视觉上感觉列表是在滚动。达到无限滚动的效果。
实现思路:
- 虚拟列表划分可以分为三个区域:视图区 + 缓冲区 + 虚拟区。
-
通过 useRef 获取元素,缓存变量
-
useEffect 初始化计算容器的高度。截取初始化列表长度。这里需要div占位,撑起滚动条。
-
通过监听滚动容器的onScroll事件,根据scrollTop来计算渲染区域向上偏移量
-
最后重新计算 end 和 start 高度来重新渲染可视区域+缓冲区域的列表
TS
TypeScript面试题八股集合——2023 - 掘金 (juejin.cn)
type和interface的区别
- 总结:interface是接口声明,type是自定义类型声明,接口可能是上下文参数的声明,而类型可能是接口中某个对象的属性类型
- interface定义数据的具体数据结构如何,有哪些属性;type定义数据的具体值是什么类型
- interface可以被class继承和实现,也可以继承class;type不行
- interface不能作为交叉、联合类型的产物,而type没有限制
- 定义两个同名的type会报异常,而interface会合并
泛型工具
将传入的属性变为可选项
type Partial<T> = {
[P in keyof T]?: T[P];
};
将传入的属性变为必填
type Required<T> = {
[P in keyof T]-?: T[P];
};
将传入的属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
定义keey value
type Record<K extends keyof any, T> = {
[P in K]: T;
};
在 T 中,过滤掉非 S 的类型
type Pick<T, K extends keyof T> = {
[key in K]: T[key]
}
从类型T中剔除所有可以赋值给类型U的类型
type Exclude<T, U> = T extends U ? never : T;
浏览器
事件循环
在谷歌浏览器源码中,他会开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务执行,过去消息队列分为宏任务微任务,现在更加细分队列,常见的有为队列有微队列,交互队列,计时队列。通常先执行全局js,不同的任务放到不同的队列,全局js执行完再按照优先级提取任务。
进程 线程
进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 进程中的任意一线程执行出错,都会导致整个进程的崩溃
- 线程之间共享进程中的数据。
- 当一个进程关闭之后,操作系统会回收进程所占用的内存
- 进程之间的内容相互隔离。
最新的 Chrome 浏览器包括:
- 1 个浏览器主进程:主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 1 个 GPU 进程 动画渲染ui
- 1 个网络进程:负责页面的网络资源加载
- 多个渲染进程 html css的解析js的v8都是在这里运行,每个标签页都会有一个渲染进程
- 多个插件进程:主要是负责插件的运行
异步
- js是一门单线程语言,它运行在浏览器的主线程中,而渲染主线程只有一个(渲染页面,js执行)用同步方式可能会导致阻塞,页面无法及时更新可能会出现卡死现象。
- 所以会采用异步的方式避免,就是当像计时器、网络、事件监听 浏览器会把任务先交给其他线程执行,然后立即结束任务去执行别的任务,当其他线程执行完会把传递的回调函数包装成任务放到消息队列末尾排队等待浏览器执行
队列
- 微队列:用户存放需要最快执行的任务(最高优先级)
- 交互队列:存放用户操作产生的事件处理任务(高优先级)
- 计时队列:存放计时器任务(中优先级)
浏览器渲染
- 浏览器网络进程收到html文档之后,会产生一个渲染任务,把他放到渲染主线程的消息队列中,在事件循环的作用下,浏览器取出渲染任务开始执行
渲染任务分多个阶段
- html解析:解析html,遇到css解析css,遇到js解析js,为了提高解析效率会有一个预解析的线程,会下载解析外部的css和下载外部的js,html解析完会得到dom树和css树
- 样式计算:计算每个节点样式得到带样式的dom树
- 布局:计算每个样式的几何信息然后得到布局树
- 分层:对整个布局树进行分层处理,这样的好处是,日后某一个层改变后,仅对该层做后续处理提升效率。(跟堆叠上下文有关)
- 绘制:为各个层产生绘制指令集,描述该层内容如何画出来,完成后将绘制信息交给合成线程处理
- 分块:合成线程会将每个层分成多个小区域
- 光栏化:交给GPU进程完成光栏化,生产位图(像素点)
- 呈现画出来
浏览器缓存
对浏览器的缓存机制的理解
浏览器缓存的全过程:
- 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用;
- 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;
- 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;
- 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
- 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200;
很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时获得网站的最新更新。
区别
(1)强缓存
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。
强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。
(1)服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
(2)Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,
Cache-Control可设置的字段:
public:设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用max-age=来精确控制;private:设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存;no-cache:设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源;no-store:设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源;max-age=:设置缓存的最大有效期,单位为秒;s-maxage=:优先级高于max-age=,仅适用于共享缓存(CDN),优先级高于max-age或者Expires头;max-stale[=]:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires。
no-cache和no-store很容易混淆:
- no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存;
- no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。
(2)协商缓存
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。
上面已经说到了,命中协商缓存的条件有两个:
max-age=xxx过期了- 值为
no-store
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。
协商缓存也可以通过两种方式来设置,分别是 http 头信息中的Etag 和Last-Modified属性。
(1)服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。
(2)因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
总结:
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
网络
用户输入url浏览器执行过程
-
浏览器接收url并开启一个新进程(这一部分可以展开浏览器的进程与线程的关系)
-
浏览器解析输入的 URL,提取出其中的协议、域名和路径等信息。(这部分涉及URL组成部分)
-
浏览器向 DNS 服务器发送请求,DNS服务器通过 多层查询 将该 域名 解析为对应的 IP地址 ,然后将请求发送到该IP地址上,与 服务器 建立连接和交换数据。(这部分涉及DNS查询)
-
浏览器与服务器建立 TCP 连接。(这部分涉及TCP三次握手/四次挥手/5层网络协议)
-
浏览器向服务器发送 HTTP 请求,包含请求头和请求体。(4,5,6,7包含http头部、响应码、报文结构、cookie等知识)
-
服务器接收并处理请求,并返回响应数据,包含状态码、响应头和响应体。
-
浏览器接收到响应数据,解析响应头和响应体,并根据状态码判断是否成功。
-
如果响应成功,浏览器接收到http数据包后的解析流程(这部分涉及到html - 词法分析,解析成DOM树,解析CSS生成CSSOM树(样式树),合并生成render渲染树(样式计算)。然后layout布局,分层,调用GPU绘制等,最后将绘制的结果合成最终的页面图像,显示在屏幕上。这个过程会发生回流和重绘)。
-
连接结束 -> 断开TCP连接 四次挥手
3. 请求/响应头部
常用的请求头部(部分):
Accept: 接收类型,表示浏览器支持的MIME类型
(对标服务端返回的Content-Type)
Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type:客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since:对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中
Expires:缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间
Max-age:代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中
If-None-Match:对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中
Cookie: 有cookie并且同域访问时会自动带上
Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host:请求的服务器URL
Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私
Referer:该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)
User-Agent:用户客户端的一些必要信息,如UA头部等
常用的响应头部(部分):
Access-Control-Allow-Headers: 服务器端允许的请求
Headers
Access-Control-Allow-Methods: 服务器端允许的请求方法
Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*)
Content-Type:服务端返回的实体内容的类型
Date:数据从服务器发送的时间
Cache-Control:告诉浏览器或其他客户,什么环境可以安全的缓存文档
Last-Modified:请求资源的最后修改时间
Expires:应该在什么时候认为文档已经过期,从而不再缓存它
Max-age:客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效
ETag:请求变量的实体标签的当前值
Set-Cookie:设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端
Keep-Alive:如果客户端有keep-alive,服务端也会有响应(如timeout=38)
Server:服务器的一些相关信息
404 错误的常见原因
(1)客户端原因
-
错误的 URL:
- 用户输入了错误的 URL。
- 客户端代码中拼写错误或路径错误。
-
过期的链接:
- 客户端使用了旧的、已失效的链接。
(2)服务器原因
-
资源被删除或移动:
- 服务器上的文件或资源被删除或移动,但链接未更新。
-
路由配置错误:
- 服务器未正确配置路由,导致无法找到请求的资源。
HTTP协议的版本历经多次更新迭代,主要包括 HTTP/1.0、HTTP/1.1和HTTP/2等版本,它们之间的主要区别如下:
1)HTTP/1.0:
- 浏览器与服务器只保持
短连接,浏览器的每次请求都需要与服务器建立一个TCP连接,都要经过三次握手,四次挥手。而且是串行请求。 - 由于浏览器必须等待响应完成才能发起下一个请求,造成
“队头阻塞”。
如果某请求一直不到达,那么下一个请求就一直不发送。(高延迟–带来页面加载速度的降低)
2)HTTP/1.1:目前使用最广泛的版本
- 支持
长连接,通过Connection: keep-alive保持HTTP连接不断开,避免重复建立TCP连接。 - 支持
管道化传输,通过长连接实现一个TCP连接中同时处理多个HTTP请求;只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
服务器会按照请求的顺序去返回响应的内容,无法存在并行响应。(http请求返回顺序按照服务器响应速度来排序,这里也会引入promise.then 和 async await 来控制接口请求顺序) - 新增了一些请求方法,新增了一些请求头和响应头(如下)
- 支持
断点续传, 新增 Range 和 Content-Range 头表示请求和响应的部分内容 - 加入缓存处理(响应头新字段Expires、Cache-Control)
- 增加了重要的头
Host字段;为了支持多虚拟主机的场景,使用同一个IP地址上可以托管多个域名,访问的都是同一个服务器,从而满足HTTP协议发展所需要的更高级的特性。 - 并且添加了其他请求方法:put、delete、options…
缺点:
- 队头阻塞
- 无状态通信模型(巨大的HTTP头部),也就是服务器端不保存客户端请求的任何状态信息。这样会造成一些需求频繁交互的应用程序难以实现,需要通过其他机制来保证状态的一致性等。
- 明文传输–不安全
- 不支持服务端推送
3)HTTP/2.0:
- 采用
二进制格式而非文本格式 多路复用,在同一个TCP连接上同时传输多条消息;每个请求和响应都被分配了唯一的标识符,称为“流(Stream)”,这样每条信息就可以独立地在网络上传输。- 使用 HPACK 算法
报头压缩,降低开销。 服务器推送,支持服务器主动将相关资源预测性地推送给客户端,以减少后续的请求和延迟。(例如 HTML、CSS、JavaScript、图像和视频等文件)
4)HTTP3.0
是 HTTP/3 中的底层支撑协议,该协议基于 UDP,又取了 TCP 中的精华,实现了即快又可靠的协议。
- 运输层由TCP改成使用UDP传输
- 队头堵塞问题的解决更为彻底
- 切换网络时的连接保持:基于TCP的协议,由于切换网络之后,IP会改变,因而之前的连接不可能继续保持。而基于UDP的QUIC协议,则可以内建与TCP中不同的连接标识方法,从而在网络完成切换之后,恢复之前与服务器的连接
- 升级新的压缩算法
注意: HTTP 1.1起支持长连接,keep-alive不会永远保持,它有一个持续时间,一般在服务器中配置(如apache),另外长连接需要客户端和服务器都支持时才有效。
管道传输和多路复用的区别
HTTP/2 的多路复用可以理解为一条公路上同时行驶多辆车的场景,每辆车对应一个请求或响应,而公路对应一个 TCP 连接。
在 HTTP/1.x 中,只能一辆车(请求或响应)通过这条公路,其他车必须等待前面的车通过后再行驶;
而在 HTTP/2 中,则允许多辆车同时在这条公路上行驶,它们之间不会互相干扰或阻塞,从而提高了公路的使用效率和通行能力。
前端安全
跨站脚本攻击(XSS)
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
-
反射型的 XSS的恶意脚本存在URL里,存储型 XSS的恶意代码存在数据库里。 -
反射型 XSS攻击常见于通过 URL 传递参数的功能,如网站搜索、跳转等。 -
存储型XSS攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。 -
而基于
DOM的XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端JavaScript自身的安全漏洞,其他两种XSS都属于服务端的安全漏洞。
预防
- 设置
Cookie 的 HttpOnly属性,禁止JavaScript读取cookie - csp白名单
- 验证码:防止脚本冒充用户提交危险操作。
跨站请求伪造(CSRF)
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
预防
- 同源检测
- CSRF token验证
- 给 Cookie 设置合适的 SameSite
webpack
谈谈你对webpack的看法:
webpack是一个模块打包工具,可以使用它管理项目中的模块依赖,并编译输出模块所需的静态文件。它可以很好地管理、打包开发中所用到的HTML,CSS,JavaScript和静态文件(图片,字体)等,让开发更高效。对于不同类型的依赖,webpack有对应的模块加载器,而且会分析模块间的依赖关系,最后合并生成优化的静态资源。
webpack 的主要特点包括:
- 模块化:支持 CommonJS、AMD、ES6 模块等多种模块规范。
- 资源管理:可以处理 JavaScript、css样式表、图片等各种资源文件。
- 代码拆分:能够将代码拆分成多个 bundle,实现按需加载。
- 插件系统:丰富的插件生态系统,可以扩展 webpack 的功能。
- 热更新:支持热模块替换(Hot Module Replacement),在开发过程中实现实时更新。
有哪些常见的Loader?
file-loader: 加载文件资源,如 字体 / 图片 等,具有移动/复制/命名等功能;url-loader: 通常用于加载图片,可以将小图片直接转换为 Date Url,减少请求;babel-loader: 加载 js / jsx 文件, 将 ES6 / ES7 代码转换成 ES5,抹平兼容性问题;ts-loader: 加载 ts / tsx 文件,编译 TypeScript;style-loader: 将 css 代码以<style>标签的形式插入到 html 中;css-loader: 分析@import和url(),引用 css 文件与对应的资源;postcss-loader: 用于 css 的兼容性处理,具有众多功能,例如 添加前缀,单位转换 等;less-loader / sass-loader: css预处理器,在 css 中新增了许多语法,提高了开发效率;
常用 Plugin:
-
htmlWebpackPlugin: 自动在打包结束后生成html文件,并引入bundle.js -
ExtractTextPlugin: 提取css到单独的文件, 分离加载样式,避免加载闪烁,并行下载css文件,提高加载速度 -
webpack-bundle-analyzer:可视化webpack输出文件的体积 -
web-webpack-plugin:方便单页应用输出html -
webpack-parallel-uglify-plugin:多进程执行代码压缩,提升构建速度 -
define-plugin:定义环境变量
loader和plugin的区别
作用:
- loader: 翻译官: 对其他资源进行预处理;因为webpack只认识javascript
- plugin:扩展webpack的功能,监听webpack提供的api改变输出的结果
配置:
- loader, module rules中配置,类型为数组,每一项都是一个Object, 内部包含test, loader, options等
- pugin单独配置,每一项是一个plugin的实例,参数通过构造函数传入。
babel的编译过程
Babel是⼀个 JavaScript 编译器 ,本质上就是在操作 AST 来完成代码的转译。AST是抽象语法树(Abstract Syntax Tree, AST)
⼯作过程可以分为三部分:
-
解析(Parse) :将源代码转换成更加抽象的表示⽅法(例如抽象语法树)。包括词法分析和语法分析。词法分析主要把字符流源代码(Char Stream)转换成令牌流( Token Stream),语法分析主要是将令牌流转换成抽象语法树(Abstract Syntax Tree,AST)。
-
转换(Transform) :通过 Babel 的插件能⼒,对(抽象语法树)做⼀些特殊处理,将⾼版本语法的 AST 转换成⽀持低版本语法的 AST。让它符合编译器的期望,当然在此过程中也可以对 AST 的 Node 节点进⾏优化操作,⽐如添加、更新以及移除节点等。
-
⽣成(Generate) :将 AST 转换成字符串形式的低版本代码,同时也能创建 Source Map 映射。
webpack解决跨域及其原理
跨域问题是由于浏览器的同源策略引起的,同源策略是一种安全机制,当协议、域名或端口不同,浏览器会禁止访问资源。在开发过程中,使用 Webpack 可以通过配置代理来解决跨域问题。
使用代理解决跨域问题
Webpack 提供了 devServer.proxy 配置项,可以将请求代理到其他服务器。以下是一个简单的配置示例:
// webpack.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '' },
changeOrigin: true,
secure: false,
},},},};
webpack热更新原理是什么?
自己开启了express应用,添加了对webpack编译的监听,添加和浏览器websocket长连接,当文件变化触发webpack进行编译完成后,会通过socket消息告诉浏览器准备刷新,不用刷新网页,而是刷新某个模块,通过hash值来对比需要更新的模块
总结:
- 启动webpack, 生成compiler, 比如启动webpack编译工作,监听本地文件变化;
- 使用express启动本地server, 让浏览器可以请求本地的静态资源
- 本地server启动后,再去启动websocket服务,通过websocket, 建立本地服务和浏览器的双向通信,及本地文件发生变化,告知浏览器更新代码
websocket是一种网络通信协议,它的主要特点是,建立在TCP协议上,服务端实现比较容易,与HTTP协议有着良好的兼容性,数据格式轻量,可以发送文本和二进制数据,没有同源限制,客户端可以与任意服务器通信,服务器也可以给客户端推送信息。
打包流程
- 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置参数。
- 开始编译:从上一步得到的参数初始化
Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译。 - 确定入口:根据配置中的
entry找出所有的入口文件。 - 编译模块:从入口文件出发,调用所有配置的
loader对模块进行翻译,再找出该模块依赖的模块,这个步骤是递归执行的,直至所有入口依赖的模块文件都经过本步骤的处理。 - 完成模块编译:经过第 4 步使用 loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
chunk,再把每个chunk转换成一个单独的文件加入到输出列表,这一步是可以修改输出内容的最后机会。 - 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
webpack5新特性介绍
- 启动命令
- 持久化缓存
- 资源模块
- moduleIds & chunkIds的优化
- 更智能的tree shaking
- nodeJs的polyfill脚本被移除
- 模块联邦 [](webpack5上手指南 - 掘金 (juejin.cn))
性能优化
Chrome devtools Lighthouse
Lighthouse 可以给出这样一份报告。
这份报告从 5 个方面来分析页面: 性能、辅助功能、最佳实践、搜索引擎优化和 PWA。像性能方面,会给出一些常见的耗时统计。
指标
- Lighthouse Performance 评分
- FCP(First Contentful Paint 首次内容绘制)
- LCP(Largest Contentful Paint 最大内容绘制)
- TTI(Time to Interactive 可交互时间)
- Speed Index(速度指数)
- TBT(Total Blocking Time 总阻塞时间)
性能优化建议主要包括以下几点:
- 减少未使用的 JS;
- 合理使用图片的格式,webp 或者 avif 更快;
- 延迟加载不在视图的图片;
- JS 压缩;
- 图片的尺寸大小应该适当;
- 减少未使用的 CSS。
Lighthouse 诊断出的网站存在的问题:
- 需要加载的资源太多太大,有 147 个请求,合计 11mb;
- 有 40 个静态资源的缓存只有 1 小时
- 滚动事件没有添加标记
{passive: true}),导致需要等待侦听器完成执行后再滚动页面; - 图像元素没有设置明确的宽度和高度;
- JS 文件太多,主线程工作量太大、JS 执行时间太长;
优化
组件按需加载
React.lazy + Suspense 封装懒加载组件,路由级组件引入懒加载组件。 同时使用骨架屏作为懒加载的兜底组件,可以让用户感知加载更快。 在鼠标移入导航栏时预加载路由组件,可以加快页面展示。
工具库按需加载
通过 import('xx').then(xx) 按需加载工具库。
静态资源上传 CDN
项目内有一些 json 文件存储的静态数据,这部分文件上传至 CDN,改为 fetch 的方式按需引入。
删除不需要的资源
检查项目中引入的 mf、npm 资源,将没有使用到的删除。
避免重复的 npm 包引入
发现业务组件库通过 npm 引入的原子组件库,而项目本身又是通过 mf 引入的原子组件库,相对于引入了 2 遍原子组件库。
这时就需要改造业务组件库,也改成用 mf 的方法引入。
1.8 避免 esm 依赖嵌套
因为 webpack 的按需加载是通过 import、export 来标记的,因此想要一个好的按需加载的效果,就需要避免依赖嵌套的问题。
1.9 图标按需加载
原子组件库 mf 暴露的方式会导致只用了 1 个 icon,就会加载组件库下所有 icon 对应的 chunk,导致资源浪费。
新建一个 icon 的 npm 包用于 icon 的按需引入。
-
体积优化
-
组件按需加载lazy+suspense
-
静态资源,第三方库上传cdn
- externals 选项配置外部依赖,告诉 webpack 这些依赖将在运行时由外部环境提供,而不需要将其打包到最终的构建文件中
-
splitChunks提取公共代码
- index中script引入cdn
- minChunks:引用阈值,被引用次数超过该阈值的模块才会被拆包处理;
- maxInitialRequest/maxAsyncRequests:用于限制Initial/Async Chunk最大并行请求数,本质上是在限制最终产生的分包数量;
- minSize: 超过这个大小的 Chunk 会被拆包;
- maxSize: 超过这个大小的 Chunk 会尝试继续拆包;
- cacheGroups:缓存组规则,为不同类型的资源设置更有针对性的拆包策略。
- priority: 优先级,在缓存组规则中使用
-
体积降低60% 差不多4mb
-
-
构建速度优化
- speed-measure-webpack-plugin构建速度分析
- 持久化缓存cache:{ type:filesystem}
- 打包文件的时候,可以选择生成sourcemap文件
- 使用style-loader不使用MiniCssExtractPlugin,对热更新支持不友好
- thread-loader多线程 balel-loader开启缓存
- 优化结果70%左右
react优化
业务场景渲染优化
当要一次render渲染的数据量或者dom非常大时候,可以考虑使用useTranstion优化,利用startTransition(回调函数)开启并发模式,降低任务优先级
####文件下载
const handleExport = async () => {
try {
const data = { name: "低代码平台数据", components: [] }; // 替换为实际数据
const jsonString = JSON.stringify(data, null, 2);
// 请求用户选择保存位置
const handle = await window.showSaveFilePicker({
suggestedName: 'data.json',
types: [{
description: 'JSON Files',
accept: { 'application/json': ['.json'] },
}],
});
// 写入文件内容
const writable = await handle.createWritable();
await writable.write(jsonString);
await writable.close();
} catch (err) {
console.error('用户取消保存或浏览器不支持 API:', err);
}
};
//navigator.userAgentData判断浏览器版本
const handleExport = () => {
const data = { name: "低代码平台数据", components: [] }; // 替换为实际数据
const jsonString = JSON.stringify(data, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'data.json'; // 默认文件名
link.click();
URL.revokeObjectURL(url);
};