笔记摘要
8.12
MessageChannel
MessageChannel 常用于主线程与 Web Worker 线程之间的通信,尤其是当需要在Worker完成计算或处理后,向主线程报告结果时
0. 避免宏任务队列堵塞:在某些场景下,使用setTimeout或Promise来延迟执行可能不够精确或会导致不必要的延迟。通过MessageChannel,可以在当前事件循环的微任务队列之外安排任务,避免阻塞UI线程。
0. 跨源Iframe通信:虽然通常使用postMessage直接进行跨iframe通信,但结合MessageChannel可以提供更安全和灵活的通信方式,尤其是在需要持续的双向通信时
Vue 和 React
0. 不可变数据VS可变数据
0. 双向数据 单向数据流
0. 函数式编程 渐进式开发体验
0. 引用组件重新渲染 组件为根目录,默认全部重新渲染
类组件与函数组件
类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
渲染性能上:shouldComponentUpdate React.memo
Hooks 比原先更细粒度的逻辑组织与复用 时间切片与并发模式
react异常
错误边界(Error Boundary)
Window 错误监听事件 window.addEventListener('error')
useEffect的错误处理
懒渲染 react-visibility-observer 进行监听
结合React.lazy和Suspense使用动态import(),可以实现组件级别的懒加载
Scheduler(调度器)
负责调度组件的渲染更新任务 车道模型 执行顺序
Reconciler(协调器)
Fiber节点的生成 Fiber任务
任务的执行与中断(Scheduler)
Commit阶段
Reconciler会生成一棵新的workInProgressFiber树+逐渐diff
React18有哪些更新
setState自动批处理
• 17中只有react合成事件会进行批处理,legacy模式;
• 18中所有事件都进行批处理,提高了性能,concurrent模式。
支持并发模式的渲染
去掉了对IE浏览器的支持
flushSync,用于退出批量更新
react组件返回值更新
• 17中返回空组件只能返回null,返回undefined会报错
• 18中支持null和undefined
支持useId(在服务器和客户端生成相同的唯一一个id,避免hydrating的不兼容)
新增hooksuseInsertionEffect:只建议在css in js库中使用,在dom生成之前执行,在useLayoutEffect之前,一般用于提前注入脚本
useSyncExternalStore:解决外部数据撕裂问题
Suspense不再需要fallback捕获
并发模式
undefined:
擅长领域:低代码建设,可视化搭建,性能优化
undefined:
医疗健康数据可视化平台 - 医疗研究机构
在某知名医疗研究机构,我参与设计并实施了一个低代码医疗健康数据可视化项目。该平台整合了来自不同医院、电子病历系统和公共卫生数据库的海量数据,通过低代码工具快速构建了交互式仪表板和分析报告,支持研究人员实时监控疫情趋势、疾病分布、药物疗效评估等关键指标。低代码的灵活性使我们能够快速迭代,根据研究需求调整数据模型和可视化界面。该项目极大地加速了研究进程,助力多项研究成果提前发布,对公共卫生产生了积极影响
undefined:
自定义递增数字的hook useIncreateNumber useFormValidate..
8.13
forwardRef + useImperativeHandle(自定义暴露内容)传递组件ref到父组件
const ChildComponent = forwardRef((props, ref) => {
const internalState = useRef(0);
useImperativeHandle(ref, () => ({
increment: () => {
internalState.current += 1;
},
decrement: () => {
internalState.current -= 1;
},
reset: () => {
internalState.current = 0;
},
getState: () => internalState.current,
}));
return <div>{`Child State: ${internalState.current}`}</div>;
});
Babel原理
解析=>as tree =>插件转换=>生成新代码以及Source Maps原代码
并发模式 react
React 16.6引入
根据应用的状态和用户交互智能地调度渲染任务
分片渲染 挂起和恢复(Suspense)
错误发生时,React可以暂停渲染过程,给予应用机会去恢复或优雅地展示错误信息,而不是立即崩溃。
可中断的渲染 同步与异步渲染的混合
函数组件与类组件的区别:
0. 类组件需要声明constructor,函数组件不需要
0. 类组件需要手动绑定this,函数组件不需要
0. 类组件有生命周期钩子,函数组件没有
0. 类组件可以定义并维护自己的state,属于有状态组件,函数组件是无状态组件
0. 类组件需要继承class,函数组件不需要
0. 类组件使用的是面向对象的方法,封装:组件属性和方法都封装在组件内部 继承:通过extends React.Component继承;函数组件使用的是函数式编程思想
React 路由
监听URL(通常是popstate事件和History API的监听)=>匹配路由规则=>找到渲染组件,卸载旧的渲染新的到指定的容器
Fiber 架构有两个阶段,render 阶段就是负责构架 Fiber 对象和链表,而 commit 阶段就是负责去构建 DOM
• Scheduler(调度器);调度任务的优先级,高优任务优先进入Reconciler;代替requestIdleCallback
• Reconciler(协调器);负责找出变化的组件 Fiber 打标记
• Renderer(渲染器);负责将变化的组件渲染到页面上
8.14
bug场景: 项目列表每次导出的数据都不是查询之后的数据
原先代码实现: 查询条件对象使用useMemo记忆,依赖项是查询的每个字段。导出执行的函数是使用useCallback的记忆函数,空依赖项。函数里引用了useMemo返回的查询条件,结果出现该问题。
原因:
• 初始渲染时,useMemo根据其依赖计算一个值,useCallback创建一个引用该useMemo返回值的回调函数。
• 当useMemo的依赖改变时,它会计算新的值,但由于useCallback的依赖数组是空的,它不会创建新的回调函数,仍然保持对初次渲染时useMemo返回值的引用。
• 这就解释了为什么在后续调用useCallback的回调时,即使useMemo内部的值已经因为依赖变化而更新,回调函数内部使用的始终是最初计算的那个useMemo返回值。因为回调函数保持不变,它内部封闭的环境(包括对useMemo返回值的引用)也就保持不变。
解决办法:在useCallback的依赖数组中包含对useMemo返回值的引用。这样,当useMemo的值因为依赖变化而更新时,useCallback也会得到通知并创建一个新的回调函数,确保每次调用时都能获取到最新的useMemo结果
扩展:useCallback回调函数中除了memo返回值,其他hook如:useCallback 函数也要添加到依赖项中去
useMemo返回值的引用指向堆中一个对象的引用:
在JavaScript中,基本类型(如数字、字符串、布尔值等)是存储在栈中的,而复杂类型(如对象、数组、函数等)则存储在堆中,栈中存放的是指向这些堆中数据的指针。当你使用useMemo计算并返回一个复杂类型的值(如对象、数组)时,这个返回值实际上是堆内存中的一个地址引用,这个引用被保存在函数组件的作用域或相关的React内部状态中
如果某个值的变化应当触发useMemo重新计算,那么这个值应当被列在useMemo的依赖数组中;同样,如果useCallback的逻辑依赖于某个状态或prop,这些也应该被列为依赖项
React 算法之深度优先遍历
最主要的使用时在ReactElement和fiber树的构造过程. 其次是在使用context时, 需要深度优先地查找消费context的节点.
function dfs(node) {
const stack = [];
stack.push(node);
// 栈顶元素还存在, 就继续循环
while ((node = stack[stack.length - 1])) {
if (node.visited) {
console.log('回溯阶段: ', node.name);
// 回溯完成, 弹出该元素
stack.pop();
} else {
console.log('探寻阶段: ', node.name);
node.visited = true;
// 利用栈的先进后出的特性, 倒序将节点送入栈中
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
}
}
• Fiber 树由链表构成,节点间通过 return(父节点)、child(第一个子节点)、sibling(下一个兄弟节点)相连。
• 当前视图对应的 Fiber 树称为 current 树,每次协调发起,都会构建新的 workInProgress 树,并在结束时替换 current 树。
• Fiber 树的遍历方式是深度优先遍历,向下的过程由 beginWork 发起,向上的过程由 completeUnitOfWork 发起。beginWork 每次只向下一步,completeUnitOfWork 则每次向上若干步(由其内部若干个一步循环达成)。
• Fiber 树是边构建边遍历的,构建在 beginWork 向下过程中发起。
• Fiber 树的 Diffing 策略体现在构建过程中:父节点已复用、key 和 type 相同是节点复用的基本条件;子节点 Diffing 从易向难,单节点 Diffing —> 多节点末尾增删(一轮循环) —> 多节点其他情况(二轮循环)。
• Diffing 的结果,诸如节点的删除、新增、移动,称为 effect,以 effectTag 的形式挂在节点上。
• completeUnitOfWork 的内部循环会自底向上收集 effect,不断把有 effectTag 的子节点和自身向上合并到父节点的 effectList 中,直至根节点。effectList 是个链表。
• 宿主相关组件节点会把宿主实例挂到 stateNode 上,间接调用宿主方法对其完成创建、更新,由此也会产生 effectTag。
8.15
async defer异步下载
async 不保证顺序,下载完立即执行,暂停解析
defer 保证执行顺序,下载完需要等到页面解析后,DOMContentLoaded事件触发之前执行
浏览器
渲染引擎-浏览器引擎
• Chrome 和 Edge 使用 Blink 引擎(原本是Webkit的一个分支)。
• Safari 使用 WebKit 引擎。
• Firefox 使用 Gecko 引擎,现在正在转向新的Quantum项目,特别是GeckoView。
• Internet Explorer 使用 Trident(老版本)和 EdgeHTML(Edge旧版,已不再维护)。
渲染层(也称作布局层或图形层)和合成层
合成层是浏览器渲染引擎中的一种优化策略,它将页面分割成多个独立的图层,并在GPU上进行组合(composite)
• 图层划分:当元素具有某些特定属性,如transform、opacity、filter(部分情况下)或被指定为will-change属性时,浏览器会将其提升为独立的合成层。
• GPU加速:合成层能够在GPU上独立渲染,然后由浏览器合成这些图层来形成最终画面。这减少了重排和重绘的需求,提高了动画的流畅性。
• 并发处理
布局: 重排(Reflow)或回流 位置和大小 布局树 绘制指令 分层信息(哪些可以提升成合成层)
绘制:元素信息转换成实际屏幕上的像素颜色值数据 合成层,它的绘制记录会被转化为纹理(Texture),存储在GPU内存中 位图/像素数据(绘制记录被转换成实际的位图数据,即像素颜色值的集合) 纹理(对于合成层)
光栅化:属于绘制阶段里面。绘制命令随后被转换为实际的像素数据。它将绘制指令转换为具体的像素阵列。对于文本,这可能包括将字体形状映射到像素网格;对于形状和图像,则涉及抗锯齿处理和颜色空间转换。光栅化过程的直接结果是生成一帧的像素数据,即每个像素点的颜色值。这些数据构成了屏幕上用户可以看到的实际图像。
合成:对于被提升为合成层的图层,它们各自的位图或图块在GPU内存中被管理。合成器(Compositor)负责将这些图层按照正确的顺序和位置叠加在一起,形成最终的页面视图。这个过程可以在独立的合成线程中进行,与主渲染进程并行工作,从而减少阻塞和提升性能
显示:合成后的位图被传递到显示器上显示 帧缓冲区(framebuffer)的更新
渲染层通过布局、绘制、光栅化生成位图,然后在合成阶段将这些位图在GPU上组合,最后输出到屏幕上
JavaScript引擎
• V8:Chrome 和 Edge 浏览器使用。
• JavaScriptCore(也被称作Nitro或SquirrelFish):主要用于Safari。
• SpiderMonkey:Firefox 使用。
• Chakra(后来的ChakraCore):曾经用于Internet Explorer和早期的Microsoft Edge
网络请求“引擎”
用户界面(UI)“引擎”
数据存储“引擎”
防抖 延迟执行,只执行最后一次触发
节流 限制执行频率 一段时间内 只执行一次
webpack
入口
loader 解析文件 专注于转换单个文件的内容,遵循从右到左的执行顺序
plugin 扩展功能,构建过程中特定事件触发,插件通过在特定的生命周期钩子(hooks)注册回调函数来监听事件,从而在合适的时机执行
出口
常见Loader
0. babel-loader: 用于将ES6+语法转换为浏览器兼容的JavaScript代码。
0. ts-loader 或 awesome-typescript-loader: 将TypeScript编译为JavaScript。
0. css-loader 和 style-loader: 用于加载和插入CSS到DOM中。css-loader负责解析CSS文件中的@import和url(),而style-loader则将CSS插入到页面中。
0. sass-loader 或 less-loader: 分别用于将Sass或Less预处理器编写的样式转换为CSS。
0. file-loader 和 url-loader: 用于处理文件资源,如图片、字体文件等。file-loader将文件发送到输出目录,并返回URL,而url-loader可以在一定阈值下将文件转换为base64编码直接嵌入代码中。
0. image-webpack-loader: 对图片进行压缩和优化。
0. eslint-loader: 在构建过程中运行ESLint检查代码质量。
• thread-loader 多进程去打包 放置在其他 loader 之前, 那 thread-loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行
• HappyPack
常见插件
0. HtmlWebpackPlugin: 自动创建HTML文件,并将打包生成的JS文件自动注入到HTML中。
0. MiniCssExtractPlugin: 将CSS从JS中提取出来,生成单独的CSS文件,有利于缓存和加载速度。
0. UglifyJsPlugin 或 TerserWebpackPlugin: 用于压缩JavaScript代码,减少文件体积。
0. CleanWebpackPlugin: 在每次构建前清空输出目录。
0. CopyWebpackPlugin: 复制静态资源到输出目录。
0. DefinePlugin: 在编译时定义全局常量,可用于环境变量替换。
0. HotModuleReplacementPlugin: 实现模块热替换(HMR),在开发过程中无需刷新页面即可实时看到代码更改的效果。
0. OptimizeCssAssetsWebpackPlugin: 压缩和优化CSS资产。
0. BundleAnalyzerPlugin: 分析打包后资源的大小,帮助识别优化点。
• HardSourceWebpackPlugins 提升二次构建及后续构建的速度
多进程/多实例+压缩类
• terser-webpack-plugin 开启 parallel 参数,使用多进程并行运行 (推荐使用这个,支持 ES6 语法压缩)
• parallel-uglify-plugin 插件
• uglifyjs-webpack-plugin 开启 parallel 参数
分割打包
• Webpack 4+:SplitChunksPlugin
• Webpack 3:CommonsChunkPlugin
缓存
cache-loader 性能开销大的 loader 之前添加此 loader
use: ["cache-loader", ...loaders]
多进程/多实例
• thread-loader 高开销的 loader 中使用
• parallel-web-pack 多入口点的项目
• HappyPack。对 file-loader、url-loader 支持的不友好
8.16
HotModuleReplacementPlugin
webpack-dev-server (开启一个HTTP服务器)=> (通过HotModuleReplacementPlugin向入口chunk中注入)HMR客户端代码(监听模块变化)=>WebSocket=> (通信)浏览器
HardSourceWebpackPlugin
提升二次构建及后续构建的速度。它通过缓存上一次构建过程中的一些中间结果,比如模块解析、loader处理结果等
缓存(loader处理输出、解析后的模块、插件处理结果等)
增量构建(项目再次构建时,通过缓存,复用之前构建的结果。如果某个模块或其依赖没有发生改变,那么这部分的编译工作就会被跳过,直接使用缓存中的结果)
thread-loader 适用提高那些CPU密集型loader
推荐用于:
babel-loader(Babel转换JavaScript代码是一个CPU密集型任务)
ts-loader(TypeScript编译也是耗时的)之前,其他重型loader: 任何执行复杂处理或转换的loader,如图像优化、模板编译等,都可以考虑使用thread-loader来加速
不建议:
轻量级loader 资源消耗大 异步执行,不能保证loader执行顺序
SplitChunksPlugin
Webpack的一个内置插件,用于代码拆分,通过optimization.splitChunks来配置。减少初始加载时间 缓存 并行加载 将公共的、可复用的代码分离到单独的chunk。 CommonsChunkPlugin已废弃,被SplitChunksPlugin取代
terser-webpack-plugin
压缩JavaScript代码的插件。配置optimization.minimizer来使用 Webpack 5及以上版本已内置,parallel 参数开启多线程压缩
https://github.com/facebook/create-react-app
Parcel
一款快速、零配置的Web应用打包工具。它旨在简化前端开发流程,通过提供自动化配置、快速冷启动编译、热模块替换(HMR)等功能
0. 零配置:Parcel 自动处理诸如模块打包、代码转换(如Babel)、样式处理(Sass、Less等)、图片优化、代码拆分等任务,无需复杂的配置文件。
0. 快速启动:Parcel 采用多进程编译模型,每个处理器都有自己的进程,这使得它在初次启动和后续编译时都非常迅速。
0. 热模块替换(HMR):自动启用HMR,允许在开发过程中实时查看代码变更,而无需刷新浏览器。
0. 全面的文件类型支持:Parcel 内置支持处理各种现代Web开发中的文件类型,包括JS、CSS、HTML、图片、SVG、字体等。
0. tree shaking:自动移除未使用的代码,帮助减小最终打包文件的大小。
0. 自动源码映射:为生产环境和开发环境生成源码映射,便于调试。
0. 环境变量:支持.env文件,方便管理不同环境下的配置。
0. 优化的生产构建:在生产环境中,Parcel会自动进行一系列优化,包括代码压缩、资源最小化等,以提高性能。
react-router-dom是React应用中最常用的路由库之一,它是react-router的一个子集
• <BrowserRouter>: 这是最常用的路由器组件,它使用HTML5的历史API(pushState, replaceState)来管理导航,提供了友好的URL体验且不需要 hashes (#)。
• <Route>: 定义了路径与组件之间的映射关系。当URL匹配到定义的路径时,对应的组件会被渲染。
• <Switch>: 包裹一系列<Route>组件,确保只有一个与当前URL匹配的<Route>会被渲染。
useHistory, useLocation, useParams: React Hooks
Midway
Node.js 的企业级框架
8.17
DllPlugin和HardSourceWebpackPlugin: DllPlugin可以将第三方库预先打包成单独的文件,减少构建时间。HardSourceWebpackPlugin可以缓存中间文件,加速后续构建过程
使用现代的压缩工具,如Brotli和Gzip,来对静态资源进行压缩
webpack和vite差异:
在开发环境中,Webpack 是先打包再启动开发服务器,而 Vite 则是直接启动,然后再按需编译依赖文件
Webpack 是基于 Node.js 构建的,而 Vite 则是基于 esbuild ,Go 语言编写的,Go 语言是纳秒级别的,而 Node.js 是毫秒级别的
热更新的处理
Webpack 重新编译 vite 需要让浏览器重新请求该模块即
Monorepo
项目代码管理方式,指单个仓库中管理多个项目
渐进式架构方案
ESLint
源代码字符串通过Parser解析成AST,之后ESLint就可以通过AST提供的信息与Rules对比,从而给出代码规范分析的结果,指出错误,并且还可以自动修复
Babel 的原理就是将 JavaScript 源代码转换为抽象语法树(AST),然后对 AST 进行转换
npm install
读取 package.json 文件,该文件列出了项目所需要的依赖。
根据 package.json 中的依赖信息以及 node_modules 目录状态,npm 会决定哪些模块需要下载和安装。
npm 会查看每个模块的可用版本,并选择符合 package.json 中指定版本范围的最新版本进行安装。
下载所需模块到本地的 node_modules 目录。
如果模块包含子模块(package.json 中 dependencies 或 devDependencies 中的模块),则递归执行上述步骤安装这些子模块
PostCss 做的是类似的事情:它可以编译尚未被浏览器广泛支持的先进的 CSS 语法,还可以自动为一些需要额外兼容的语法增加前缀。
webpack打包优化:
一、分析打包速度
在webpack构建过程中,实际上耗费时间大多数用在 loader 解析转换以及代码的压缩中;speed-measure-webpack-plugin 测量你的 webpack 构建期间各个阶段花费的时间
二、分析影响打包速度环节
搜索时间
——优化 loader 配置
使用 Loader 时可以通过 test 、 include 、 exclude 三个配置项来命中 Loader 要应用规则的文件
——优化 resolve.module 配置
resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块
——优化 resolve.alias 配置
resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径,减少耗时的递归解析操作
———优化 resolve.extensions 配置
在导入语句没带文件后缀时,webpack 会根据 resolve.extension 自动带上后缀后去尝试询问文件是否存在
• resolve.extensions 列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
• 频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。
• 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程
————优化 resolve.mainFields 配置
有一些第三方模块会针对不同环境提供几分代码。 例如分别提供采用 ES5 和 ES6 的2份代码,这2份代码的位置写在 package.json
webpack 会根据 mainFields 的配置去决定优先采用那份代码
webpack 会按照数组里的顺序去 package.json 文件里寻找,只会使用找到的第一个。
假如你想优先采用 ES6 的那份代码,可以这样配置:
mainFields: ['jsnext:main', 'browser', 'main']
—————优化 module.noParse 配置
module.noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。 原因是一些库,例如 jQuery 、ChartJS, 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义
详细配置
// 编译代码的基础配置
module.exports = {
// ...
module: {
// 项目中使用的 jquery 并没有采用模块化标准,webpack 忽略它
noParse: /jquery/,
rules: [
{
// 这里编译 js、jsx
// 注意:如果项目源码中没有 jsx 文件就不要写 /\.jsx?$/,提升正则表达式性能
test: /\.(js|jsx)$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
// 排除 node_modules 目录下的文件
// node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: /node_modules/,
},
]
},
resolve: {
// 设置模块导入规则,import/require时会直接在这些目录找文件
// 可以指明存放第三方模块的绝对路径,以减少寻找
modules: [
path.resolve(`${project}/client/components`),
path.resolve('h5_commonr/components'),
'node_modules'
],
// import导入时省略后缀
// 注意:尽可能的减少后缀尝试的可能性
extensions: ['.js', '.jsx', '.react.js', '.css', '.json'],
// import导入时别名,减少耗时的递归解析操作
alias: {
'@compontents': path.resolve(`${project}/compontents`),
}
},
};
解析时间(单线程逐文件处理)
—— 开启多线程打包
——1. thread-loader(webpack4 官方推荐 专门处理loader)
——2. HappyPack 多进程打包(Webpack 5 +不推荐 内置通过 thread-loader 和 worker-loader)
通常,你会为特定类型的加载器(如 Babel)设置一个 HappyPack 实例
// ... 其他配置 ...
module: {
rules: [
{
test: /\.js$/,
use: ['happypack/loader?id=babel'], // 使用 HappyPack 处理 .js 文件
exclude: /node_modules/,
},
// ... 其他规则 ...
],
},
plugins: [
new HappyPack({
id: 'babel', // 这个id需要与上面的use中的id匹配
loaders: [{
loader: 'babel-loader',
options: { // 传递给 babel-loader 的选项
presets: ['@babel/preset-env'],
},
}],
threadPool: HappyPack.ThreadPool({ size: 4 }), // 线程池大小,默认是 os.cpus().length
}),
// ... 其他插件 ...
],
当项目较小时,多线程打包反而会使打包速度变慢
压缩时间(分析处理AST)
webpack3 启动打包时加上 --optimize-minimize
ParallelUglifyPlugin多线程压缩(4+中被废弃 不支持 ES6 +)
webpack4 中 webpack.optimize.UglifyJsPlugin 已被废弃
terser-webpack-plugin(4+ 推荐)
二次打包时间
———合理利用缓存(初始慢)
ache-loader (放其他性能大loader之前)
HardSourceWebpackPlugin 或 babel-loader 的 cacheDirectory
DllPlugin
提升构建速度的插件,将一些不经常改变的代码(如第三方库)预先打包成一个或多个(DLL),从而在后续的构建过程中避免重新打包这些库
npm
安装慢 每个依赖的每个版本都会被安装一次,磁盘占用高
tnpm 镜像
pnpm
安装快 占磁盘不高 但是,推广程度不高 兼容性
8.20
样式篇
postcss-loader
PostCSS 相当于 CSS 的 Babel
postcss-cssnext 体验未来css特性
cssnano这样的插件进行CSS代码压缩、去除无用代码等优化
{
// 使用style-loader将CSS注入到DOM中(或MiniCssExtractPlugin.loader抽取到文件)
'style-loader',
// 使用css-loader处理CSS @import 和 url()
'css-loader',
// 使用resolve-url-loader修正CSS中的URL
{
loader: 'resolve-url-loader',
options: {
attempts: 1, // 尝试解析的次数,默认1次
},
},
{
loader: "postcss-loader",
options: {
plugins: () => ([
require("autoprefixer"), //自动加前缀
require('cssnano')({ preset: 'default' }), // CSS压缩
require('postcss-preset-env')({ browsers: 'last 2 versions' , '>1%', 'iOS 7'}), // 转换现代CSS语法
require("precss") // 使用类似sass标签
])
}
}
}
css-loader 默认处理 相对导入
参数 importLoaders :对所找到的导入文件执行 css-loader 之前需要执行多少个 loader 。您通过 @import 语句从 CSS 导入其他 CSS 文件,并希望通过特定的 loader 处理导入 很重要
resolve-url-loader
处理CSS文件中引用的相对URL路径
MiniCssExtractPlugin
生成单独的 CSS 包,只会作用于编译阶段,它不适用于热模块更换(HMR)
PurifyCSS uncss. 去除未使用样式
图像篇
url-loader
在使用 limit 选项 达到绝对限制后的情况下,url-loader默认将可能的附加选项传递给 file-loader,如果你想使用另一个 loader , fallback: "some-loader"
不要同时在图像上同时应用两个 loader!如果 url-loader limit 不够,请使用 include 字段进一步控制
加载 SVG
{
test: /\.svg$/,
use: "file-loader",
}
react-svg-loader svg-url-loader svg-sprite-loader
优化图像
压缩图像 image-webpack-loader、svgo-loader(专用于 SVG)或 imagemin-webpack-plugin 应用于最后数据 放use 列表中的最后一个
利用 srcset
resize-image-loader 和 responsevie-loader
使用图片占位符
image-trace-loader
lqip-loader
引用图像
babel-plugin-transform-react-jsx-img-import
css-loader
字体篇
url-loader 和 file-loader
{
// 除了 .woff?v=1.1.1 这样的格式以外,还要匹配 woff2.
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: "url-loader",
options: {
// 限制为 50k. 超过它就生成独立的文件
limit: 50000,
// 设置 mimetype
// 如果没有这一项,默认会根据文件扩展名来获取
mimetype: "application/font-woff",
// 输出字体到文件夹
name: "./fonts/[name].[ext]",
publicPath: "../", // 输出时基于这个目录
}
},
},
注意优先级
@font-face {
font-family: 'myfontfamily';
src: url('./fonts/myfontfile.woff2') format('woff2'), url('./fonts/myfontfile.woff') format('woff'),
url('./fonts/myfontfile.eot') format('embedded-opentype'), url('./fonts/myfontfile.ttf') format('truetype');
/* 还可以添加你认为合适的其他格式 */
}
基于 SVG 生成字体文件
webfonts-loader
使用字体图标
iconfont-webpack-plugin
脚本篇
Babel
babel-loader babel-webpack-plugin
• plugins:babel 中使用的插件,这些插件可以控制如何转换代码
presets:babel 可以使用哪些新的语法特性,
babel-plugin-react • babel-plugin-import
babel/plugin-transform-runtime
• @babel/preset-env:允许您为旧版浏览器支持某些语言特性。为此,您应该启用其 useBuiltIns 选项(设置 "useBuiltIns": true 或 "useBuiltIns": "usage")并安装 @babel/polyfill。您必须通过 import 或 entry(app: ["@babel/polyfill", PATHS.app])将其包含在项目中。@babel/preset-env 根据您选定的浏览器重写导入,并仅加载所需要的 polyfill。
• @babel/polyfill:会在全局范围内提供了像 Promise、Set 这样的对象,这会污染全局作用域,对于一些库的开发者来说这可能会造成影响,这时候可以使用 `@babel/plugin-transform-runtime 。它可以作为 Babel 插件启用,避免了全局变量污染的问题。
• babel-plugin-import:重写模块导入,以便您可以使用这样的形式(import { Button } from 'antd')来导入模块,而不必指出精准的路径
TypeScript
• ts-loader
• awesome-typescript-loader
环境变量
DefinePlugin webpack-conditional-loader
8.21
glob 强大的方式来匹配和选择文件
const glob = require('glob');
glob("**/*.js", function (err, files) {
console.log(files); // 打印所有.js文件的路径
});
样式篇
postcss-loader
PostCSS 相当于 CSS 的 Babel
postcss-cssnext 体验未来css特性
cssnano这样的插件进行CSS代码压缩、去除无用代码等优化
{
// 使用style-loader将CSS注入到DOM中(或MiniCssExtractPlugin.loader抽取到文件)
'style-loader',
// 使用css-loader处理CSS @import 和 url()
'css-loader',
// 使用resolve-url-loader修正CSS中的URL
{
loader: 'resolve-url-loader',
options: {
attempts: 1, // 尝试解析的次数,默认1次
},
},
{
loader: "postcss-loader",
options: {
plugins: () => ([
require("autoprefixer"), //自动加前缀
require('cssnano')({ preset: 'default' }), // CSS压缩
require('postcss-preset-env')({ browsers: 'last 2 versions' , '>1%', 'iOS 7'}), // 转换现代CSS语法
require("precss") // 使用类似sass标签
])
}
}
}
css-loader 默认处理 相对导入
参数 importLoaders :对所找到的导入文件执行 css-loader 之前需要执行多少个 loader 。您通过 @import 语句从 CSS 导入其他 CSS 文件,并希望通过特定的 loader 处理导入 很重要
resolve-url-loader
处理CSS文件中引用的相对URL路径
MiniCssExtractPlugin
生成单独的 CSS 包,只会作用于编译阶段,它不适用于热模块更换(HMR)
PurifyCSS uncss. 去除未使用样式
critters-webpack-plugin 自动提取 关键CSS,并内联到HTML中,同时将剩余的CSS分离到外部文件
isomorphic-style-loader (服务器端渲染) 提供一致的CSS加载体验,确保样式在首次渲染时就已应用,从而避免了无样式内容闪烁(FOUT, Flash of Unstyled Content)或样式闪现(FOUC, Flash of Unstyled Content)的问题
图像篇
url-loader
在使用 limit 选项 达到绝对限制后的情况下,url-loader默认将可能的附加选项传递给 file-loader,如果你想使用另一个 loader , fallback: "some-loader"
不要同时在图像上同时应用两个 loader!如果 url-loader limit 不够,请使用 include 字段进一步控制
加载 SVG
{
test: /\.svg$/,
use: "file-loader",
}
react-svg-loader SVG文件作为React组件导入并在React应用中使用
svg-url-loader
svg-sprite-loader
优化图像
压缩图像
image-webpack-loader 它在图像文件被其他加载器(如file-loader或url-loader)处理后,进一步执行图像优化。它利用了mozjpeg、optipng、pngquant、gifsicle等底层工具来压缩JPEG、PNG、GIF等格式的图片。
svgo-loader(压缩SVG)
imagemin-webpack-plugin 应用于最后数据 放use 列表中的最后一个
利用 srcset
resize-image-loader 和
responsevie-loader 它会根据配置自动生成不同尺寸的图片,并且可以生成相应的HTML srcset 和 sizes 属性,以支持响应式图像
使用图片占位符
image-trace-loader 将SVG或位图图像(如JPEG、PNG等)转换为SVG矢量路径,像素图像转换为可缩放的矢量图形,从而在任何分辨率下都能保持清晰度 适用图标之类
lqip-loader 生成并立即显示图像的低质量预览图,随后再加载高质量图像,通常会生成一个Base64编码的小图作为占位符
引用图像
babel-plugin-transform-react-jsx-img-import
css-loader
字体篇
url-loader 和 file-loader
{
// 除了 .woff?v=1.1.1 这样的格式以外,还要匹配 woff2.
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: "url-loader",
options: {
// 限制为 50k. 超过它就生成独立的文件
limit: 50000,
// 设置 mimetype
// 如果没有这一项,默认会根据文件扩展名来获取
mimetype: "application/font-woff",
// 输出字体到文件夹
name: "./fonts/[name].[ext]",
publicPath: "../", // 输出时基于这个目录
}
},
},
注意优先级
@font-face {
font-family: 'myfontfamily';
src: url('./fonts/myfontfile.woff2') format('woff2'), url('./fonts/myfontfile.woff') format('woff'),
url('./fonts/myfontfile.eot') format('embedded-opentype'), url('./fonts/myfontfile.ttf') format('truetype');
/* 还可以添加你认为合适的其他格式 */
}
基于 SVG 生成字体文件
webfonts-loader
细粒度的字体文件处理能力,包括Base64嵌入和CSS自动生成
用于处理Web字体文件,包括Icon Fonts。它不仅能够加载字体文件,还可以自动生成CSS样式,甚至可以将图标编码为Base64字符串直接嵌入到CSS中,进一步减少HTTP请求。
使用字体图标
iconfont-webpack-plugin
处理Icon Font(图标字体)。它可以帮助你将图标字体文件(如.ttf、.eot、.woff、.woff2等)自动注入到你的项目中,确保图标字体资源在打包时被正确处理和优化。使用这个插件可以简化Icon Font的集成过程,避免手动编写链接和样式引用,特别是在使用自定义或第三方Icon Fonts时非常有用
plugins: [
new IconFontPlugin({
// 配置项,如字体文件路径、输出目录等
fontPath: './path/to/iconfont', // 图标字体文件目录
fontName: 'my-iconfont', // 字体名称
formats: ['woff2', 'woff', 'ttf', 'eot', 'svg'], // 支持的字体格式
}),
]
脚本篇
Babel
babel-loader babel-webpack-plugin
• plugins:babel 中使用的插件,这些插件可以控制如何转换代码
presets:babel 可以使用哪些新的语法特性,
babel-plugin-react •
babel-plugin-import. 主要用于按需加载模块,特别适用于那些提供了按需加载机制的库,如Ant Design的按需加载CSS
babel/plugin-transform-runtime 这个插件用于避免重复包含Babel的帮助函数
• @babel/preset-env:允许您为旧版浏览器支持某些语言特性。为此,您应该启用其 useBuiltIns 选项(设置 "useBuiltIns": true 或 "useBuiltIns": "usage")并安装 @babel/polyfill。您必须通过 import 或 entry(app: ["@babel/polyfill", PATHS.app])将其包含在项目中。@babel/preset-env 根据您选定的浏览器重写导入,并仅加载所需要的 polyfill。
• @babel/polyfill:会在全局范围内提供了像 Promise、Set 这样的对象,这会污染全局作用域,对于一些库的开发者来说这可能会造成影响,这时候可以使用 `@babel/plugin-transform-runtime 。它可以作为 Babel 插件启用,避免了全局变量污染的问题。
• babel-plugin-import:重写模块导入,以便您可以使用这样的形式(import { Button } from 'antd')来导入模块,而不必指出精准的路径
TypeScript
用于加载TypeScript文件的加载器。它们允许你在Webpack构建流程中直接使用TypeScript代码,而无需先将其编译成JavaScript
• ts-loader 官方支持 包括transpileOnly模式,该模式可以显著加快编译速度,但牺牲了一部分类型检查功能
• awesome-typescript-loader
环境变量
DefinePlugin webpack-conditional-loader
它允许你在编译时创建全局常量。设置环境变量、API端点、版本号等非常有用,因为它允许你根据不同的环境(开发、生产等)条件编译不同的代码块,而不需要在代码中硬编码这些值
lugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'), // 设置环境变量
'MY_APP_VERSION': JSON.stringify('1.2.3'), // 设置应用版本号
// 更多全局常量定义...
}),
],
if (process.env.NODE_ENV === 'production') {
console.log('Production environment detected.');
}
console.log('App version:', MY_APP_VERSION);
现象:项目施工列表点击数据,有时候地图上路线渲染正常,有时候闪一下消失,有时候直接不显示
排查过程:首先排查数据问题,每次点击路线图层拿到的数据是不是正常。结果数据没问题,后来怀疑渲染问题,检查React组件是否没有重新渲染,最后发现图层组件渲染问题
原因: 组件內的key值有重复,不唯一,导致react 复用旧节点没替换,页面路线数据诡异现象
bug场景: 项目列表每次导出的数据都不是查询之后的数据
原先代码实现: 查询条件对象使用useMemo记忆,依赖项是查询的每个字段。导出执行的函数是使用useCallback的记忆函数,空依赖项。函数里引用了useMemo返回的查询条件,结果出现该问题。
原因:
• 初始渲染时,useMemo根据其依赖计算一个值,useCallback创建一个引用该useMemo返回值的回调函数。
• 当useMemo的依赖改变时,它会计算新的值,但由于useCallback的依赖数组是空的,它不会创建新的回调函数,仍然保持对初次渲染时useMemo返回值的引用。
• 这就解释了为什么在后续调用useCallback的回调时,即使useMemo内部的值已经因为依赖变化而更新,回调函数内部使用的始终是最初计算的那个useMemo返回值。因为回调函数保持不变,它内部封闭的环境(包括对useMemo返回值的引用)也就保持不变。
解决办法:在useCallback的依赖数组中包含对useMemo返回值的引用。这样,当useMemo的值因为依赖变化而更新时,useCallback也会得到通知并创建一个新的回调函数,确保每次调用时都能获取到最新的useMemo结果
扩展:useCallback回调函数中除了memo返回值,其他hook如:useCallback 函数也要添加到依赖项中去
8.26
sideEffects 布尔值或字符串数组,用于声明模块是否有副作用
• 布尔值:如果设置为false,表明该模块没有副作用,打包工具可以更激进地进行Tree-shaking,安全地移除未被直接引用的代码。
• 数组:如果是一个字符串数组,列出模块中具有副作用的文件路径(相对于模块根目录)。这样,即使整个模块未被直接引用,这些特定的文件也不会被优化掉
Webpack 4及以前:
NamedChunksPlugin
目的是给输出的代码块(chunk)赋予更具可读性的名字
在开发环境中启用,以提高开发时的可读性
NamedModulesPlugin
将模块的ID替换为模块的实际路径名。这在开发模式下堆栈跟踪特别有用
HtmlWebpackPlugin 自动生成HTML文件,并自动注入Webpack打包生成的所有JS资源
WebpackShellPlugin 允许你在Webpack构建前后执行shell命令,比如重启服务器、清理目录等,常用于构建自动化
WebPackManifestPlugin 生成一个manifest.json文件,列出所有产出的文件及其哈希值
mainFields
0. exports (Node.js 12.0.0+ 新增,用于更细粒度的导出控制)
0. module (用于ES模块)
0. main (通用的入口点,通常用于CommonJS模块)
PurifyCSSPlugin 废弃 CSS中移除未使用的样式
替代方案:PurgeCSS 和 purgecss-webpack-plugin
uglifyjs-webpack-plugin 压缩JavaScript ,parallel: true来开启并行处理,利用Node.js的多线程能力加速压缩过程,尤其是在多核处理器的机器
terser-webpack-plugin是另一个常用的替代品 Webpack 5+
Polyfill.io 兼容性服务
pngcrush, optipng, 和 tinypng pngquant都是用于优化PNG图像的工具
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// 配置pngquant
pngquant: {
quality: [0.65, 0.9],
speed: 4
},
gifsicle: {
interlaced: false,
},
optipng: {
enabled: false,
},
webp: {
quality: 75
}
},
},
],
clean-css-loader 压缩和优化CSS代码,放在CSS的加载器链的末尾
test: /\.css$/,
use: [
// 如果你打算将CSS提取到文件中,这里应该使用MiniCssExtractPlugin.loader或style-loader
// 'style-loader',
MiniCssExtractPlugin.loader,
'css-loader', // 解析CSS
'postcss-loader', // 可选,用于额外的CSS处理,如自动添加前缀
{
loader: 'clean-css-loader', // 在这里使用clean-css-loader进行CSS压缩
options: {
// 这里可以配置clean-css的选项
level: 2, // 压缩级别,0-2,数字越大压缩程度越高
},
},
],
OptimizeCssAssetsPlugin 压缩CSS
css-minimizer-webpack-plugin Webpack 5+,已被官方推荐的 (最小化CSS文件)
mini-css-extract-plugin CSS提取成单独文件
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
// ...其他规则
],
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].css",
chunkFilename: "[id].css",
}),
html-loader 解析HTML文件资源引用 引用本地文件,转换为require/import语句
posthtml-minifier 压缩和优化HTML代码
test: /\.html$/,
use: [
{
loader: 'html-loader', // 首先使用html-loader处理HTML文件
options: {
// html-loader 的配置选项
},
},
{
loader: 'posthtml-loader', // 然后使用posthtml-loader,这里可以配置使用posthtml-minifier
options: {
plugins: [
MiniHtmlWebpackPlugin({
removeComments: true, // 移除HTML注释
collapseWhitespace: true, // 压缩空格和换行
minifyCSS: true, // 同时压缩内联CSS
minifyJS: true, // 同时压缩内联JS
}),
],
},
},
],
webpack-closure-compiler(出现早). closure-webpack-plugin 优化js
Webpack SplitChunksPlugin 代码拆分
optimization: {
splitChunks: {
chunks: 'all', // 对所有类型的 chunk(同步和异步)应用代码分割
minSize: 10000, // 最小尺寸,达到此大小的模块才会被分割
maxSize: 0, // 可选,最大的 chunk 大小,超过此大小的 chunk 会被进一步分割
minChunks: 1, // 最小共享次数,被多少个 chunk 共享的模块才会被分割
maxAsyncRequests: 30, // 最大异步请求数量
maxInitialRequests: 30, // 最大初始化请求数量
automaticNameDelimiter: '~', // 名称分隔符
name: true, // 基于 chunks 和 cacheGroupKey 自动命名
cacheGroups: { // 配置缓存组,可以定义更细粒度的分割策略
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
filename: 'vendors.js',
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
AggressiveSplittingPlugin 代码分割的
new AggressiveSplittingPlugin({
minSize: 10000, // 指定最小模块大小,单位字节,只有大于这个大小的模块才会被分割
maxSize: 50000, // 可选,最大模块大小,超过这个大小的chunk会被进一步分割
}),
AggressiveMergingPlugin 合并较小的 chunks 来减少总的HTTP请求数量
DllReferencePlugin 依赖打包成dll
speed-measure-webpack-plugin loader和plugin的执行时间
8.27
空数组 的some 返回false
空数组every 返回true
知识点归类
Webpack性能优化 总结:
输出分析(前提)
webpack-bundle-analyzer
打包(编译):
文件查找
resolve.modules
resolve.mainFields
resolve.alias
resolve.extensions
插件
DllPlugin 和DllReferencePlugin 生成dll文件,缓存第三方依赖
HappyPack 多进程loader
ParallelUglifyPlugin 多进程js压缩
HardSourceWebpackPlugin 中间缓存步骤
TerserWebpackPlugin 缓存
babel-loader?cacheDirectory=true 缓存
thread-loader 多进程
打包输出:
TreeShaking
样式
css-loader?minimize + ExtractTextPlugin cssnano 压缩并提取到单独文件
脚本
ParallelUglifyPlugin 多进程js压缩
uglify-webpack-plugin 压缩es6
DLLPlugin DLLReferencePlugin
CommonChunksPlugin => SplitChunksPlugin(webpack4+)
UglifyJsPlugin
externals防止打包 CDN 引入
图片
9.10
空数组 的some 返回false
空数组every 返回true
知识点归类
Webpack性能优化 总结:
输出分析(前提)
webpack-bundle-analyzer
打包(编译):
文件查找
resolve.modules
resolve.mainFields
resolve.alias
resolve.extensions
插件
DllPlugin 和DllReferencePlugin 生成dll文件,缓存第三方依赖
HappyPack 多进程loader
ParallelUglifyPlugin 多进程js压缩
HardSourceWebpackPlugin 中间缓存步骤
TerserWebpackPlugin 缓存
babel-loader?cacheDirectory=true 缓存
thread-loader 多进程
打包输出:
TreeShaking
样式
css-loader?minimize + ExtractTextPlugin cssnano 压缩并提取到单独文件
clean-css-loader
mini-css-extract-plugin
• style-loader:将模块的导出作为样式添加到 DOM 中
• css-loader:解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
• less-loader:加载和转译 LESS 文件
• sass-loader:加载和转译 SASS/SCSS 文件
• postcss-loader:使用 PostCSS 加载和转译 CSS/SSS 文件
脚本
ParallelUglifyPlugin 多进程js压缩
uglify-webpack-plugin 压缩es6
DLLPlugin DLLReferencePlugin
CommonChunksPlugin => SplitChunksPlugin(webpack4+) 分割打包
UglifyJsPlugin(官方不推荐) 压缩
externals防止打包 CDN 引入
TerserWebpackPlugin (推荐)压缩
ts-loader
图片
img-loader
imagemin-webpack
imagemin-webpack-loader
imagemin-webpack-plugin
文档
posthtml-minifier
html-loader
HtmlWebpackPlugin
url-loader:像 file loader 一样工作,如果文件小于限制,返回 data URL
file-loader:将文件发送到输出文件夹,并返回(相对)URL
框架
vue-loader
工作流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
0. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
0. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
0. 确定入口:根据配置中的 entry 找出所有的入口文件
0. 编译模块:从入口文件出发,调用所有配置的 loader 对模块进行翻译,再找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理
0. 完成模块编译:在经过第 4 步使用 loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的 依赖关系图
0. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
0. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
0. 初始化:启动构建,读取与合并配置参数,加载 plugin,实例化 Compiler。
0. 编译:从 entry 出发,针对每个 module 串行调用对应的 loader 去翻译文件内容,再找到该 module 依赖的 module,递归地进行编译处理。
0. 输出:对编译后的 module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统
9.12
Umi
Remix
Next.js
ice
Modern.js React字节开发的框架 支持ssr 微前端
可能会造成性能问题、内存泄漏、CPU/GPU飙升的原因如下:
0. 频繁的DOM操作:
• 在renderTraffictLight函数中,每一帧都会对DOM进行查询和修改操作,这在高帧率下会非常消耗性能。
• trafficLightDom.forEach中对每个红绿灯进行操作,如果红绿灯数量很多,这将是一个性能瓶颈。
0. 大量的WebGL渲染:
• renderOneFramePointsPosition函数在每一帧都可能会重建整个顶点数组,这在数据量大时,会频繁触发GPU渲染,可能导致性能问题。
0. 内存泄漏:
• trafficLightMarkers数组如果不及时清理,可能会随着时间积累越来越多的标记,造成内存泄漏。
• 如果fetchDataInterval定时器在组件卸载时没有被清除,它将继续在后台执行,即使组件不再使用。
0. 并发和异步处理:
• 使用多个异步请求(如fetchData和fetchCacheData)而没有适当的控制或取消机制,可能会导致大量的并发操作,消耗大量内存和CPU资源。
0. 红绿灯渲染问题:
• 如果红绿灯有时渲染不出来,可能是由于数据更新和DOM渲染不同步,或者是因为某些红绿灯数据不完整。
优化建议:
0. 优化DOM操作:
• 减少在renderTraffictLight中的DOM查询操作,可以考虑使用类名或ID来直接引用DOM元素。
• 对于红绿灯状态的更新,可以考虑使用CSS类来控制颜色变化,而不是直接操作style属性。
0. 优化WebGL渲染:
• 避免在每一帧都重建顶点数组。可以预先计算好整个轨迹的顶点,然后在渲染时只更新需要显示的部分。
• 使用BufferGeometry的setDrawRange方法来只渲染可视范围内的点。
0. 避免内存泄漏:
• 确保在组件卸载时清除所有的定时器、事件监听器和WebGL资源。
• 定期清理不再需要的对象和数据,例如trafficLightMarkers。
0. 控制并发和异步处理:
• 使用Promise的all或者race方法来控制并发请求,避免同时处理过多的异步操作。
• 提供取消机制,如在组件卸载时取消未完成的请求。
0. 修复红绿灯渲染问题:
• 确保红绿灯数据和DOM元素的同步,可以使用状态管理库来控制状态和渲染。
• 检查红绿灯数据是否完整,并在数据缺失时提供默认行为。
针对红绿灯有时渲染不出来的问题,可以进一步检查以下几点:
• 确认红绿灯数据是否正确,并且包含了必要的position信息。
• 检查是否所有的红绿灯标记都被正确添加到了地图上。
• 查看控制红绿灯显示的逻辑,确认是否由于某些条件未满足导致红绿灯没有显示。
在优化过程中,监控工具可以帮助识别性能瓶颈和内存泄漏的具体位置。例如,使用Chrome的开发者工具来分析CPU和内存的使用情况,以及查看可能存在的内存泄漏
CPU占满100%的原因:
0. 高频的渲染和计算:
• setInterval在putTrackPointsOnMap方法中设置了一个高频的定时器(1000/framesPerSecond),这会导致频繁的执行renderOneFramePointsPosition和renderTraffictLight方法,这两个方法涉及到图形渲染和计算,可能会占用大量CPU资源。
0. Web Workers的使用:
• 使用了Web Worker在calcData方法中进行数据处理。如果数据处理逻辑复杂或者数据量很大,这可能会占用大量CPU资源。
0. 大量的DOM操作:
• 在renderTrafficLightMarkers方法中,频繁地创建和销毁AMap.Marker实例以及操作DOM元素,这可能会引起重绘和回流,从而增加CPU的负担。
0. 图形渲染:
• 使用Three.js进行3D图形渲染,尤其是在render方法中,如果场景复杂或者渲染设置不当,都可能导致CPU或GPU负载过高。
0. 大量的数据请求和处理:
• 在fetchData方法中,如果频繁地请求数据,并且数据量较大,处理这些数据可能会占用大量CPU资源。
页面DOM节点持续增多的原因:
0. 重复添加红绿灯标记:
• 在renderTrafficLightMarkers方法中,每次调用都会创建新的AMap.Marker实例并添加到地图上,但如果之前的标记没有被正确地移除,就会导致DOM节点持续增多。
0. 没有正确清理DOM:
• 在clearCurrentTrafficLightMarkers方法中,虽然尝试移除DOM节点,但如果某些节点没有被成功移除(例如,由于引用仍然存在),它们将留在DOM中。
0. 类名操作:
• 在renderTraffictLight方法中,对.traffic-light__marker类的元素进行操作,如果元素没有被正确地隐藏或移除,它们会继续存在于DOM中。
解决这些问题的一些方法可能包括:
• 优化定时器使用,避免不必要的频繁调用。
• 优化数据处理逻辑,减少在Web Worker中的计算量。
• 在不需要时,确保正确地清理和销毁Three.js的渲染资源。
• 确保每次创建新的地图标记前,旧的标记都被正确地移除。
• 优化DOM操作,避免不必要的DOM节点创建和删除。
优化搜索性能通常涉及多个方面,包括算法选择、数据结构优化、索引使用、缓存机制、并行处理等。以下是一些通用的优化策略:
1. 数据结构和算法优化
• 使用高效的数据结构:例如,使用哈希表(如JavaScript中的对象或Map)来快速查找数据。
• 选择合适的搜索算法:对于不同的数据集和搜索需求,选择合适的算法(如二分搜索、深度优先搜索、广度优先搜索等)。
• 剪枝:在搜索过程中,如果能够确定某个分支不可能产生结果,则可以提前终止该分支的搜索。
2. 索引
• 建立索引:对于经常搜索的数据,建立索引可以大幅提高搜索效率。例如,数据库中的索引、全文搜索引擎的倒排索引等。
• 多级索引:对于复杂的数据结构,可以使用多级索引来快速定位到数据所在的区域。
3. 缓存
• 缓存搜索结果:如果相同的搜索请求会被多次执行,可以将结果缓存起来,避免重复计算。
• 局部性原理:利用数据访问的局部性原理,缓存最近或最频繁访问的数据。
4. 并行处理
• 多线程/多进程:将搜索任务分解成多个子任务,并行执行以利用多核处理器的优势。
• 分布式搜索:对于非常大的数据集,可以使用分布式系统来进行并行搜索。
5. 优化查询
• 减少查询范围:通过过滤不必要的数据来减少搜索范围。
• 预处理查询:在可能的情况下,对查询进行预处理,比如正则表达式编译。
6. 硬件优化
• 使用更快的存储:使用SSD代替HDD,以提高数据读取速度。
• 增加内存:增加可用内存可以减少磁盘I/O操作,提高搜索速度。
7. 代码优化
• 避免不必要的计算:移除代码中不必要的计算,减少循环和递归的深度。
• 优化循环和递归:优化循环结构,减少循环次数,避免不必要的递归调用。
8. 监控和分析
• 性能监控:使用性能监控工具来识别瓶颈。
• 分析查询模式:通过分析用户的查询模式来优化搜索策略
处理大量节点的情况通常需要考虑数据结构和算法的选择、内存管理、并发处理以及系统架构等多个方面。以下是一些处理大量节点的策略:
1. 数据结构选择
• 分而治之:使用树状结构(如B树、B+树、红黑树等)来管理节点,这样可以将大量的节点分布在不同的层级和子树中,提高搜索和插入的效率。
• 哈希表:对于不需要顺序遍历的场景,使用哈希表可以快速访问节点。
• 图数据结构:对于节点之间有复杂关系的场景,使用图数据结构,并通过适当的图算法来处理。
2. 算法优化
• 避免全量遍历:对于需要遍历的场景,尽量使用迭代而非递归,以避免栈溢出。
• 优化搜索算法:使用更适合大量节点的搜索算法,如A*搜索、Dijkstra算法等。
3. 内存管理
• 内存池:使用内存池来管理节点的内存分配,减少内存碎片和分配开销。
• 对象复用:在可能的情况下,复用已存在的节点对象,减少内存分配。
4. 并发处理
• 多线程/多进程:利用多线程或多进程来并行处理节点,特别是在CPU密集型的操作中。
• 分布式系统:对于非常大的节点集合,可以使用分布式系统来分散负载。
5. 系统架构
• 分片:将节点分布在不同的分片中,每个分片独立处理一部分节点,降低单个节点的负载。
• 负载均衡:使用负载均衡技术来分配节点处理任务,确保系统资源得到有效利用。
6. 索引和缓存
• 建立索引:为节点建立索引,以加速查找和访问速度。
• 缓存热点数据:缓存频繁访问的节点数据,减少磁盘I/O操作。
7. 数据存储
• 外部存储:对于无法全部加载到内存中的节点,使用外部存储(如数据库、文件系统等)。
• 数据压缩:对存储的数据进行压缩,以减少存储空间的需求。
8. 性能监控和调优
• 性能监控:实时监控系统的性能,及时发现瓶颈。
• 调优:根据监控结果对系统进行调优,如调整缓存大小、优化数据库查询等
在处理大量节点的搜索问题时,优化搜索算法的性能是非常重要的。以下是一些常用的搜索优化技巧:
1. 剪枝(Pruning)
• 预剪枝:在搜索过程中,如果发现当前路径不可能产生比当前最优解更好的解,则可以提前终止该路径的搜索。
• 后剪枝:在搜索完成后,如果发现某个节点不会影响最终解,则可以将其删除。
2. 启发式搜索(Heuristic Search)
• A*搜索算法:使用启发式函数估计从当前节点到目标节点的成本,优先搜索更有可能找到最优解的路径。
• IDA*搜索算法:迭代加深A*算法,结合深度优先搜索和启发式搜索的优点。
3. 空间优化
• 迭代加深搜索(IDDFS):结合深度优先搜索和广度优先搜索的优点,逐步增加搜索深度。
• 跳跃表(Skip List):在搜索时跳过一些节点,加速搜索过程。
4. 数据结构优化
• 优先队列(Priority Queue):使用优先队列来管理待搜索的节点,保证总是先搜索最有希望的节点。
• 哈希表(Hash Table):用于快速查找和更新节点状态。
5. 动态规划(Dynamic Programming)
• 记忆化搜索(Memoization):存储已计算的结果,避免重复计算。
• 状态表:使用表格来存储中间状态,便于快速查找。
6. 并行搜索
• 多线程/多进程搜索:将搜索任务分解为多个子任务,并行执行。
• 分布式搜索:在多个机器上分布式执行搜索任务。
7. 局部搜索优化
• 爬山算法(Hill Climbing):总是选择当前最优的移动。
• 模拟退火(Simulated Annealing):允许在一定概率下接受非最优解,以跳出局部最优。
• 遗传算法(Genetic Algorithms):使用生物进化原理来搜索解空间。
8. 搜索策略优化
• 双向搜索:从目标节点和起始节点同时进行搜索,直到两者相遇。
• 重启动策略:在搜索陷入局部最优时重新开始搜索。
9. 其他技巧
• 路径压缩:在搜索过程中压缩已访问路径,减少搜索空间。
• 循环检测:在搜索过程中检测并避免进入循环。
• 位运算优化:在处理状态时使用位运算来提高效率。
实际应用中的注意事项
• 理解问题特性:针对具体问题选择合适的搜索算法和优化技巧。
• 测试和调整:通过实验测试不同优化技巧的效果,并根据结果调整策略。
• 资源监控:监控搜索过程中的资源使用情况,如内存和CPU使用,避免资源耗尽。
搜索缓存的设计是提升搜索效率的关键部分,它能够减少对原始数据的重复处理,加快搜索响应时间。以下是一些设计高效搜索缓存的策略:
1. 缓存粒度
• 细粒度缓存:缓存单个查询结果,适用于查询结果变化不频繁且查询量大。
• 粗粒度缓存:缓存整个数据集或数据集的一部分,适用于数据更新不频繁的场景。
2. 缓存失效策略
• 定时失效:设置缓存项在一定时间后自动失效。
• 主动更新:当数据源发生变化时,主动更新或清除相关缓存项。
• 懒加载:在缓存项被访问时检查其有效性,如果无效则重新加载。
3. 缓存存储
• 内存缓存:使用内存作为缓存介质,如Redis或Memcached,以获得最快的访问速度。
• 磁盘缓存:对于数据量大且不经常访问的数据,可以使用磁盘缓存。
4. 缓存结构
• 哈希表:用于快速查找和访问缓存项。
• 树结构:如B树、红黑树,适用于需要范围查询的场景。
5. 缓存内容
• 结果缓存:直接缓存查询的最终结果。
• 中间结果缓存:缓存搜索过程中产生的中间结果,减少重复计算。
6. 缓存命中率优化
• 缓存热点数据:优先缓存访问频率高的数据。
• 缓存预加载:预测即将被访问的数据并提前加载到缓存中。
7. 缓存并发控制
• 读写锁:控制缓存项的并发读写,保证数据的一致性。
• 原子操作:使用原子操作来更新缓存,避免并发问题。
8. 缓存大小管理
• 固定大小:设置缓存的最大容量,当达到容量限制时,使用LRU(最近最少使用)、LFU(最不经常使用)等算法淘汰旧数据。
• 动态调整:根据系统负载和资源使用情况动态调整缓存大小。
9. 缓存安全性
• 数据加密:对敏感数据进行加密存储。
• 访问控制:确保只有授权的用户或服务可以访问缓存。
10. 监控与统计
• 性能监控:监控缓存命中率、响应时间等关键指标。
• 日志记录:记录缓存操作日志,便于问题追踪和分析。
实现高效搜索缓存的步骤:
0. 需求分析:了解搜索查询的特性和访问模式。
0. 缓存策略设计:根据需求选择合适的缓存粒度、失效策略、存储方式和结构。
0. 缓存实现:编写代码实现缓存逻辑,并进行单元测试。
0. 性能测试:通过压力测试来评估缓存性能,并根据测试结果进行优化。
0. 持续优化:根据实际运行情况进行调整,提高缓存效率。
arguments.callee是指当前正在执行的函数 匿名函数的递归调用
DOMContentLoaded事件必须等待其所属script之前的样式表加载解析完成后才会触发
循环中多次在刚给 DOM 更新样式位置后,立即通过 offsetTop 获取 DOM 位置。这样的操作会强制启动重排,因为浏览器并不清楚上一个循环内 DOM 有没有改变位置,必须立即重新布局才能计算 DOM 位置
preload本质:preload 是声明式的 fetch,可以改变浏览器加载资源的优先级,强制浏览器请求资源,同时不阻塞文档 onload 事件,也因此可以将 load 事件与脚本解析过程解耦
prefetch本质:让浏览器空闲的时候加载下一页可能需要的资源,同样的load和解析解耦
dns-fetch,让浏览器提前做dns预解析,当静态资源和html不在同一个域的时候,特别好用
async,defer是script的属性,其让html解析与下载并行,但是async下载完之后立即执行,而defer是html解析完之后再执行,一般按照顺序执行,但据说不是百分百靠谱
性能角度考虑,尽量不要把读操作和写操作,放在一个语句里面。DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作
先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
第六条,position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
第七条,只在必要的时候,才将元素的display属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility : hidden的元素只对重绘有影响,不影响重排。
渐进加载
渐进加载是指先加载低质量甚至模糊的图片,然后随着页面继续加载,使用LQIP(低质量图片占位符)技术替换为高质量的完整版本。
这种技术确实提高了首次进行有意义绘制的时间
浏览器每次处理 HTML 标记时,都会完成上述所有步骤:将字节转换为字符,确定令牌,将令牌转换为节点,然后构建 DOM 树。
对于较大的页面,此过程可能需要更长的时间。在制作流畅的动画时,如果浏览器必须处理大量 HTML,这很容易成为瓶颈。
DOM 树会捕获文档标记的属性和关系,但不会告知我们元素在渲染时的外观。这就是 CSSOM
CSS 字节会依次转换为字符、令牌、节点,最后链接到一个称为“CSS 对象模型”(CSSOM) 的树形结构
阴影的计算和渲染“成本高昂”
CSS 是一种阻塞渲染的资源
CRP 主要包括以下几个阶段:
0. HTML 解析:
• 浏览器开始读取 HTML 文档并构建 DOM 树(Document Object Model)。
• HTML 的每个标签都会生成一个对应的 DOM 节点。
0. CSS 解析:
• 浏览器解析 CSS 文件和内联样式,生成 CSSOM 树(CSS Object Model)。
• CSSOM 树结合 DOM 树,产生一个渲染树,每个渲染树节点对应一个要显示的页面元素。
0. 布局(Layout):
• 浏览器根据渲染树为每个节点计算位置和尺寸,这一步也称为回流(reflow)。
0. 绘制(Paint):
• 将渲染树节点转换为屏幕上的像素,这个过程称为绘制。
0. 合成(Composition):
• 将不同的层(Layers,例如固定定位元素、动画元素等)合成一个最终的页面。
影响 CRP 的因素
0. 阻塞渲染的 CSS 和 JavaScript:
• CSS 文件是渲染阻塞的,因为浏览器必须先构建 CSSOM 才能正确渲染页面。
• JavaScript 文件也可能阻塞渲染,因为它们可能会修改 DOM 和 CSSOM,从而影响页面内容和样式。
0. 资源加载顺序:
• 资源的加载顺序和解析顺序会直接影响页面的显示时间。关键资源应尽量优先加载。
优化关键渲染路径
0. 最小化 CSS 阻塞:
• 内联关键 CSS:将关键样式内联到 HTML 中,减少外部 CSS 文件的请求时间。
• 延迟/异步加载非关键 CSS:使用媒体查询或 loadCSS 等工具延迟加载非关键 CSS。
0. 最小化 JavaScript 阻塞:
• 使用 async 或 defer 属性:对于外部 JavaScript 文件,使用 async 或 defer 属性异步加载脚本。
• 拆分脚本:将必要的脚本部分放在 <head> 中,延迟或异步加载非关键的脚本。
0. 优化资源加载:
• 压缩和合并资源:通过压缩和合并 CSS 和 JavaScript 文件减少 HTTP 请求和文件大小。
• 使用 HTTP/2:HTTP/2 通过多路复用请求提高资源加载速度。
• 缓存资源:利用浏览器缓存和服务器缓存策略减少重复加载时间。
0. 优化图像:
• 延迟加载图像(Lazy Loading):通过 Lazy Loading 技术,图片仅在需要显示时加载。
• 使用合适的图像格式:选择合适的图像格式(如 WebP)和压缩图像以减少文件大小。
最小组件集,并将它们批量处理为单个网络请求
一个组件到 JS 模块的映射,以告知客户端要加载哪些模块。通过将组件标记为延迟加载,JS 模块将稍后加载
静态import和动态import()都很有用。它们各自都有非常独特的用例。使用 staticimport来获取初始绘制依赖项,尤其是首屏内容。在其他情况下,请考虑使用 dynamic 按需加载依赖项import()。
https://github.com/GoogleChrome/web-vitals
优化 JavaScript 和资源
• 消除阻塞渲染的资源。
• 拆分代码并使用动态import()。
• 将所有内容分成单独的部分,并延迟加载折叠下方的 HTML 文件。
• 按需执行并加载 JavaScript。
• <script>通过在标签上使用异步属性并与重要来源建立早期连接(资源提示,如dns-prefetch、preconnect和preload),识别缓慢的 JavaScript 资源并优化加载过程。
• 删除未使用的代码,并最小化和压缩代码。
• 使用 CDN。
• 使用 Service Worker和Workbox控制缓存。
优化图像
• 延迟加载首屏以下的图片。
• 使用 CDN 优化图像,提供适当大小的图像,压缩图像,并采用适合工作的图像格式(WebP、SVG、Web Fonts)。
优化 CLS
• 使用CSSaspect-ratio在图像加载时为图像保留所需的空间。
• min-height在元素延迟加载时使用 CSS来最小化布局偏移。
navigator.connection.effectiveType === '2g
scroll但事件侦听器本身会安排大量必须在主线程上运行的工作。这项工作导致主线程争用,从而增加了交互延迟,导致搜索页面上的 INP 不佳。
• scroll事件处理程序已去抖动,以减少事件回调在给定时间段内触发的次数。通过降低事件scroll回调运行的频率,主线程能够更快地响应搜索页面上的用户交互。
• 通过使用回调对最终的渲染工作进行优先排序requestAnimationFrame。requestAnimationFrame告诉浏览器回调中的工作必须在下一帧之前完成。
• 为了通过更早地触发延迟加载来改善用户体验和视觉性能,在搜索结果页面的倒数第二张卡片上获取了新一批结果。
• 延迟加载期间,每次网络调用获取的结果更少。通过将获取的结果从 30 个减少到 10 个,可以观察到 INP 范围从 870 到 900 减少到 350 到 370。
<input type="text"
inputmode="numeric"
autocomplete="one-time-code"
pattern="\d{6}"
required>
content-visibility,浏览器可以延迟对不可见内容的渲染
使用 content-visibility 延迟渲染屏幕外的元素
WebCodecs. 音视频编解码器。视频绘制在canvas
主要功能
• 视频编解码:编码和解码视频数据流。
• 音频编解码:编码和解码音频数据流。
• 低延迟:适用于实时应用,比如视频会议和游戏流媒体。
• 高性能:通过直接访问编解码器来提高性能,减少开销。
https://sketch.pixiv.net/
Asm.js 是 WebAssembly 的前身,可以认为是一种 WebAssembly 的“轻量级”实现。虽然 WebAssembly 现在通常更受欢迎,但 Asm.js 仍然是一个重要的技术,因为它不需要特定的浏览器支持(所有支持 JavaScript 引擎的浏览器均支持)。
重大更改(例如增加“html”元素的字体大小)将导致缓存失效、依赖和整个树的重新绘制
当你使用 will-change 属性时,浏览器会根据指定的属性或内容进行优化。例如,如果你告知一个元素的 transform 将会改变,浏览器可能会为该元素分配一个独立的图层,从而避免在动画或变换时触发重排 (reflow) 或重绘 (repaint)
图层是渲染树的一个优化机制,浏览器会将一些元素提升到单独的图层中进行绘制。这些图层最终会被合成(compositing)成一个完整的页面图像。图层可以减少重排(reflow)和重绘(repaint)操作的开销,因为图层中的元素变化不会影响其他图层。
图层的创建条件
浏览器通常在以下情况下创建新的图层:
0. CSS 属性:使用具有 3D 变换(如 transform: translateZ(0)),或其他可能导致提升为独立图层的属性(如 will-change)。
0. 显示属性:使用 CSS 属性 position: fixed 或 position: sticky。
0. 动画和过渡:对包含动画(如 @keyframes)的元素会尝试提升为图层,以提高动画性能。
0. CSS 滤镜和混合模式:使用 filter 或 mix-blend-mode。
0. Canvas 和 视频元素:HTML5 的 <canvas> 和 <video> 元素通常会被提升为独立图层
mix-blend-mode 是一个 CSS 属性,用于设置元素的内容与其父元素的背景或其他内容混合的方式
svg-sprite-loader svgo-loader webpack-ant-icon-loader
preload-webpack-plugin
Storybook