备战前端面试—工具与优化篇
Webpack
简述一下 webpack 的原理?
webpack
是一个用于现代JavaScript
应用程序的静态模块打包工具。当
webpack
处理应用程序时,会先从入口文件(通过entry指定)开始,递归查询依赖(这个过程使用 babel 来遍历 AST 抽象语法树,提取其中的 import 声明)。随后生成依赖图谱(使用了图这种数据结构,把各个模块的依赖推入数组)。
webpack 将各个模块的代码封装到函数(seal)中,使用内部的一些工具函数处理依赖关系、上下文。
实现一个加载方法将封装各个模块的函数引入(chunk),置于同一个执行环境。
执行加载方法,准备输出产物。
确认好输出内容后,根据配置输出的路径和文件名,把文件内容写入到文件系统。
说说 webpack 的常用配置?
module.exports = { // __dirname值为所在文件的目录,context默认为执行webpack命令所在的目录 context: path.resolve(__dirname, 'app'), // 必填项,编译入口,webpack启动会从配置文件开始解析,如下三种(还有一种动态加载entry的方式就是给entry传入一个函数,这个在项目比较大,页面很多的情况下可以优化编译时间) entry: './app/entry', // 只有一个入口,入口只有一个文件 entry: ['./app/entry1', './app/entry2'], // 只有一个入口,入口有两个文件 // 两个入口 entry: { entry1: './app/entry1', entry2: './app/entry2' }, // 输出文件配置 output: { // 输出文件存放的目录,必须是string类型的绝对路径 path: path.resolve(__dirname, 'dist'), // 输出文件的名称 filename: 'bundle.js', filename: '[name].js', // 配置了多个入口entry是[name]的值会被入口的key值替换,此处输出文件会输出的文件名为entry1.js和entry2.js filename: [chunkhash].js, // 根据chunk的内容的Hash值生成文件的名称,其他只还有id,hash,hash和chunkhash后面可以使用:number来取值,默认为20位,比如[name]@[chunkhash:12].js, // 文件发布到线上的资源的URL前缀,一般用于描述js和css位置,举个例子,打包项目时会导出一些html,那么html里边注入的script和link的地址就是通过这里配置的 publicPath: "https://cdn.example.com/assets/", // CDN(总是 HTTPS 协议) publicPath: "//cdn.example.com/assets/", // CDN (协议相同) publicPath: "/assets/", // 相对于服务(server-relative) publicPath: "assets/", // 相对于 HTML 页面 publicPath: "../assets/", // 相对于 HTML 页面 publicPath: "", // 相对于 HTML 页面(目录相同) // 当需要构建的项目可以被其他模块导入使用,会用到libraryTarget和library library: 'xxx', // 配置导出库的名称,但是和libraryTarget有关,如果是commonjs2默认导出这个名字就没啥用 // 从webpack3.1.0开始,可以为每个target起不同的名称 library: { root: "MyLibrary", amd: "my-library", commonjs: "my-common-library" }, libraryTarget: 'umd', // 导出库的类型,枚举值: umd、commonjs2、commonjs,amd、this、var(默认)、assign、window、global、jsonp // 需要单独导出的子模块,这样可以直接在引用的时候使用子模块,默认的时候是_entry_return_ libraryExport: 'default', // __entry_return_.default libraryExport: 'MyModule', // __entry_return_.MyModule libraryExport: ['MyModule', 'MySubModule '], // 使用数组代表到指定模块的取值路径 __entry_return_.MyModule.MySubModule // 配置无入口的chunk在输出时的文件名称,但仅用于在运行过程中生成的Chunk在输出时的文件名称,这个应该一般和插件的导出有关,支持和filename一样的内置变量 chunkFilename: '[id].js', // 是否包含文件依赖相关的注释信息,在mode为development默认为true pathinfo: true, // JSONP异步加载chunk,或者拼接多个初始chunk(CommonsChunkPlugin,AggressiveSplittingPlugin) jsonpFunction: 'myWebpackJsonp', // 此选项会向硬盘写入一个输出文件,只在devtool启动了sourceMap选项时采用,默认为`[file].map`,除了和filename一样外还可以使用[file] sourceMapFilename: '[file].map', // 浏览器开发者工具里显示的源码模块名称,此选项仅在 「devtool 使用了需要模块名称的选项」时使用,使用source-map调试,关联模块鼠标移动到上面的时候显示的地址,默认这个值是有的,一般不需要关心 devtoolModuleFilenameTemplate: 'test://[resource-path]' }, // 配置模块相关 module: { rules: [ // 配置loaders { test: /\.jsx?$/, // 匹配规则,匹配文件使用,一般使用正则表达值 include: [path.resolve(__dirname, 'app')], // 只会命中这个目录文件 exclude: [path.resolve(__diranme, 'app/demo-files')], // 命中的时候排除的目录 use: [ // 使用的loader,每一项为一个loader,从该数组的最后一个往前执行 'style-loader', // loader的名称,这样则是使用默认配置,可以在后面加!配置属性,也可以用下面方式 { loader: 'css-loader', // loader的名称 options: {} // loader接受的参数 } ], noParse: [ // 不用解析和处理的模块 RegExp | [RegExp] | function(从 webpack 3.0.0 开始) /jquery|lodash/ ] } ] }, // 配置插件 plugins: [ // 压缩js的plugin new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, drop_console: false, } }), ], // 解析文件引用的模块的配置 resolve: { // 模块的根目录,默认从node_modules开始找 modules: [ 'node_modules', 'browser_modules' ], // 模块的后缀名,我们引入模块有时候不写扩展名,自动补充扩展名的顺序如下 extensions: ['.js', '.json', '.jsx', '.css'], // 模块解析时候的别名 alias: { // 那么导入模块时则可以写import myComponent from '$component/myComponent'; $component: './src/component', // 末尾加$精确匹配 xyz$: path.resolve(__dirname, 'path/to/file.js') }, // 此选项决定优先使用package.json配置哪份导出文件 mainFields: ['jsnext:main', 'browser', 'main'], // 是否强制导入语句写明后缀 enforceExtension: false, // 是否将符号链接(symlink)解析到它们的符号链接位置(symlink location) symlinks: true, }, // 选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。 devtool: 'source-map', // 配置输出代码的运行环境,可以为async-node,electron-main,electron-renderer,node,node-webkit,web(默认),webworker target: 'web', externals: { // 使用来自于js运行环境提供的全局变量 jquery: 'jQuery' }, // 控制台输出日志控制 stats: { assets: true, // 添加资源信息 colors: true, // 控制台日志信息是否有颜色 errors: true, // 添加错误信息 errorDetails: true, // 添加错误的详细信息(就像解析日志一样) hash: true, // 添加 compilation 的哈希值 }, devServer: { // 本地开发服务相关配置 proxy: { // 代理到后端服务接口 '/api': 'http://localhost:3000' }, contentBase: path.join(__dirname, 'public'), // 配置devserver http服务器文件的根目录 compress: true, // 是否开启gzip压缩 hot: true, // 是否开启模块热交换功能 https: false, // 是否开启https模式 historyApiFallback: true, // 是否开发HTML5 History API网页 }, profile: true, // 是否捕捉webpack构建的性能信息,用于分析是什么原因导致的构建性能不佳 cache: false, // 缓存生成的 webpack 模块和 chunk,来改善构建速度。缓存默认在观察模式(watch mode)启用。 cache: { // 如果传递一个对象,webpack 将使用这个对象进行缓存。保持对此对象的引用,将可以在 compiler 调用之间共享同一缓存: cache: SharedCache // let SharedCache = {} }, watch: true, // 是否启用监听模式 watchOptions: { // 监听模式选项 ignored: /node_modules/, // 不监听的文件或文件夹,支持正则匹配,默认为空 aggregateTimeout: 300, //监听到变化后,300ms再执行动作,节流,防止文件更新频率太快导致重新编译频率太快 poll: 1000 // 检测文件是否变化,间隔时间 }, // 输出文件的性能检查配置 perfomance: { hints: 'warning', // 有性能问题时输出警告 hints: 'error', // 有性能问题时输出错误 hints: false, // 关闭性能检查 maxAssetSize: 200000, // 最大文件大小,单位bytes maxEntrypointSize: 400000, // 最大入口文件的大小,单位bytes // 此属性允许 webpack 控制用于计算性能提示的文件。 assetFilter: function(assetFilename) { return assetFilename.endsWith('.css') || assetFilename.endsWith('.js'); } } }
你常用的 loader 有哪些?
- file-loader:加载文件资源,如字体、图片,具有移动、复制、删除功能。
- url-loader:计算文件的 base64 编码,常用于加载较小图片,转化为 DataURL 来减少请求次数。
- babel-loader:加载 js / jsx 文件, 将 ES6 / ES7 代码转换成 ES5,抹平兼容性问题。
- ts-loader:加载 ts / tsx 文件,编译 TypeScript。
- style-loader:将 css 文件以
<style>
的形式插入 html 中。 - css-loader:分析
@import
和url()
引入 css 文件和对应资源。 - less-loader / sass-loader:css 预处理器,在 css 中新增了许多语法,提高了开发效率。
- html-minify-loader:压缩HTML。
介绍一下 webpack 常用的 plugin?
通过配置文件导出对象中
plugins
属性传入new
实例对象。其本质是一个具有apply
方法的javascript
对象。用于修改行为:
- DefinePlugin:编译时配置全局变量。
- ignore-plugin:忽略部分文件。
用于优化:
- extract-text-webpack-plugin:提取 CSS 到一个单独的文件中。
- optimize-css-assets-webpack-plugin:对 css 快速去重。
- webpack-bundle-analyzer:一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。
- happypack:通过多进程模型加快代码构建。
- DllPlugin:分离打包加快构建。
- prepack-webpack-plugin:通过 Facebook 的 Prepack 优化输出的 JavaScript 代码性能。
- uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码。
- imagemin-webpack-plugin:压缩图片文件。
- HotModuleReplacementPlugin:模块热替换。
用于提高开发效率:
- ProvidePlugin:从环境中提供的全局变量中加载模块,而不用导入对应的文件,代替require 和 import。
- html-webpack-plugin:可以根据模板自动生成 html 代码,并自动引用 css 和 js 文件。
- web-webpack-plugin:方便的为单页应用输出 HTML,比 html-webpack-plugin 好用。
- clean-webpack-plugin:自动清理构建目录。
说说 Loader 和 Plugin 的区别?
从功能上来看:
loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。
plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他功能。
从整个运行时机上来看:
loader 运行在打包之前。
plugins 在整个编译周期都起作用。
从原理上看:
loader 实质是一个转换器函数。因为 webpack 运行在 nodeJS 环境,只能识别 js 文件,loader 以 webpack 对象作为自己的上下文,接收一个文件源内容作为参数,对其进行编译转化。
plugins 本质是发布订阅模式中的订阅者。它监听了 webpack 生命周期的一些事件,在事件广播时触发回调函数,执行其中的逻辑,通过 webpack 的 API 来改变 webpack 行为。在配置 plugins 时需要 new 一个实例对象。
说说 webpack 的热更新是如何做到的?原理是什么?
HMR
全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用。例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失。如果使用的是
HMR
,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用。在
webpack
中配置开启:const webpack = require('webpack') module.exports = { // ... devServer: { // 开启 HMR 特性 hot: true // hotOnly: true } }
需要有一些额外的操作,指定哪些模块发生更新时进行
HMR
:if(module.hot){ module.hot.accept('./util.js',()=>{ console.log("util.js更新了") }) }
原理:
通过 webpack-dev-server 建立两个服务器:静态资源代理服务器(express)和 socket 服务器。
express server 将打包后的资源发送给浏览器,通过 AJAX 被浏览器请求和解析。
socket server 建立 websocket 长连接,实现双向瞬时通信。
socket server 监听到对应的模块变动,生成两个文件
.json
(manifest 文件,包含了hash
和chundId
,用来说明变化的内容) 和.js
(update chunk)。通过长连接,socket server 可以直接将这两个文件主动推送给浏览器。
浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新。
说说 webpack proxy 工作原理?为什么能解决跨域?
webpack proxy
,即webpack
提供的代理服务,基本行为就是接收客户端发送的请求后转发给其他服务器。其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制)。
想要实现代理首先需要一个中间服务器,
webpack
中提供服务器的工具为webpack-dev-server
,只适用在开发阶段。proxy
的工作原理实质上是利用http-proxy-middleware
这个http
代理中间件,实现请求转发给其他服务器,利用服务器之间通信不存在跨域来解决问题。目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地。tree-shaking 如何配置?工作原理?
Tree-shaking,即消除那些被引用了但未被使用的模块代码。
想要对代码进行 tree-shaking,首先必须处于生产模式。Webpack 只有在压缩代码的时候会 tree-shaking,而这只会发生在生产模式中。
其次,需要采用 ES6 模块化语法,tree-shaking 是静态编译时优化,使用 commonJS 的模块无法被分析。
随着 Webpack4.0 采用约定高于配置的理念,在 mode 为
production
的情况默认开启优化配置项。默认使用的是 terser-webpack-plugin 压缩插件,在此之前是使用 uglifyjs-webpack-plugin,其中的区别是内置对 ES6 的压缩不是很好。
注意默认情况下,所有的模块都会被认为是有副作用的,Tree Shaking 不能删除带副作用的代码。所以,webpack 提供了 sideEffects 这个配置,在 package.json 中将这个配置设置为 false,意思就是 webpack 认为所有文件都是没有副作用的。
原理:
Tree Shaking 会分析所有的 es6 module import 和 export,判断哪些是无用的代码,同时为它们打上标记,然后使用压缩工具(TerserPlugin)删除这些冗余的代码。
说说 webpack 的代码分割?解决了哪些问题?
code-spliting 是提高代码使用率的关键技术,即代码分割。代码分割是指,将脚本中无需立即调用的代码在代码构建时转变为异步加载的过程。在 Webpack 构建时,会避免加载已声明要异步加载的代码 ,异步代码会被单独分离出一个文件,当代码实际调用时被加载至页面。
可以通过
import()
关键字让浏览器在程序执行时异步加载相关资源。代码分割也包括静态和动态分割两种。
静态代码分割是指,在代码中明确声明需要异步加载的代码。
import Listener from './listeners.js' const getModal = () => import('./src/modal.js') Listener.on( 'didSomethingToWarrentModalBeingLoaded', () => { // Async fetching modal code from a separate chunk getModal().then( (module) => { const modalTarget = document.getElementById('Modal') module.initModal(modalTarget) }) } )
动态代码分割是指:在代码调用时根据当前的状态,「动态地」异步加载对应的代码块。
const getTheme = (themeName) => import(`./src/themes/${themeName}`) // using `import()` 'dynamically' if (window.feeling.stylish) { getTheme('stylish').then((module) => { module.applyTheme() }) } else if (window.feeling.trendy) { getTheme('trendy').then((module) => { module.applyTheme() }) }
实现了类似 commonJS 的效果。但它们的工作原理却大不相同。实际上 webpack 已经在构建时将所有声明的分割模块分离为一个单独的文件(contextModule),在调用时和静态分割一样,是在请求一个已经准备好的文件。
什么是 webpack 的作用域提升?为什么要作用域提升?
可以简单地把 Scope Hoisting 理解为把每个模块被 Webpack 处理成模块初始化函数整理到一个统一的包裹函数里,也就是把多个作用域用一个作用域取代,以减少内存消耗并减少包裹块代码,从每个模块有一个包裹函数变成只有一个包裹函数包裹所有的模块。
但是有一个前提就是,当模块的引用次数大于 1 时,比如被引用了两次或以上,那么这个效果会无效,这是因为被引用多次即这个模块代码会被内联多次,从而增加了打包出来的 JS Bundle 体积。也就是被引用多次的模块在被 Webpack 处理后,会被独立的包裹函数所包裹。
需要作用域提升的原因:
- 大量函数闭包包裹代码,导致体积增大,模块越多越明显
- 运行代码时创建的函数作用域变多,内存开销变大
SourceMap 有什么用?
Source map 就是一个信息文件,里面储存着代码的位置信息。这种文件主要用于开发调试,现在代码都会经过压缩混淆,这样报错提示会很难定位代码。通过 SourceMap 能快速定位到源代码,并进行调试。
通常情况 SourceMap 在开发环境开启,线上环境关闭。
开发环境推荐:
cheap-module-eval-source-map
生产环境推荐:
cheap-module-source-map 、hidden-source-map
应用场景:
- 开发期间,开发人员能直接通过浏览器调试工具直接定位错误或进行 Debug 调试。
- 线上排查问题的时候可以将 SourceMap 上传到错误监控系统。
如何提高webpack的构建速度?
常见的提升构建速度的手段有如下:
优化 loader 配置
在使用
loader
时,可以通过配置include
、exclude
、test
属性来匹配文件,include
、exclude
规定哪些匹配应用loader
。合理使用 resolve.extensions
通过
resolve.extensions
在解析文件时自动添加拓展名,当我们引入文件的时候,若没有文件后缀名,则会根据数组内的值依次查找,配置的时候,不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度。优化 resolve.modules
resolve.modules
用于配置webpack
去哪些目录下寻找第三方模块。所以可以指明存放第三方模块的绝对路径,以减少寻找。优化 resolve.alias
alias
给一些常用的路径起一个别名,当我们的项目目录结构比较深的时候,一个文件的路径可能是./../../
的形式,通过配置alias
以减少查找过程。使用 cache-loader
在一些性能开销较大的
loader
之前添加cache-loader
,以将结果缓存到磁盘里,显著提升二次构建速度,保存和读取这些缓存文件会有一些时间开销,所以只对性能开销较大的loader
使用。启动多线程
借助一些插件如 HappyPack,使用多进程并行运行来提高构建速度。
合理使用 sourceMap
打包生成
sourceMap
的时候,如果信息越详细,打包速度就会越慢。因此根据实际需要选择最合适的配置。
Redux
说说你对Redux的理解?其工作原理?
在 React 应用中存在很多个组件,每个组件的
state
是由自身进行管理,包括组件定义自身的state
、组件之间的通信通过props
传递、使用Context
实现数据共享。如果让每个组件都存储自身相关的状态,理论上来讲不会影响应用的运行,但在开发及后续维护阶段,我们将花费大量精力去查询状态的变化过程。这种情况下,如果将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的。
redux
就是一个实现上述集中管理的容器,遵循三大基本原则:- 单向数据流
- 不可变数据
- 纯函数
工作原理:
通过在 context 注入一个全局的 store,集中式地管理数据。
为 store 指定 reducer,完成连接。
其内部的 state 对象一旦创建就不可变,更新 state,采用全新的 state 替换原本的 state。
通过 Action Creator 定义不同的 Action,来操作不同的 state。
通过 reducer 的 dispatch 操作,传入 Action 来修改组件内部状态。
你在React项目中是如何使用Redux的? 项目结构是如何划分的?
通过
redux
将整个应用状态存储到store
中,组件可以派发dispatch
行为action
给store
。其他组件通过订阅
store
中的状态state
来更新自身的视图。可以根据项目具体情况进行选择,以下列出两种常见的组织结构:
按角色组织(MVC)
按照代码在项目中的角色定位来组织,如:
reducers/ todoReducer.js filterReducer.js actions/ todoAction.js filterActions.js components/ todoList.js todoItem.js filter.js containers/ todoListContainer.js todoItemContainer.js filterContainer.js
按功能组织
把完成同一功能的代码放在一个目录下,一个应用功能包含多个角色的代码。如:
todoList/ actions.js actionTypes.js index.js reducer.js views/ components.js containers.js filter/ actions.js actionTypes.js index.js reducer.js views/ components.js container.js
说说对Redux中间件的理解?常用的中间件有哪些?实现原理?
中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务,衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的。
Redux 中如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件,中间件就是放在就是在
dispatch
过程,在分发action
时进行拦截处理。本质上是一个函数,在内部对 store.dispatch 进行了封装,在发出 action 和执行 reducer 之间,添加了一些额外的操作。
常用的中间件有:
- redux-thunk:用于异步操作
- redux-logger:用于日志记录
上述的中间件都需要通过
applyMiddlewares
进行注册,作用是将所有的中间件组成一个数组,依次执行然后作为第二个参数传入到
createStore
中。const store = createStore( reducer, applyMiddleware(thunk, logger) );
实现原理:
看看
applyMiddlewares
的源码:export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return {...store, dispatch} } }
所有中间件被放进了一个数组
chain
,然后遍历执行,最后将所有的中间件组合成一个函数,传入store.dispatch
。了解过 Redux-thunk 吗?能不能手动实现?
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
从源码来看,redux-thunk 非常简单。如果传入的 action 是函数,就直接执行它,并传入 dispatch 等作为参数。在 action 函数内部,就可以在异步代码中执行 dispatch 了。
项目性能优化
说说如何借助webpack来优化前端性能?
通过
webpack
优化前端的手段有:JS 代码压缩
比较常用的 uglifyjs-webpack-plugin、TerserPlugin 都是对 JS 文件进行压缩处理。
CSS 代码压缩
css-minimizer-webpack-plugin,通常是去除无用的空格。optimize-css-assets-webpack-plugin 可以对 css 进行去重。
HTML 代码压缩
使用 HtmlWebpackPlugin 插件来生成 HTML 模板时,通过配置属性 minify 进行 html 优化。
文件大小压缩
ComepressionPlugin,减少文件体积,减少 http 传输过程中的损耗。
图片大小压缩
在打包之后,一些图片文件的大小是远远要比
js
或者css
文件要大,所以图片压缩较为重要。可在 image-webpack-loader 中配置不同格式的图片压缩,或者 url-loader 将较小的图片转换为 base64 编码。
Tree Shaking
实现 Tree shaking 有两种不同的方案:
usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化
sideEffects:webpack 将认为整个文件无副作用,完全移除所有未使用的代码。
代码分割
默认情况下,所有的 JavaScript 代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度。
将代码分离到不同的 bundle 中,之后我们可以按需加载,或者并行加载这些文件。
代码分离可以分出更小的 bundle,以及控制资源加载优先级,提供代码的加载性能。
作用域提升
把一些包裹模块的独立函数整合到一个统一的包裹函数中,以减少内存消耗并减少包裹块代码。
谈谈你对编码优化的理解?
编码优化,指的就是 在代码编写时,通过一些最佳实践,提升代码的执行性能。通常这并不会带来非常大的收益,但这属于程序猿的自我修养。
循环: 通常是编码性能的关键点,代码的性能问题会再循环中被指数倍放大。
- 尽可能 减少循环次数,包括减少遍历的数据量,完成目的后立即终止循环。
- 避免在循环中执行大量的运算,避免重复计算,相同的执行结果应该使用缓存。
- 尽量避免使用 for-in 循环,因为它会枚举原型对象,耗时大于普通循环。
条件流程性能: Map / Object > switch > if-else
数据读取:
- 尽量在局部作用域中进行变量缓存。
- 避免嵌套过深的数据结构,数据扁平化 有利于数据的读取和维护。
dom 优化:
- 减少访问 dom 的次数,如需多次,将 dom 缓存,如 useRef。
- 减少重绘与回流,多次操作合并为一次,减少对计算属性的访问。大量操作时,可将 dom 脱离文档流或者隐藏,待操作完成后再重新恢复。
- 使用事件委托,避免大量的事件绑定。
css 优化:
- 层级扁平,避免过于多层级的选择器嵌套。
- 特定的选择器 好过一层一层查找:
.xxx-child-text{}
优于.xxx .child .text{}
- 减少使用通配符与属性选择器。
- 减少不必要的多余属性。
- 使用 动画属性 实现动画,动画时脱离文档流,开启硬件加速,优先使用 css 动画。
- 使用
<link>
替代原生@import
。
html 优化:
- 减少 dom 数量,避免不必要的节点或嵌套。
- 避免
<img src="" />
空标签,能减少服务器压力,因为 src 为空时,浏览器仍然会发起请求。 - 图片提前 指定宽高 或者 脱离文档流,能有效减少因图片加载导致的页面回流。
- 语义化标签 有利于 SEO 与浏览器的解析时间。
- 减少使用 table 进行布局,避免使用
<br/>
与<hr/>
在页面的基础上可以做哪些优化?
引入位置:
- css 文件
<head>
中引入, js 文件<body>
底部引入。 - 影响首屏的,优先级很高的 js 也可以头部引入,甚至内联。
减少请求(http 1.0 - 1.1):
- 合并请求,正确设置 http 缓存。
减少文件体积:
- 删除多余代码,tree-shaking,UglifyJs 等。
- 混淆 / 压缩代码,开启 gzip 压缩。
- 多份编译文件按条件引入,针对现代浏览器直接给 ES6 文件,只针对低端浏览器引用编译后的 ES5 文件。
- 动态 polyfill,只针对不支持的浏览器引入 polyfill。
图片优化:
- 根据业务场景,与 UI 探讨选择合适质量,合适尺寸。
- 根据需求和平台,选择合适格式,例如非透明时可用 jpg;非 IOS 端,使用 webp。
- 小图片合成雪碧图,低于 5K 的图片可以转换成 base64 内嵌。
- 合适场景下,使用 iconfont 或者 svg。
使用缓存:
- 浏览器缓存,通过设置请求的过期时间,合理运用浏览器缓存。
- CDN缓存,静态文件合理使用 CDN 缓存技术。
- 服务器缓存,将不变的数据、页面缓存到内存或远程存储(redis等)上。
- 数据缓存: 通过各种存储将不常变的数据进行缓存,缩短数据的获取时间。
- css 文件
首屏渲染优化有哪些方式?
加载优化:
- 代码分割,使首屏依赖的文件体积最小。
- 非关键性的文件尽可能的异步加载和懒加载,避免阻塞首屏渲染。
- 使用
dns-prefetch / preconnect / prefetch / preload
等浏览器提供的资源提示,加快文件传输。 - 谨慎控制好 Web字体,控制字体包的加载时机,如果使用的字体有限,那尽可能只将使用的文字单独打包,能有效减少体积。
- 合理利用 Localstorage / server-worker 等存储方式进行 数据与资源缓存。
顺序优化:
- 分清轻重缓急,重要的元素优先渲染,视窗内的元素优先渲染。
用户感知优化:
- 利用一些动画过渡效果,能有效减少用户对卡顿的感知
- 尽可能利用骨架屏(Placeholder) / Loading 等减少用户对白屏的感知。
- 动画帧数尽量保证在30帧以上,低帧数、卡顿的动画宁愿不要。
- js 执行时间避免超过 100ms,寻找可缓存的点,任务分割异步或 web worker 执行。
服务端渲染(SSR):
- 减少首屏需要的数据量,剔除冗余数据和请求。
- 控制好缓存,对数据/页面进行合理的缓存。
- 页面的请求使用流的形式进行传递。
知道哪些性能优化的指标?
资源加载:
chrome 瀑布图,打开 NetWork,
后记
待续。
- Post link: https://blog.sticla.top/2021/08/20/front-end-interview-review-tools/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.
GitHub Issues