Skip to main content

前端工程化、性能优化面试题44题

1.如何⽤ webpack 来优化前端性能?

要通过 Webpack 来优化前端性能,可以考虑以下一些优化策略:

  1. 代码分割(Code Splitting): 使用 Webpack 的代码分割功能,将代码拆分为多个小块,按需加载。这样可以减少初始加载时需要下载的资源量,提高页面的加载速度。

  2. 压缩代码: 使用 Webpack 的 UglifyJsPlugin 或 TerserPlugin 来压缩 JavaScript 代码,以减少文件大小,加快加载速度。注 uglifyjs-webpack-plugin、terser-webapck-plugin来压缩js代码,css-minimizer-webpack-plugin来压缩css代码

  3. Tree Shaking: 通过 Webpack 的 Tree Shaking 功能,消除 JavaScript 中未使用的代码,减少最终打包文件的体积。

  4. 图片优化: 使用 image-webpack-loader 或者 url-loader 来优化图片资源,包括压缩图片、转换为 Base64 格式或者按需加载等方式。

  5. 使用 CDN: 将静态资源部署到 CDN 上,可以加速资源的加载速度,减轻服务器压力。

  6. 缓存优化: 通过 Webpack 的文件名哈希、chunkhash 等机制,实现文件名的缓存优化,确保文件更新后能够正确地被浏览器缓存。

  7. 懒加载和预加载: 使用 Webpack 实现懒加载和预加载,根据页面的实际需求来延迟加载某些资源或者提前加载可能需要的资源,以提高用户体验和性能。

    1. 使用Webpack实现懒加载和预加载可以通过动态导入(dynamic import)和webpack的代码分割(code splitting)功能来实现。以下是两种方式的简要说明:

      1. 懒加载(Lazy Loading):懒加载是指在页面实际需要时再去加载特定资源,而不是在初始加载时就加载所有资源。在Webpack中,可以使用动态导入(dynamic import)来实现懒加载。例如:
      const button = document.getElementById('myButton');
      button.addEventListener('click', async () => {
      const module = await import('./module');
      module.doSomething();
      });

      在上面的例子中,当按钮被点击时,才会去动态加载'module'模块,实现了懒加载的效果。

      1. 预加载(Preloading):预加载是指在页面加载完成后,提前加载可能需要的资源,以提高用户体验和性能。在Webpack中,可以使用prefetchpreload来实现预加载。例如:
      import(/* webpackPrefetch: true */ './module');

      或者

      import(/* webpackPreload: true */ './module');

      这样做可以告诉Webpack在空闲时提前加载指定模块,以便在未来可能需要时能够更快地加载。

      通过使用动态导入和webpack的代码分割功能,可以灵活地实现懒加载和预加载,根据页面的实际需求来延迟加载某些资源或者提前加载可能需要的资源,从而提高用户体验和性能。

  8. 使用 Webpack 插件: 使用一些优化类的 Webpack 插件,如 MiniCssExtractPlugin 提取 CSS、HtmlWebpackPlugin 生成 HTML 文件、BundleAnalyzerPlugin 分析打包文件等,来帮助进行更细致的优化工作。

以上是一些基本的 Webpack 优化策略,实际项目中可以根据具体情况和需求,结合其他优化

2.如何提⾼ webpack 的打包速度?

要提高 Webpack 的打包速度,可以考虑以下一些优化策略:

  1. 使用最新版本的 Webpack: 每个新版本的 Webpack 都会带来性能方面的改进和优化,因此始终使用最新版本的 Webpack 可以获得更好的性能表现。
  2. 合理配置 loader 和 plugin: 确保只加载必要的 loader 和 plugin,避免不必要的资源消耗。并且合理配置 loader 和 plugin 的选项,以免造成额外的性能开销。
  3. 使用 HappyPack: HappyPack 可以将 loader 的执行任务分配给多个子进程,从而加快构建速度。
  4. 使用 DllPlugin 和 DllReferencePlugin: 使用 DllPlugin 将第三方库(如 React、Vue 等)打包为单独的文件,然后使用 DllReferencePlugin 在开发环境和生产环境中引用该文件,减少打包时间。
  5. 使用缓存: 使用 Webpack 的缓存功能,减少重复构建的时间。可以使用 cache-loader 或者 hard-source-webpack-plugin 等插件来实现缓存。
  6. 优化文件搜索范围: 在配置 loader 和 plugin 时,尽量缩小文件搜索的范围,只对必要的文件进行处理,避免不必要的性能开销。
  7. 使用 Tree Shaking: 通过配置 Webpack,确保只打包使用到的代码,而不是将整个库都打包进去。
  8. 使用并行构建工具: 可以考虑使用类似于 parallel-webpack、thread-loader 等并行构建工具,以提高构建速度。
  9. 优化配置项: 对 Webpack 的配置文件进行优化,尽量简化配置,减少不必要的计算和处理。

通过以上优化策略,可以提高 Webpack 的打包速度。但需要注意的是,优化的效果可能因项目的具体情况而有所不同,因此建议在实施优化策略时,结合具体项目的需求和特点,进行合理的优化调整。

另外,还可以通过使用现代化的构建工具(如 Vite、esbuild 等)来替代 Webpack,在某些场景下可以获得更好的构建性能。在实际项目中,可以根据具体情况进行评估和选择。

3.如何提⾼ webpack 的构建速度?

要提高 Webpack 的构建速度,可以考虑以下一些优化策略:

  1. 使用最新版本的 Webpack: 每个新版本的 Webpack 都会带来性能方面的改进和优化,因此始终使用最新版本的 Webpack 可以获得更好的构建性能。
  2. 合理配置 loader 和 plugin: 确保只加载必要的 loader 和 plugin,避免不必要的资源消耗。并且合理配置 loader 和 plugin 的选项,以免造成额外的性能开销。
  3. 使用缓存: 使用 Webpack 的缓存功能,减少重复构建的时间。可以使用 cache-loader 或者 hard-source-webpack-plugin 等插件来实现缓存,避免每次构建都重新处理所有文件。
  4. 使用 Tree Shaking: 通过配置 Webpack,确保只打包使用到的代码,而不是将整个库都打包进去。这可以通过配置 optimization.treeShaking 来实现。
  5. 优化文件搜索范围: 在配置 loader 和 plugin 时,尽量缩小文件搜索的范围,只对必要的文件进行处理,避免不必要的性能开销。
  6. 使用并行构建工具: 可以考虑使用类似于 parallel-webpack、thread-loader 等并行构建工具,以提高构建速度。
  7. 优化配置项: 对 Webpack 的配置文件进行优化,尽量简化配置,减少不必要的计算和处理。
  8. 使用 DllPlugin 和 DllReferencePlugin: 使用 DllPlugin 将第三方库(如 React、Vue 等)打包为单独的文件,然后使用 DllReferencePlugin 在开发环境和生产环境中引用该文件,减少打包时间。
  9. 使用现代化构建工具: 在某些场景下,可以考虑使用现代化的构建工具(如 Vite、esbuild 等)来替代 Webpack,在某些场景下可以获得更好的构建性能。
  10. 使用持久缓存: Webpack 5 引入了持久性缓存,可以通过配置 cache.type: 'filesystem' 来启用,这样可以将构建结果缓存到磁盘中,提高重建速度。
  11. 优化输出文件: 对输出的文件进行优化,包括使用压缩算法、减少文件体积、合并文件等方式,以提高文件的传输和加载速度。
  12. 使用 CDN: 将静态资源部署到 CDN 上,可以加速资源的加载速度,减轻服务器压力。

以上是一些常见的 Webpack 构建速度优化策略,实际项目中可以根据具体情况和需求,结合其他优化手段来提高构建速度。

4.webpack 的构建流程

Webpack 的构建流程可以大致分为以下几个步骤:

  1. 解析配置文件: Webpack 会首先读取并解析项目中的配置文件(如 webpack.config.js),根据配置文件中的设置来确定需要构建的入口文件、输出文件路径、以及各种 loader、plugin 的配置等信息。

  2. 识别入口文件: 根据配置文件中指定的入口文件(entry),Webpack 开始递归解析入口文件及其依赖的模块。在这一过程中,Webpack 使用内置的模块解析器来分析模块之间的依赖关系。

  3. 执行 loader: 在识别并解析模块的过程中,Webpack 会根据配置文件中的规则,对不同类型的模块应用相应的 loader 进行转换。这可以包括将 ES6 代码转换为 ES5、将 SCSS 转换为 CSS、对图片进行压缩等操作。

  4. 应用 plugin: 在模块转换完成后,Webpack 会执行配置中的各种 plugin,这些 plugin 可以用于完成各种任务,如代码压缩、资源提取、环境变量注入等。

  5. 生成输出文件: 经过 loader 和 plugin 的处理后,Webpack 将所有模块打包成一个或多个 bundle 文件。这些文件包括 JavaScript、CSS、图片等资源,它们可以通过浏览器访问并加载。

  6. 输出结果: 最后,Webpack 将打包生成的文件输出到指定的目录中,完成整个构建流程。

在以上步骤中,Webpack 会根据配置文件中的设置以及模块之间的依赖关系,对项目中的资源进行处理和打包,最终生成可以在浏览器中运行的静态文件。

5. babel 是什么,怎么做到的?

Babel 是一个广泛使用的 JavaScript 编译器,它的主要功能是将当前或旧版本的 JavaScript 代码转换为向后兼容的 JavaScript 代码,以便在当前和旧版本的浏览器或环境中运行。Babel 能够实现这一功能的原因在于它具备以下特点和功能:

  1. 语法转换:Babel 可以将 ECMAScript 2015+ 的新语法转换为向后兼容的 JavaScript 代码,这包括箭头函数、解构赋值、Promise 等新的语言特性。

  2. API 转换:Babel 可以将 ECMAScript 2015+ 的新内置 API 转换为向后兼容的 JavaScript 代码,这包括 Set、Map、Symbol 等新的内置对象和方法。

  3. Polyfill 添加:Babel 可以通过添加 polyfill 的方式来模拟新 API 的行为,使得旧版本的 JavaScript 运行环境也能够支持新的 API。

  4. 插件化:Babel 是一个高度可扩展的工具,它可以通过插件的方式来实现不同的转换功能,用户可以根据自己的需求选择不同的插件进行配置,以满足特定的转换需求。

Babel 实现这些功能的原理主要包括以下步骤:

  1. 词法分析和语法分析:Babel 首先会对输入的 JavaScript 代码进行词法分析和语法分析,将代码转换为抽象语法树(AST)的形式。

  2. 转换处理:Babel 会基于 AST 对代码进行转换处理,根据配置的插件和预设,对代码中的特定语法和 API 进行转换和兼容处理,例如将箭头函数转换为普通函数、

6. webpack 热更新的机制原理?

Webpack 的热更新(Hot Module Replacement,HMR)机制是一种能够在应用程序运行时替换、添加或删除模块而无需完全刷新页面的技术。它使开发人员能够在保持应用程序状态的同时,快速地查看对代码的更改。下面是Webpack热更新的基本原理:

  1. 启动 HMR 功能:在 webpack 配置中,通过启用 HotModuleReplacementPlugin 插件来开启 HMR 功能。这个插件将会在应用运行时,替换、添加或删除模块而无需完全刷新页面。

  2. 监控文件变化:在开发模式下,Webpack Dev Server 会监控项目中所有文件的变化。当文件发生变化时,Webpack 会构建新的模块,并将这些模块传递给 HMR runtime。

  3. HMR Runtime:Webpack 在构建输出的 bundle 中包含了 HMR runtime,它负责在运行时接收来自 webpack Dev Server 的更新,并将这些更新应用到运行中的应用程序中。

  4. 模块热替换:当开发人员对代码进行修改后,Webpack Dev Server 会构建新的模块。然后 HMR runtime 会与 webpack Dev Server 通信,以确定哪些模块发生了变化。HMR runtime 将新模块的更新发送给应用程序,并使用更新后的模块替换旧模块,从而实现热更新。

总的来说,Webpack 热更新的机制原理是通过 HMR runtime 在运行时接收来自 webpack Dev Server 的更新,并将这些更新应用到运行中的应用程序中,从而实现模块的热替换,使得开发人员能够在保持应用程序状态的同时,快速地查看对代码的更改。

二、实现原理

首先来看看一张图,如下:

热更新-原理图

  • Webpack Compile:将 JS 源代码编译成 bundle.js
  • HMR Server:用来将热更新的文件输出给 HMR Runtime
  • Bundle Server:静态资源文件服务器,提供文件访问路径
  • HMR Runtime:socket 服务器,会被注入到浏览器,更新文件的变化
  • bundle.js:构建输出的文件
  • 在 HMR Runtime 和 HMR Server 之间建立 websocket,即图上 4 号线,用于实时更新文件变化

上面图中,可以分成两个阶段:

  • 启动阶段为上图 1 - 2 - A - B

在编写未经过webpack打包的源代码后,Webpack Compile将源代码和HMR Runtime一起编译成bundle文件,传输给Bundle Server静态资源服务器

  • 更新阶段为上图 1 - 2 - 3 - 4

当某一个文件或者模块发生变化时,webpack监听到文件变化对文件重新编译打包,编译生成唯一的hash值,这个hash值用来作为下一次热更新的标识

根据变化的内容生成两个补丁文件:manifest(包含了hashchundId,用来说明变化的内容)和chunk.js模块

由于socket服务器在HMR RuntimeHMR Server之间建立websocket链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash值,如下图的h属性,作为下一次热更细的标识

热更新-补丁文件1

在浏览器接受到这条消息之前,浏览器已经在上一次socket消息中已经记住了此时的hash标识,这时候我们会创建一个ajax去服务端请求获取到变化内容的manifest文件

mainfest文件包含重新build生成的hash值,以及变化的模块,对应上图的c属性

浏览器根据manifest文件获取模块变化的内容,从而触发render流程,实现局部模块更新

热更新-补丁文件2

三、总结

关于webpack热模块更新的总结如下:

  • 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和 Socket 服务
  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
  • socket server 是一个 websocket 的长连接,双方可以通信
  • 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest 文件)和.js 文件(update chunk)
  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新

7. 是否有写过 webpack 插件?

了解 webpack 插件吗?

  • 了解,webpack 的大致流程是,初始化配置 => 从入口模块开始解析 => 经过 loader 处理 => 遍历 ast => 找到依赖 => 继续解析依赖,直到所有的子模块都解析完成 => 优化 chunk => 生成 assets => 根据 assets 生成最终的产物
  • 而这个过程中 webpack 不能满足所有的场景,为了 webpack 更加灵活与拓展,设计了插件机制,webpack 的插件机制,基于tapable实现,而tapable提供了多种类型的 hook,比如同步串行 hook,异步并行 hook 等
  • 然后 webpack 目前提供的 hook 大概有 5 类,第一类是 compiler 上的 hook,这类 hook 是大流程上的节点;第二类 complation 上的 hook,这类 hook 是构建模块实例、优化 chunk 等流程上的节点;第三类 NormalModuleFactory 上的 hook,这类 hook 是模块创建、超找模块等流程上节点;第四类是 JavascriptParser 上的 hook,这类 hook 就是遍历 ast 流程上的节点;第五类就是 ContextModuleFactory 上的 hook 与 NormalModuleFactory 上的 hook 类似,但是用的少
  • 最后一个插件以 apply 方法作为入口函数,入口函数会接受一个 compiler 参数,接下来就是根据 webpack 在 compiler,compilation 等对象上爆料的 hooks 上注册 callback,在 callback 内完成拓展功能

写过 webpack 插件吗?

  • 写过,我写过约定式路由插件(任何自己写的插件),写这个插件的目的是为了解决手写 routes 配置文件,做到自动生成 routes 文件,以高开发效率
  • 为了使生成 routes 文件生效,我选择在 webpack 编译之前的 hooks 内完成 routes 文件的生成,而编译之前的 hooks 有,environment、initialize 等 hook,我这里选择 initialize hook,这一个同步串行 hook
  • 最后在 initialize hook 上注册 callback,在 callback 内读取目录及相关的配置,生成路由配置文件

8. 谈下 webpack loader 机制?

Webpack Loader 机制是 Webpack 提供的一种机制,用于处理项目中的各种资源文件,例如 JavaScript 模块、样式表、图片、字体等。通过 Loader 机制,Webpack 可以将不同类型的文件转换为模块,并且可以在转换过程中应用各种转换操作,例如编译、压缩、转译等。

Webpack Loader 机制的主要特点包括:

  1. 模块转换:Loader 可以将不同类型的文件转换为 JavaScript 模块,使得这些文件可以作为模块被引入和使用。

  2. 链式调用:多个 Loader 可以串联使用,每个 Loader 可以对模块进行转换,并将转换后的结果传递给下一个 Loader。

  3. 配置灵活:对于每个 Loader,可以通过配置选项进行定制化配置,从而满足各种不同的转换需求。

  4. 处理多种资源:除了 JavaScript 模块外,Loader 也可以处理样式表、图片、字体等各种资源文件,使得这些资源文件也可以成为模块被引入和使用。

在项目中使用 Loader 通常需要进行以下步骤:

  1. 安装 Loader 模块:首先需要通过 npm 或 yarn 安装所需的 Loader 模块,例如 babel-loader 用于处理 JavaScript 文件、css-loader 用于处理 CSS 文件等。

  2. 配置 Loader:在 webpack 的配置文件中,通过 module.rules 配置项配置需要使用的 Loader,可以指定 Loader 的名称、处理的文件类型、Loader 的配置选项等。

  3. 链式调用:如果需要对文件进行多步转换,可以通过数组的方式指定多个 Loader,并且可以通过 ! 符号指定 Loader 的执行顺序。

举例来说,以下是一个简单的 webpack 配置,使用了 `babel-loader

9.uglify 原理?

Uglify 是一个 JavaScript 代码压缩工具,它的主要原理是通过删除代码中的空白符、注释、简化变量名等方式,从而减小代码体积,提高加载速度。Uglify 的原理可以简单概括为词法分析、语法分析和代码压缩三个步骤。

  1. 词法分析(Lexical Analysis):Uglify 首先对输入的 JavaScript 代码进行词法分析,将代码字符串分割成一系列的 Token(标记),每个 Token 包含了代码中的一个词法单元,如关键字、标识符、运算符等。词法分析器会忽略空格、注释等无关紧要的字符,并为每个 Token 添加对应的类型和值。

  2. 语法分析(Syntax Analysis):接下来,Uglify 使用语法分析器将 Token 流转换成抽象语法树(AST)。抽象语法树是一个树状结构,它反映了代码的语法结构,每个节点代表了代码中的一个语法单元,如变量声明、函数调用、条件语句等。语法分析器会根据 Token 流的语法规则构建对应的抽象语法树。

  3. 代码压缩(Code Compression):一旦得到了抽象语法树,Uglify 就可以利用各种代码压缩技术对代码进行压缩。这些压缩技术包括但不限于:

    • 删除无效的代码和不可达代码
    • 简化变量名和属性名
    • 压缩常量和表达式
    • 删除注释和空白符
    • 合并代码块和语句
    • 代码混淆(将变量名、函数

10. babel 原理?

Babel 是一个广泛使用的 JavaScript 编译器,它主要用于将 ECMAScript 2015+ 的代码转换为向后兼容的 JavaScript 版本,以便在现有环境中运行。Babel 的主要原理包括词法分析、语法分析、转换和生成代码四个步骤。

  1. 词法分析(Lexical Analysis):Babel 首先对输入的代码进行词法分析,将代码字符串分割成一系列的 Token(标记),每个 Token 包含了代码中的一个词法单元,如关键字、标识符、运算符等。词法分析器会忽略空格、注释等无关紧要的字符,并为每个 Token 添加对应的类型和值。

  2. 语法分析(Syntax Analysis):接下来,Babel 使用语法分析器将 Token 流转换成抽象语法树(AST)。抽象语法树是一个树状结构,它反映了代码的语法结构,每个节点代表了代码中的一个语法单元,如变量声明、函数调用、条件语句等。语法分析器会根据 Token 流的语法规则构建对应的抽象语法树。

  3. 转换(Transformation):一旦得到了抽象语法树,Babel 就可以利用插件系统对语法树进行转换。Babel 的插件可以对特定的语法结构进行识别和处理,例如箭头函数、解构赋值、类定义等。插件会遍历语法树,对匹配的语法结构进行转换,从而实现对特定语法特性的支持或转换。

  4. 生成代码(Code Generation):最后,Babel 将转换后的抽象语法树转换回 JavaScript 代码

11. webpack 插件机制?

Webpack 的插件机制是其极为重要的扩展机制之一,通过插件机制可以对Webpack的构建过程进行定制和扩展。在Webpack的构建过程中,插件可以监听Webpack构建生命周期中的各个钩子(Hooks),并且可以访问Webpack的编译实例以及整个构建过程中的各种资源,从而实现对构建过程的干预和定制化处理。

Webpack 插件机制的主要特点包括:

  1. 生命周期钩子(Hooks):Webpack 在构建过程中定义了一系列的生命周期钩子,例如beforeRunrunemitafterEmit等。插件可以通过监听这些钩子来介入Webpack的构建过程。

  2. 编译实例(Compiler)和资源(Compilation):Webpack 插件可以访问Webpack的编译实例和资源,对资源进行修改、分析和优化。编译实例代表了整个Webpack的编译过程,而资源则代表了Webpack正在处理的各种文件和模块。

  3. 功能扩展和优化:通过编写插件,可以实现对Webpack的功能扩展和优化,例如资源压缩、代码分割、模块分析、自定义输出等。

编写一个Webpack插件通常需要以下步骤:

  1. 创建一个JavaScript类或函数,该类或函数需要包含一个apply方法或者是一个符合Webpack插件规范的函数。

  2. apply方法中,可以访问Webpack提供的编译实例和资源,以及注册对应的生命周期钩子的处理函数。

  3. 在处理函数中,可以根据需要对资源进行修改或者添加自定义的构建逻辑。

举例来说,一个简单的Webpack插件可能会监听emit钩子,在每次构

12. 白屏怎么优化?

白屏问题通常指的是页面加载时长,用户看到的是空白的页面,而非期望的内容。以下是一些优化白屏问题的方法:

  1. 代码优化:减少不必要的代码、压缩和合并 JavaScript、CSS 和 HTML 文件,以减少页面加载时间。确保代码精简和高效加载。
  2. 图片优化:优化图片大小和格式,采用适当的压缩方式,以减少图片加载时间。延迟加载不必要的图片,避免一次性加载大量图片导致页面延迟。
  3. 异步加载:将不必要的资源加载延迟到页面初次渲染后再进行加载,通过异步加载脚本和资源文件,以加快页面加载速度。
  4. 代码分割:将页面所需的 JavaScript 代码分割为多个小块,并根据页面需要进行按需加载,以减少初始加载时间。
  5. 服务端渲染(SSR):对于需要较快的首屏加载的页面,可以考虑使用服务端渲染,以在服务器端生成完整的 HTML 页面,减少客户端渲染时间。
  6. 浏览器缓存:合理设置静态资源的缓存策略,利用浏览器缓存,减少重复请求,提高页面加载速度。
  7. 使用预加载和预渲染:通过预加载和预渲染关键资源和页面,以提前加载所需内容,加快页面呈现速度。
  8. 性能监控和分析:使用工具进行性能监控和分析,及时发现页面加载性能瓶颈,并进行优化。

通过综合运用这些方法,可以有效地优化白屏问题,加快页面加载速度,提升用户体验。

13.动画性能?

要提升动画性能,特别是在 Web 应用程序中,可以采取以下几个方法:

  1. 使用 CSS 动画:尽量使用 CSS 动画来实现动画效果,而不是使用 JavaScript 来操作 DOM 元素。CSS 动画通常比 JavaScript 动画性能更好,因为它们可以通过 GPU 加速来实现,从而减少 CPU 的负载。
  2. 使用 transform 和 opacity:在 CSS 动画中,尽量使用 transformopacity 属性来实现动画效果,因为这两个属性可以通过硬件加速来进行处理,从而提高动画的性能。
  3. 避免布局抖动:在进行动画设计时,尽量避免频繁地改变元素的布局属性(如宽度、高度、位置等),因为这样会导致页面的布局抖动,降低动画的流畅性。
  4. 使用 requestAnimationFrame:在 JavaScript 动画中,使用 requestAnimationFrame 来执行动画循环,这样可以让浏览器在下一次重绘之前执行动画更新,从而提高动画的性能和流畅度。
  5. 优化图片和视频:对于包含大量图片或视频的动画,可以优化图片和视频的大小和格式,以减少网络传输和解码的开销,从而提高动画的加载和播放性能。
  6. 减少重排和重绘:尽量减少对 DOM 结构和样式的修改,因为这会导致页面的重排和重绘,影响动画的性能。可以通过批量处理样式修改、使用文档片段等方式来减少重排和重绘的次数。
  7. 使用硬件加速:对于复杂的动画效果,可以考虑使用 CSS
  8. 脱离文档流对于如何优化动画,我们知道,一般情况下,动画需要频繁的操作 DOM,就就会导致页面的性能问题,我们可以将动画的 **position**属性设置为** absolute**或者** fixed**,将动画脱离文档流,这样他的回流就不会影响到页面了。

14. 渲染合成层?

简单来说,浏览器为了提升动画的性能,为了在动画的每一帧的过程中不必每次都重新绘制整个页面。在特定方式下可以触发生成一个合成层(Composite Layers),合成层拥有单独的 GraphicsLayer。

需要进行动画的元素包含在这个合成层之下,这样动画的每一帧只需要去重新绘制这个 GraphicsLayer 即可,从而达到提升动画性能的目的。

如果我们想尽可能的优化我们的 CSS 动画,或者在日常 CSS 开发中,尽可能的提升 CSS 的性能,这是一个非常重要的概念。通过生成独立的 GraphicsLayer,让此层内的重绘重排不引起整个页面的重绘重排。

这也就是我们常说的,CSS 3D 硬件加速的最本质的原因。

那么一个元素什么时候会触发创建一个 Graphics Layer 层?

从目前来说,满足以下任意情况便会创建层:

  • 硬件加速的 iframe 元素(比如 iframe 嵌入的页面中有合成层)

  • 硬件加速的插件,比如 flash 等等

  • 使用加速视频解码的

  • 3D 或者 硬件加速的 2D Canvas 元素

  • 3D 或透视变换(perspective、transform) 的 CSS 属性

  • 对自己的 opacity 做 CSS 动画或使用一个动画变换的元素

  • 拥有加速 CSS 过滤器的元素

  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)

  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素

    因此,通常最为常见的创建一个复合层的方式就是:

  • transform

  • opacity

  • filter(使用频率较低)

    我们可以通过合理的使用这几个元素,有效提升页面的性能,譬如使用 transform 代替 left、top,实现位移动画。

什么是 GPU 渲染加速?

GPU 渲染加速除了与上面的内容紧密相连外,还需要了解另外一个非常有意思的属性 -- will-change。 根据 MDN,属性 will-change 为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。 譬如:

.sidebar {
will-change: transform;
}

这会使声明了该样式属性的元素生成一个图形层,告诉浏览器接下来该元素将会进行 transform 变换,让浏览器提前做好准备。

是的,这个属性是 CSS 中,除了 transform: translateZ(0) 外,另外一种强制开启硬件加速的方式。 通常,我们有两种,强制可以让元素开启硬件加速的方式。 1.transform: translateZ(0) 2.will-change: transform; 如何更好的理解硬件加速?我尝试讲讲我的理解。 当我们使用 transform 和 opacity 对元素进行动画时,浏览会尝试优化这段动画。浏览器将所有内容作为纹理(texture)传输到 GPU,而不是对每一帧上的像素进行光栅化。GPU 非常擅长执行此类基于纹理的转换,因此,我们得到了非常流畅、高性能的动画,这称为“硬件加速”。 而有趣的是,GPU 和 CPU 的对页面的渲染效果略有不同。当 CPU 将渲染任务交给 GPU 时,我们有时会看到页面动画会有一些轻微的变化。(譬如动画的抖动) 而此时,will-change 就非常适合成为 GPU 和 CPU 之间的桥梁。通过 will-change 属性,提前告知浏览器,让浏览器提前做好准备。 当然,通过将元素的渲染委托给 GPU,它将消耗更多的内存资源,而这种资源是有限的,尤其是在低端移动设备上。 因此,想使用好 will-change 属性并不是很容易:

是的,这个属性是 CSS 中,除了 transform: translateZ(0) 外,另外一种强制开启硬件加速的方式。

通常,我们有两种,强制可以让元素开启硬件加速的方式。

1transform: translateZ(0)

2will-change: transform;

如何更好的理解硬件加速?我尝试讲讲我的理解。

当我们使用 transform 和 opacity 对元素进行动画时,浏览会尝试优化这段动画。浏览器将所有内容作为纹理(texture)传输到 GPU,而不是对每一帧上的像素进行光栅化。GPU 非常擅长执行此类基于纹理的转换,因此,我们得到了非常流畅、高性能的动画,这称为“硬件加速”。

而有趣的是,GPU 和 CPU 的对页面的渲染效果略有不同。当 CPU 将渲染任务交给 GPU 时,我们有时会看到页面动画会有一些轻微的变化。(譬如动画的抖动)

而此时,will-change 就非常适合成为 GPU 和 CPU 之间的桥梁。通过 will-change 属性,提前告知浏览器,让浏览器提前做好准备。

当然,通过将元素的渲染委托给 GPU,它将消耗更多的内存资源,而这种资源是有限的,尤其是在低端移动设备上。

因此,想使用好 will-change 属性并不是很容易:

  • 在一些低端设备上,will-change 会导致很多小问题,譬如会使图片模糊,有的时候很容易适得其反,所以使用的时候还需要多加测试。
  • 不要将 will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。
  • 有节制地使用:通常,当元素恢复到初始状态时,浏览器会丢弃掉之前做的优化工作。但是如果直接在样式表中显式声明了 will-change 属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值。
  • 不要过早应用 will-change 优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致生成大量图层,进而导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程,这会导致更严重的性能问题。
  • 给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上 will-change 属性。

15.tree shaking 是什么,有什么作用,原理是什么?

Tree shaking 是一种用于优化 JavaScript 模块的技术,其主要作用是在打包过程中去除未被引用的代码,以减少最终生成的包的体积。Tree shaking 技术的原理是基于 ES6 模块的静态结构特性,通过静态分析代码的引用关系,识别并去除未被引用的代码。

Tree shaking 的作用主要包括:

  1. 减少包体积:通过去除未被引用的代码,可以减少最终生成的包的体积,从而提高应用程序的加载速度和运行性能。
  2. 优化资源利用:去除未被引用的代码可以减少资源的加载和解析时间,提高资源的利用效率。

Tree shaking 的原理主要包括以下步骤:

  1. 静态分析:Tree shaking 通过静态分析代码的引用关系来识别未被引用的代码。在 ES6 模块中,模块的导入和导出是静态的,这意味着模块的导入关系在代码编译阶段就已经确定,因此可以通过静态分析来确定代码的引用关系。
  2. 标记未引用代码:在静态分析的基础上,Tree shaking 会标记未被引用的代码,并将其标记为“未使用”。
  3. 去除未引用代码:在标记未引用代码后,Tree shaking 工具会在打包过程中去除这些被标记的未使用代码,从而实现包的精简和优化。

在实际的应用中,Tree shaking 技术通常与模块打包工具(如 Webpack、Rollup 等)结合使用,通过工具提供的 Tree shaking 功能来实现未被引用

16. 前端微服务?

前端微服务是指将前端应用程序拆分为多个小型、独立的服务单元,每个单元负责特定的功能或页面。这种架构模式类似于后端微服务架构,但是应用于前端开发领域。

前端微服务的特点包括:

  1. 独立部署和维护:每个前端微服务都可以独立部署和维护,降低了系统的耦合性,使得团队可以更加灵活地进行开发、部署和维护。

  2. 技术栈多样性:不同的前端微服务可以使用不同的技术栈,例如React、Vue、Angular等,这使得团队可以根据需求选择最适合的技术栈,而无需受限于整个应用的统一技术选型。

  3. 增量升级和扩展:可以针对某个特定的前端微服务进行增量升级和扩展,而不会对整个应用产生影响。

  4. 性能优化:前端微服务可以根据需要进行按需加载,从而提高页面加载性能和用户体验。

  5. 团队协作:不同的团队可以负责不同的前端微服务,有利于团队的分工协作和聚焦特定业务领域。

前端微服务的实现可以借助于现代的前端框架和技术,如Web Components、Webpack模块联邦、微前端架构等。然而,前端微服务也需要面对一些挑战,如跨微服务通信、路由管理、共享状态管理等问题,需要谨慎设计和实施。

17. CDN 的概念

CDN(Content Delivery Network,**内容分发网络**)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。

典型的 CDN 系统由下面三个部分组成:

  • 分发服务系统:**最基本的工作单元就是 Cache 设备,cache(边缘 cache)负责直接响应最终用户的访问请求,把缓存在本地的内容快速地提供给用户。同时 cache 还负责与源站点进行内容同步,把更新的内容以及本地没有的内容从源站点获取并保存在本地。Cache 设备的数量、规模、总服务能力是衡量一个 CDN 系统服务能力的最基本的指标。**
  • 负载均衡系统:**主要功能是负责对所有发起服务请求的用户进行访问调度,确定提供给用户的最终实际访问地址。两级调度体系分为全局负载均衡(GSLB)和本地负载均衡(SLB)。**全局负载均衡**主要根据用户就近性原则,通过对每个服务节点进行“最优”判断,确定向用户提供服务的 cache 的物理位置。**本地负载均衡**主要负责节点内部的设备负载均衡**
  • 运营管理系统:**运营管理系统分为运营管理和网络管理子系统,负责处理业务层面的与外界系统交互所必须的收集、整理、交付工作,包含客户管理、产品管理、计费管理、统计分析等功能。**

18.CDN 的作用

CDN 一般会用来托管 Web 资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用 CDN 来加速这些资源的访问。

(1)在性能方面,引入 CDN 的作用在于:

  • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
  • 部分资源请求分配给了 CDN,减少了服务器的负载

(2)在安全方面,CDN 有助于防御 DDoS、MITM 等网络攻击:

  • 针对 DDoS:通过监控分析异常流量,限制其请求频率
  • 针对 MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信

除此之外,CDN 作为一种基础的云服务,同样具有资源托管、按需扩展(能够应对流量高峰)等方面的优势。

19. CDN 的原理

CDN 的工作原理:

(1)用户未使用 CDN 缓存资源的过程:

  1. 浏览器通过 DNS 对域名进行解析(就是上面的 DNS 解析过程),依次得到此域名对应的 IP 地址
  2. 浏览器根据得到的 IP 地址,向域名的服务主机发送数据请求
  3. 服务器向浏览器返回响应数据

(2)用户使用 CDN 缓存资源的过程:

  1. 对于点击的数据的 URL,经过本地 DNS 系统的解析,发现该 URL 对应的是一个 CDN 专用的 DNS 服务器,DNS 系统就会将域名解析权交给 CNAME 指向的 CDN 专用的 DNS 服务器。
  2. CND 专用 DNS 服务器将 CND 的全局负载均衡设备 IP 地址返回给用户
  3. 用户向 CDN 的全局负载均衡设备发起数据请求
  4. CDN 的全局负载均衡设备根据用户的 IP 地址,以及用户请求的内容 URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
  5. 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的 IP 地址返回给全局负载均衡设备
  6. 全局负载均衡设备把服务器的 IP 地址返回给用户
  7. 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端。

如果缓存服务器没有用户想要的内容,那么缓存服务器就会向它的上一级缓存服务器请求内容,以此类推,直到获取到需要的资源。最后如果还是没有,就会回到自己的服务器去获取资源。

CNAME(意为:别名):在域名解析中,实际上解析出来的指定域名对应的 IP 地址,或者该域名的一个 CNAME,然后再根据这个 CNAME 来查找对应的 IP 地址。

20、CDN 的使用场景

  • 使用第三方的 CDN 服务:**如果想要开源一些项目,可以使用第三方的 CDN 服务**
  • 使用 CDN 进行静态资源的缓存:**将自己网站的静态资源放在 CDN 上,比如 js、css、图片等。可以将整个项目放在 CDN 上,完成一键部署。**
  • 直播传送:**直播本质上是使用流媒体进行传送,CDN 也是支持流媒体传送的,所以直播完全可以使用 CDN 来提高访问速度。CDN 在处理流媒体的时候与处理普通静态文件有所不同,普通文件如果在边缘节点没有找到的话,就会去上一层接着寻找,但是流媒体本身数据量就非常大,如果使用回源的方式,必然会带来性能问题,所以流媒体一般采用的都是主动推送的方式来进行。**

21. 懒加载的概念

懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。

如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。

22.懒加载的特点

  • 减少无用资源的加载:**使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担。**
  • 提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而使用懒加载就能大大的提高用户体验。
  • 防止加载过多图片而影响其他资源文件的加载 :**会影响网站应用的正常使用。**

23. 懒加载的实现原理

  1. 通过 onscroll 事件与 getBoundingClientRect API 实现图片的懒加载方案
  2. 通过 Intersection Observer(交叉观察器)实现比监听 onscroll 性能更佳的图片懒加载方案
  3. 通过 content-visibility: auto 实现图片资源的延迟渲染
  4. 通过 loading=lazy HTML 属性实现图片懒加载
  5. 通过 decoding=async HTML 属性实现图片的异步解码
  6. 使用js去计算是否达到可视区域

图片的加载是由 src**引起的,当对** src**赋值时,浏览器就会请求图片资源。根据这个原理,我们使用 HTML5 的** data-xxx**属性来储存图片的路径,在需要加载图片的时候,将** data-xxx**中图片的路径赋值给** src**,这样就实现了图片的按需加载,即懒加载。**

注意:**data-xxx** 中的 xxx**可以自定义,这里我们使用** data-src**来定义。**

懒加载的实现重点在于确定用户需要加载哪张图片,在浏览器中,可视区域内的资源就是用户需要的资源。所以当图片出现在可视区域时,获取图片的真实地址并赋值给图片即可。

使用原生 JavaScript 实现懒加载:

知识点:

(1)**window.innerHeight** 是浏览器可视区的高度 document.documentElement.clientHeight

(2)**document.body.scrollTop || document.documentElement.scrollTop** 是浏览器滚动的过的距离

(3)**imgs.offsetTop** 是元素顶部距离文档顶部的高度(包括滚动条的距离)

(4)图片加载条件:**img.offsetTop < window.innerHeight + document.body.scrollTop;**

图示:

代码实现:

<div class="container">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winHeight= window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winHeight ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
window.onscroll = lozyLoad();
</script>

24. 懒加载与预加载的区别

这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

  • 懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载**,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。**
  • 预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。**通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。**

25. 回流与重绘的概念及触发条件

1)回流

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为**回流**。

下面这些操作会导致回流:

  • 页面的首次渲染
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸或者位置发生变化
  • 元素的字体大小发生变化
  • 激活 CSS 伪类
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的 DOM 元素

在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的 DOM 元素重新排列,它的影响范围有两种:

  • 全局范围:从根节点开始,对整个渲染树进行重新布局
  • 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局

(2)重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是**重绘**。

下面这些操作会导致回流:

  • color、background 相关属性:background-color、background-image 等
  • outline 相关属性:outline-color、outline-width 、text-decoration
  • border-radius、visibility、box-shadow

注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

26. 如何避免回流与重绘?

减少回流与重绘的措施:

  • 操作 DOM 时,尽量在低层级的 DOM 节点进行操作
  • 不要使用 table**布局, 一个小的改动可能会使整个** table**进行重新布局**
  • 使用 CSS 的表达式
  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
  • 使用 absolute 或者 fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  • 避免频繁操作 DOM,可以创建一个文档片段 documentFragment**,在它上面应用所有 DOM 操作,最后再把它添加到文档中**
  • 将元素先设置 display: none**,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。**
  • 将 DOM 的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于**浏览器的渲染队列机制**。

浏览器针对页面的回流与重绘,进行了自身的优化——**渲染队列**

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

上面,将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。

27.documentFragment 是什么?用它跟直接操作 DOM 的区别是什么?

MDN 中对 documentFragment**的解释:**

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 使用,就像标准的 document 一样,存储由节点(nodes)组成的文档结构。与 document 相比,最大的区别是 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的 DOM 操作时,我们就可以将 DOM 元素插入 DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作 DOM 相比,将 DocumentFragment 节点插入 DOM 树时,不会触发页面的重绘,这样就大大提高了页面的性能。

28.对节流与防抖的理解

  • 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
  • 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

防抖函数的应用场景:

  • 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次
  • 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤ lodash.debounce

节流函数的适⽤场景:

  • 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动
  • 缩放场景:监控浏览器 resize
  • 动画场景:避免短时间内多次触发动画引起性能问题

29.实现节流函数和防抖函数

30.如何对项目中的图片进行优化?

  1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
  2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:
  • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
  • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
  • 照片使用 JPEG

31.常见的图片格式及使用场景

(1)**BMP**是无损的、既支持索引色也支持直接色的点阵图。这种图片格式几乎没有对数据进行压缩,所以 BMP 格式的图片通常是较大的文件。

(2)**GIF**是无损的、采用索引色的点阵图。采用 LZW 压缩算法进行编码。文件小,是 GIF 格式的优点,同时,GIF 格式还具有支持动画以及透明的优点。但是 GIF 格式仅支持 8bit 的索引色,所以 GIF 格式适用于对色彩要求不高同时需要文件体积较小的场景。

(3)**JPEG**是有损的、采用直接色的点阵图。JPEG 的图片的优点是采用了直接色,得益于更丰富的色彩,JPEG 非常适合用来存储照片,与 GIF 相比,JPEG 不适合用来存储企业 Logo、线框类的图。因为有损压缩会导致图片模糊,而直接色的选用,又会导致图片文件较 GIF 更大。

(4)**PNG-8**是无损的、使用索引色的点阵图。PNG 是一种比较新的图片格式,PNG-8 是非常好的 GIF 格式替代者,在可能的情况下,应该尽可能的使用 PNG-8 而不是 GIF,因为在相同的图片效果下,PNG-8 具有更小的文件体积。除此之外,PNG-8 还支持透明度的调节,而 GIF 并不支持。除非需要动画的支持,否则没有理由使用 GIF 而不是 PNG-8。

(5)**PNG-24**是无损的、使用直接色的点阵图。PNG-24 的优点在于它压缩了图片的数据,使得同样效果的图片,PNG-24 格式的文件大小要比 BMP 小得多。当然,PNG24 的图片还是要比 JPEG、GIF、PNG-8 大得多。

(6)**SVG**是无损的矢量图。SVG 是矢量图意味着 SVG 图片由直线和曲线以及绘制它们的方法组成。当放大 SVG 图片时,看到的还是线和曲线,而不会出现像素点。这意味着 SVG 图片在放大时,不会失真,所以它非常适合用来绘制 Logo、Icon 等。

(7)**WebP**是谷歌开发的一种新图片格式,WebP 是同时支持有损和无损压缩的、使用直接色的点阵图。从名字就可以看出来它是为 Web 而生的,什么叫为 Web 而生呢?就是说相同质量的图片,WebP 具有更小的文件体积。现在网站上充满了大量的图片,如果能够降低每一个图片的文件大小,那么将大大减少浏览器和服务器之间的数据传输量,进而降低访问延迟,提升访问体验。目前只有 Chrome 浏览器和 Opera 浏览器支持 WebP 格式,兼容性不太好。

  • 在无损压缩的情况下,相同质量的 WebP 图片,文件大小要比 PNG 小 26%;
  • 在有损压缩的情况下,具有相同图片精度的 WebP 图片,文件大小要比 JPEG 小 25%~34%;
  • WebP 图片格式支持图片透明度,一个无损压缩的 WebP 图片,如果要支持透明度只需要 22%的格外文件大小。

32.如何提⾼ webpack 的打包速度?

(1)优化 Loader

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,**转换代码越多,效率就越低**。当然了,这是可以优化的。

首先我们**优化 Loader 的文件搜索范围**

module.exports = {
module: {
rules: [
{
// js 文件才使用 babel
test: /\.js$/,
loader: "babel-loader",
// 只在 src 文件夹下查找
include: [resolve("src")],
// 不会去查找的路径
exclude: /node_modules/,
},
],
},
}

对于 Babel 来说,希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。

当然这样做还不够,还可以将 Babel 编译过的文件**缓存**起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

HappyPack 可以将 Loader 的同步执行转换为并行的**,这样就能充分利用系统资源来加快打包效率了**

module: {
loaders: [
{
test: /\.js$/,
include: [resolve('src')],
exclude: /node_modules/,
// id 后面的内容对应下面
loader: 'happypack/loader?id=happybabel'
}
]
},
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory'],
// 开启 4 个线程
threads: 4
})
]

(3)DllPlugin

DllPlugin 可以将特定的类库提前打包然后引入**。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。DllPlugin 的使用方法如下:**

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require("path")
const webpack = require("webpack")
module.exports = {
entry: {
// 想统一打包的类库
vendor: ["react"],
},
output: {
path: path.join(__dirname, "dist"),
filename: "[name].dll.js",
library: "[name]-[hash]",
},
plugins: [
new webpack.DllPlugin({
// name 必须和 output.library 一致
name: "[name]-[hash]",
// 该属性需要与 DllReferencePlugin 中一致
context: __dirname,
path: path.join(__dirname, "dist", "[name]-manifest.json"),
}),
],
}

然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中

// webpack.conf.js
module.exports = {
// ...省略其他配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest 就是之前打包出来的 json 文件
manifest: require("./dist/vendor-manifest.json"),
}),
],
}

(4)代码压缩

在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS**,从而提高效率。**

在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

(5)其他

可以通过一些小的优化点来加快打包速度

  • resolve.extensions**:用来表明文件后缀列表,默认查找顺序是** ['.js', '.json']**,如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面**
  • resolve.alias**:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径**
  • module.noParse**:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助**

33.如何减少 Webpack 打包体积

(1)按需加载

在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,**这时候就可以使用按需加载,将每个路由页面单独打包为一个文件**。当然不仅仅路由可以按需加载,对于 loadash 这种大型类库同样可以使用这个功能。

按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个 Promise**,当** Promise 成功以后去执行回调。

(2)Scope Hoisting

Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。

比如希望打包两个文件:

// test.js
export const a = 1
// index.js
import { a } from './test.js'

对于这种情况,打包出来的代码会类似这样:

[
/* 0 */
function (module, exports, require) {
//...
},
/* 1 */
function (module, exports, require) {
//...
}
]

但是如果使用 Scope Hoisting ,代码就会尽可能的合并到一个函数中去,也就变成了这样的类似代码:

[
/* 0 */
function (module, exports, require) {
//...
}
]

这样的打包方式生成的代码明显比之前的少多了。如果在 Webpack4 中你希望开启这个功能,只需要启用 optimization.concatenateModules 就可以了:

module.exports = {
optimization: {
concatenateModules: true
}
}

(3)Tree Shaking

Tree Shaking 可以实现删除项目中未被引用的代码**,比如:**

// test.js
export const a = 1
export const b = 2
// index.js
import { a } from './test.js'

对于以上情况,**test** 文件中的变量 b 如果没有在项目中使用到的话,就不会被打包到文件中。

如果使用 Webpack 4 的话,开启生产环境就会自动启动这个优化功能。

34.浏览器渲染优化

  • script 立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行 js 代码,js 代码执行完毕后继续渲染页面;
  • async 是在下载完成之后,立即异步加载,加载好后立即执行,多个带 async 属性的标签,不能保证加载的顺序;
  • defer 是在下载完成之后,立即异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果 DOM 树已经准备好,则立即执行。多个带 defer 属性的标签,按照顺序执行。

(2)针对 CSS:******使用 CSS 有三种方式:使用********link、@import、内联样式**,其中 link 和@import 都是导入外部样式。它们之间的区别:**

  • link**:浏览器会派发一个新等线程(HTTP 线程)去加载资源文件,与此同时 GUI 渲染线程会继续向下渲染代码**
  • @import**:GUI 渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)**
  • style**:GUI 直接渲染**

外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以 CSS 一般写在 headr 中,让浏览器尽快发送请求去获取 css 样式。

所以,在开发过程中,导入外部样式使用 link,而不用@import。如果 css 少,尽可能采用内嵌样式,直接写在 style 标签中。

(3)针对 DOM 树、CSSOM 树:

可以通过以下几种方式来减少渲染的时间:

  • HTML 文件的代码层级尽量不要太深
  • 使用语义化的标签,来避免不标准语义化的特殊处理
  • 减少 CSSD 代码的层级,因为选择器是从右向左进行解析的

(4)减少回流与重绘:

  • 操作 DOM 时,尽量在低层级的 DOM 节点进行操作
  • 不要使用 table**布局, 一个小的改动可能会使整个** table**进行重新布局**
  • 使用 CSS 的表达式
  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
  • 使用 absolute 或者 fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  • 避免频繁操作 DOM,可以创建一个文档片段 documentFragment**,在它上面应用所有 DOM 操作,最后再把它添加到文档中**
  • 将元素先设置 display: none**,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。**
  • 将 DOM 的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于**浏览器的渲染队列机制**。

浏览器针对页面的回流与重绘,进行了自身的优化——**渲染队列**

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。

35.Webpack vs Rollup 的区同点

Webpack 和 Rollup 都是前端常用的模块打包工具,它们在项目构建和打包方面有一些区别和特点:

  1. 目标不同:

    • Webpack 的主要目标是解决前端开发中的一切资源(包括 JavaScript 模块、样式、图片、字体等)的打包和构建问题,它非常适合构建复杂的 Web 应用程序。
    • Rollup 的主要目标是专注于 JavaScript 模块的打包,特别是 ES6 模块。Rollup 的特点是能够进行 Tree-shaking,即只打包代码中实际使用的部分,去除未使用的代码,生成更小、更高效的代码包。
  2. 生态和插件系统:

    • Webpack 拥有非常丰富的插件系统和生态圈,可以处理各种类型的资源文件,支持各种开发场景的定制化。
    • Rollup 的插件系统相对较简单,主要关注于 JavaScript 模块的处理和优化,对于纯 JavaScript 模块的打包效果更加突出。
  3. 代码拆分和动态加载:

    • Webpack 在处理代码拆分和动态加载方面非常灵活,可以通过代码拆分和按需加载来优化应用程序的性能和加载速度。
    • Rollup 也支持代码拆分,但相对于 Webpack 来说,其主要关注点是生成更小、更高效的代码包,特别适合于库和工具的打包。
  4. 社区和使用场景:

    • Webpack 在前端开发社区中拥有更广泛的应用和使用场景,特别适合构建复杂的现代 Web 应用程序。
    • Rollup 在一些库和工具的开发中比较流行,特别是一些 CLI 工具或命令行工具的开发中,也会选择使用 Rollup 进行打包

36.如何理解前端工程化

前端工程化是指通过工程化的方法和工具来提高前端开发效率、降低维护成本、提升项目质量的一种开发模式。前端工程化包括了项目构建、模块化开发、自动化测试、持续集成、部署流程等一系列技术和实践的整合,旨在优化前端开发过程和项目管理。

以下是前端工程化的一些核心概念和实践:

  1. 自动化构建:使用构建工具(如Webpack、Rollup等)对前端资源进行打包、压缩、合并、编译等处理,减少手动操作,提高开发效率。

  2. 模块化开发:采用模块化的开发方式(如ES6 模块、CommonJS、AMD等),将代码分割成独立的模块,提高代码复用性和可维护性。

  3. 自动化测试:编写单元测试、集成测试和端到端测试,并通过自动化测试工具(如Jest、Mocha、Selenium等)进行自动化测试,确保代码质量和稳定性。

  4. 代码规范和静态检查:通过工具(如ESLint、Prettier等)对代码进行规范检查、格式化和静态分析,保证代码风格一致,并发现潜在的问题。

  5. 版本控制和协作:使用版本控制系统(如Git)进行代码管理,并结合协作工具(如GitHub、GitLab等)进行团队协作和代码审查。

  6. 持续集成和持续部署:建立持续集成(CI)和持续部署(CD)流程,通过自动化工具(如Jenkins、Travis CI等)进行自动化构建

37.如何理解 Monorepo

Monorepo 是指在单个版本库(repository)中管理多个项目或组件的代码。通常,每个项目或组件都有自己的目录结构,但它们共享相同的版本控制和构建流程。Monorepo 提供了一种集中式的代码管理方式,可以用于管理大型项目或者跨项目共享代码的情况。

在理解 Monorepo 时,需要考虑以下几个方面:

  1. 代码共享:Monorepo 允许不同项目或组件之间共享代码,可以减少重复代码的编写,提高代码的复用性。共享代码可以是通用的工具类、组件库,或者公共的配置文件等。

  2. 统一版本控制:所有项目或组件都受到相同的版本控制管理,这意味着可以更容易地进行跨项目的代码协作、版本发布和回溯操作。

  3. 统一构建流程:Monorepo 可以使用统一的构建工具和流程来管理所有项目或组件的构建过程,这样可以确保构建的一致性和可靠性。

  4. 依赖管理:Monorepo 可以更好地管理项目之间的依赖关系,可以通过统一的依赖管理工具来管理所有项目的依赖,避免依赖冲突和版本不一致的问题。

  5. 整体一致性:Monorepo 可以提供更好的整体一致性,可以更容易地进行全局的代码重构、格式化和代码规范的统一。

需要注意的是,尽管 Monorepo 提供了一种集中式的代码管理方式,但在实际使用时需要考虑到代码量、团队规模、构建性能等因素,以及合适的工具和流程来支持 Monorepo 的

38.除了 Webpack,你还了解哪些构件工具

除了Webpack,目前市面上还有一些其他流行的构建工具,它们在前端开发中也扮演着重要的角色。以下是一些常见的构建工具:

  1. Parcel:Parcel 是一个快速、零配置的 Web 应用程序打包工具。它支持零配置并且能够自动处理各种类型的文件,包括 JavaScript、CSS、HTML、图像等。Parcel 通过并行化处理和缓存来提供快速的构建速度。
  2. Rollup:Rollup 是一个 JavaScript 模块打包器,专注于构建 ES6 模块。Rollup 的特点是能够进行 Tree-shaking,即只打包代码中实际使用的部分,去除未使用的代码,生成更小、更高效的代码包。
  3. Gulp:Gulp 是一个基于流的自动化构建工具,它可以帮助开发者完成诸如文件压缩、文件合并、代码检查等任务。Gulp 使用简单的 API 和管道(pipe)来实现构建任务的自动化。
  4. Grunt:Grunt 是一个 JavaScript 任务运行器,它能够自动执行常见的前端开发任务。通过配置任务列表,Grunt 可以帮助开发者完成文件压缩、文件合并、代码检查等任务。
  5. Brunch:Brunch 是一个快速的前端构建工具,它支持快速原型开发和生产环境构建。Brunch 的特点是配置简单,能够自动检测文件变化并重新构建项目。

这些构建工具各有特点,开发者可以根据项目需求和个人偏好选择适合的构建工具,以提高开发效率和优化项目构建流程。

39.SPA 首屏为什么加载慢

单页面应用(Single Page Application,SPA)的首屏加载慢可能由以下原因导致:

  1. 大量 JavaScript 和 CSS 文件加载慢:SPA通常有很多 JavaScript 和 CSS 文件,如果这些文件的大小过大或加载速度慢,就会导致首屏加载缓慢。可以通过代码分割和打包、使用CDN等方式来优化加载速度。
  2. 数据请求过多或数据请求太慢:SPA通过 AJAX 或 Fetch 等方式从后端获取数据,如果数据请求过多或数据请求太慢,也会导致首屏加载缓慢。可以通过减少数据请求、使用数据缓存、优化数据接口等方式来优化数据请求速度。
  3. 大量图片加载慢:如果首屏需要加载大量图片,而这些图片大小过大或加载速度慢,也会导致首屏加载缓慢。可以通过图片压缩、使用图片懒加载等方式来优化图片加载速度。
  4. 过多的渲染和重绘操作:如果在首屏加载时进行大量的渲染和重绘操作,也会导致首屏加载缓慢。可以通过尽可能少的DOM操作、使用CSS3动画代替JS动画等方式来优化渲染和重绘操作。
  5. 网络问题:网络问题也会影响SPA首屏加载速度,比如网络延迟、丢包等。可以通过使用CDN、使用HTTP/2等方式来优化网络问题。

除了上述方案外,还可以:

  1. 使用 SSR 渲染首屏内容,但注意服务器负载情况;

综上所述,SPA首屏加载缓慢可能是多种原因共同导致的,需要综合考虑并采取相应的优化措施。

40.前端展示大批量图片如何优化

解决方案 前端展示大量图片可能会导致页面加载变慢,因此需要进行优化。以下是几种优化方法: 图片懒加载:图片懒加载是指在页面滚动到需要展示图片的位置时,才开始加载图片,可以减少页面首次加载时间和带宽占用。常见的懒加载实现方式包括Intersection Observer API和jQuery.lazyload等。 图片压缩:将图片压缩至适当的尺寸和质量,可以减少图片大小和下载时间。常见的图片压缩工具包括TinyPNG和ImageOptim等。 CDN加速:使用CDN可以将图片分发到全球的服务器节点,减少网络延迟和带宽占用,加速图片加载。常见的CDN服务提供商包括阿里云、腾讯云和七牛云等。 WebP格式:WebP是一种由Google开发的图片格式,它可以将图片压缩至更小的尺寸,同时保持较高的质量和透明度支持。在支持WebP格式的浏览器中,可以将原来的JPEG或PNG图片替换成WebP格式的图片,以加速图片加载。 懒加载占位符:在页面中使用懒加载占位符,可以在图片未加载时,展示一张占位符图片或背景颜色,以保证页面布局的稳定性和用户体验。 以上是常见的前端展示大批量图片的优化方法,可以根据具体情况进行选择和组合。

为什么会变慢? 1、网络请求变多,HTTP1.x 下会触发浏览器并发连接限制,出现请求排队的情况,最终完整的加载时间流变成串行形式;即使在 H2,也可能挤占 tcp 链路,受制于网络带宽; 2、很多 img 元素没有声明宽高,需要浏览器加载回来之后才能计算出具体的面积,此时会触发页面回流,影响 FPS; PS: 现代计算设备中,图片编解码一般都已经是硬解了,从解码到渲染的时间复杂度是比较低的,一般不会造成时间维度的性能卡点(不过大量图片还是会占用较多内存 & GPU 资源);

41.如何理解 RAIL 性能指标模型

RAIL => response/animation/idle/load

●response:在 100 毫秒内完成由用户输入发起的转换,让用户感觉交互是即时的。

○ 交互(点击/输入/触屏)后立即(100ms)得到响应

○ 如果无法立即作出响应,也应该提供加载状态等中间态

●animation:

○ 目标为流畅的视觉效果。用户会注意到帧速率的变化,理想情况下应保持做 60fps

○ 在 10 毫秒或更短的时间内生成动画的每一帧。从技术上来讲,每帧的最大预算为 16 毫秒(1000 毫秒/每秒 60 帧 ≈16 毫秒),但是,浏览器需要大约 6 毫秒来渲染一帧,因此,准则为每帧 10 毫秒。

○ 视觉动画/滚动(虚拟滚动)/拖拽

●idle:最大限度增加空闲时间以提高页面在 50 毫秒内响应用户输入的几率

●load:

○ 根据用户的设备和网络能力优化相关的快速加载性能。目前,对于首次加载,在使用速度较慢 3G 连接的中端移动设备上,理想的目标是在 5 秒或更短的时间实现可交互

○ 对于后续加载,理想的目标是在 2 秒内加载页面。

image.png

怎么优化

Rail 模型提供了关于性能思考等方法论,我们不可能把时间都花在性能优化上,只能尽量把时间花在刀刃上,所以需要有一个性能模型,告诉我们该尽量优化那些方面,我们需要追求各个维度都尽量满足 RAIL 阈值

●response:

○ 事件处理函数在 50ms 内完成,考虑到 idle task 的情况,事件会排队,等待时间大概在 50ms。适用于 click,toggle,starting animations 等,不适用于 drag 和 scroll。

○ 复杂的 js 计算尽可能放在后台,如 web worker/settime,避免对用户输入造成阻塞

○ 超过 50ms 的响应,一定要提供反馈,比如倒计时,进度百分比等。

●animation

○ 在一些高压点上,比如动画,不要去挑战 cpu,尽可能地少做事,如:取 offset,设置 style 等操作。尽可能地保证 60 帧的体验。

○ 在渲染性能上,针对不同的动画做一些特定优化

○ 优化滚动效率,如虚拟滚动

●idle

○ 用空闲时间来完成一些延后的工作,如先加载页面可见的部分,然后利用空闲时间加载剩余部分,此处可以使用 requestIdleCallback API

○ 在空闲时间内执行的任务尽量控制在 50ms 以内,如果更长的话,会影响 input handle 的 pending 时间

○ 如果用户在空闲时间任务进行时进行交互,必须以此为最高优先级,并暂停空闲时间的任务

● load

○ 在手机设备上测试加载性能,选用中配的 3G 网络(400kb/s,400ms RTT),可以使用 WebPageTest 来测试

○ 要注意的是,即使用户的网络是 4G,但因为丢包或者网络波动,可能会比预期的更慢

禁用渲染阻塞的资源,延后加载,async/defer

○ 可以采用 lazy loadcode-splitting其他优化 手段,让第一次加载的资源更少

资料:● https://web.dev/i18n/zh/rail/

42.Vue的性能优化有哪些

(1)编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
  • v-if和v-for不能连用
  • 如果需要使用v-for给每项元素绑定事件时使用事件代理
  • SPA 页面采用keep-alive缓存组件
  • 在更多的情况下,使用v-if替代v-show
  • key保证唯一
  • 使用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动态加载
  • 图片懒加载

(2)SEO优化

  • 预渲染
  • 服务端渲染SSR

(3)打包优化

  • 压缩代码
  • Tree Shaking/Scope Hoisting
  • 使用cdn加载第三方模块
  • 多线程打包happypack
  • splitChunks抽离公共文件
  • sourceMap优化

(4)用户体验

  • 骨架屏
  • PWA
  • 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM;但 Vue 项目中仍然存在项目首屏优化、Webpack 编译配置优化等问题,所以我们仍然需要去关注 Vue 项目性能方面的优化,使项目具有更高效的性能、更好的用户体验。本文是作者通过实际项目的优化实践进行总结而来,希望读者读完本文,有一定的启发思考,从而对自己的项目进行优化起到帮助。本文内容分为以下三部分组成:

一、代码层面的优化

1.1、v-if 和 v-show 区分使用场景 v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换。 所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。 1.2、computed 和 watch 区分使用场景 computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值; watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作; 运用场景: 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算; 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。 1.3、v-for 遍历必须为 item 添加 key,且避免同时使用 v-if (1)v-for 遍历必须为 item 添加 key 在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff 。 (2)v-for 遍历避免同时使用 v-if v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性。 推荐:

<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}

不推荐:

<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>

1.4、长列表性能优化 Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};

1.5、事件的销毁 Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。如果在 js 内

created() {
addEventListener('click', this.click, false)
},
beforeDestroy() {
removeEventListener('click', this.click, false)
}

1.6、图片资源懒加载 对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 Vue 的 vue-lazyload 插件: (1)安装插件 npm install vue-lazyload --save-dev (2)在入口文件 man.js 中引入并使用 import VueLazyload from 'vue-lazyload' 然后再 vue 中直接使用 Vue.use(VueLazyload) 或者添加自定义选项

Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1
})

(3)在 vue 文件中将 img 标签的 src 属性直接改为 v-lazy ,从而将图片显示方式更改为懒加载显示。 1.7、路由懒加载 Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。 路由懒加载:

const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})

1.8、第三方插件的按需引入 我们在项目中经常会需要引入第三方插件,如果我们直接引入整个插件,会导致项目的体积太大,我们可以借助 babel-plugin-component ,然后可以只引入需要的组件,以达到减小项目体积的目的。以下为项目中引入 element-ui 组件库为例: (1)首先,安装 babel-plugin-component : npm install babel-plugin-component -D (2)然后,将 .babelrc 修改为:

{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

(3)在 main.js 中引入部分组件:

import Vue from 'vue';
import { Button, Select } from 'element-ui';

Vue.use(Button)
Vue.use(Select)

1.9、优化无限列表性能 如果你的应用存在非常长或者无限滚动的列表,那么需要采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。你可以参考以下开源项目 vue-virtual-scroll-list 和 vue-virtual-scroller 来优化这种无限列表的场景的。 1.10、服务端渲染 SSR or 预渲染 服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。 (1)服务端渲染的优点: 更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面; 更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间; (2)服务端渲染的缺点: 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境; 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。 如果你的项目的 SEO 和 首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO,具体的 Vue SSR 如何实现,可以参考作者的另一篇文章《Vue SSR 踩坑之旅》。如果你的 Vue 项目只需改善少数营销页面(例如 /, /about, /contact 等)的 SEO,那么你可能需要预渲染,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点,具体你可以使用 prerender-spa-plugin 就可以轻松地添加预渲染 。

二、Webpack 层面的优化


2.1、Webpack 对图片进行压缩 在 vue 项目中除了可以在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片: (1)首先,安装 image-webpack-loader : npm install image-webpack-loader --save-dev (2)然后,在 webpack.base.conf.js 中进行配置:

{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}

2.2、减少 ES6 转为 ES5 的冗余代码 Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,例如下面的 ES6 代码: class HelloWebpack extends Component{...} 这段代码再被转换成能正常运行的 ES5 代码时需要以下两个辅助函数:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits // 用于实现 extends 语法

在默认情况下, Babel 会在每个输出文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以在依赖它们时通过 require('babel-runtime/helpers/createClass') 的方式导入,这样就能做到只让它们出现一次。babel-plugin-transform-runtime 插件就是用来实现这个作用的,将相关辅助函数进行替换成导入语句,从而减小 babel 编译出来的代码的文件大小。 (1)首先,安装 babel-plugin-transform-runtime : npm install babel-plugin-transform-runtime --save-dev (2)然后,修改 .babelrc 配置文件为:

"plugins": [
"transform-runtime"
]

如果要看插件的更多详细内容,可以查看babel-plugin-transform-runtime 的 详细介绍。 2.3、提取公共代码 如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题: 相同的资源被重复加载,浪费用户的流量和服务器的成本。 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。 所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题 。Webpack 内置了专门用于提取多个Chunk 中的公共部分的插件 CommonsChunkPlugin,我们在项目中 CommonsChunkPlugin 的配置如下:

// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
);
}
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})

如果要看插件的更多详细内容,可以查看 CommonsChunkPlugin 的 详细介绍。 2.4、模板预编译 当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。 预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。 如果你使用 webpack,并且喜欢分离 JavaScript 和模板文件,你可以使用 vue-template-loader,它也可以在构建过程中把模板文件转换成为 JavaScript 渲染函数。 2.5、提取组件的 CSS 当使用单文件组件时,组件内的 CSS 会以 style 标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,如果你使用服务端渲染,这会导致一段 “无样式内容闪烁 (fouc) ” 。将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存。 查阅这个构建工具各自的文档来了解更多: webpack + vue-loader ( vue-cli 的 webpack 模板已经预先配置好) Browserify + vueify Rollup + rollup-plugin-vue 2.6、优化 SourceMap 我们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩、去掉多余的空格、babel编译化后,最终将编译得到的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有 bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发来说不好调式定位问题,因此 sourceMap 出现了,它就是为了解决不好调式代码问题的。 SourceMap 的可选值如下(+ 号越多,代表速度越快,- 号越多,代表速度越慢, o 代表中等速度 ) 开发环境推荐:cheap-module-eval-source-map 生产环境推荐:cheap-module-source-map 原因如下: cheap:源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加 cheap 的基本类型来忽略打包前后的列信息; module :不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个 Vue 文件报错了,我们希望能定位到具体的 Vue 文件,因此我们也需要 module 配置; soure-map :source-map 会为每一个打包后的模块生成独立的 soucemap 文件 ,因此我们需要增加source-map 属性; eval-source-map:eval 打包代码的速度非常快,因为它不生成 map 文件,但是可以对 eval 组合使用 eval-source-map 使用会将 map 文件以 DataURL 的形式存在打包后的 js 文件中。在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。 2.7、构建结果输出分析 Webpack 输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展示出来,让我们快速了解问题所在。接下来讲解我们在 Vue 项目中用到的分析工具:webpack-bundle-analyzer 。 我们在项目中 webpack.prod.conf.js 进行配置:

if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

执行 $ npm run build --report 后生成分析报告如下: 2.8、Vue 项目的编译优化 如果你的 Vue 项目使用 Webpack 编译,需要你喝一杯咖啡的时间,那么也许你需要对项目的 Webpack 配置进行优化,提高 Webpack 的构建效率。

三、基础的 Web 技术优化

3.1、开启 gzip 压缩 gzip 是 GNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持,gzip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右 以下我们以服务端使用我们熟悉的 express 为例,开启 gzip 非常简单,相关步骤如下: 安装: npm install compression --save 添加代码逻辑:

var compression = require('compression');
var app = express();
app.use(compression())

3.2、浏览器缓存 ​ 为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,对比缓存)。 ​ 3.3、CDN 的使用 ​ 浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率 。 ​ 3.4、使用 Chrome Performance 查找性能瓶颈 ​ Chrome 的 Performance 面板可以录制一段时间内的 js 执行细节及时间。使用 Chrome 开发者工具分析页面性能的步骤如下。

  1. 打开 Chrome 开发者工具,切换到 Performance 面板
  2. 点击 Record 开始录制
  3. 刷新页面或展开某个节点
  4. 点击 Stop 停止录制

43.详解 Vue 性能优化

Vue 相关的性能优化,大致可以分为:

  • 页面渲染加载性能优化
    • 避免将所有数据设置为响应式数据
    • 合理使用 computed 和 watch
    • 列表性能优化
    • 服务端渲染 SSR
  • 页面更新性能优化
    • 静态内容标记为只渲染一次(v-once)以及缓存模板的子树(v-memo)
    • 减少大型不可变数据的响应性开销
    • 给 v-for 添加 key 且避免同时使用 v-if
    • 合理使用 v-if 与 v-show
    • 使用 keep-alive 组件
  • **○**构建优化
    • 路由懒加载
    • 包体积优化与 Tree-shaking 优化
    • SourceMap 的处理

44.vite为什么比webpack快

运行原理

首先,我们从运行原理上分析一下,vite为什么比webpack快。

webpack运行原理

n1PETD

当我们使用webpack启动项目时,webpack会根据我们配置文件(webpack.config.js) 中的入口文件(entry),分析出项目项目所有依赖关系,然后打包成一个文件(bundle.js),交给浏览器去加载渲染。

这样就会带来一个问题,项目越大,需要打包的东西越多,启动时间越长。

关于ES module

在讲vite运行原理之前,我们先说一下ES module

目前,绝大多数现代浏览器都已经支持ES module了, 我们只需要在<script>标签中添加type="module",就可以使用ES module了。

下面这段代码是可以直接在浏览器中运行的。

javascript复制代码// test.js
export default function hello() {
console.log('hello world');
}

// index.html
<script type="module">
import hello from './test.js';

hello(); // hello world
</scirpt>

vite运行原理

EmFj1C

<script type="module">中,浏览器遇到内部的import引用时,会自动发起http请求,去加载对应的模块。

vite也正是利用了ES module这个特性,使用vite运行项目时,首先会用esbuild进行预构建,将所有模块转换为es module,不需要对我们整个项目进行编译打包,而是在浏览器需要加载某个模块时,拦截浏览器发出的请求,根据请求进行按需编译,然后返回给浏览器。

这样一来,首次启动项目(冷启动)时,自然也就比webpack快很多了,并且项目大小对vite启动速度的影响也很小。

webpack在项目调试之前,要把所有文件的依赖关系收集完,打包后才能运行,这是它慢的主要原因,随着项目规模(强调,新手有这个视野很nice)的激增,慢的一坨屎一样(数分钟)。于是,针对webpack的bundle思路,社区推出了bundless思路框架:Vite。

bundlebundless,原因是浏览器里的JavaScript没有很好的方式去引入其他文件Webpack(node环境运行) 只能通过require(commonJS) 将一堆js 按依赖关系组织起来,打包后运行。但是现在主流浏览器都支持ES6的module功能(import/export)。

只要在script标签上添加type="module"标记, main.js 就可以直接使用import 语法(动态导入)去引入js 文件,所以可以不用webpack(node)打包功能,直接使用浏览器的module功能就可以组织我们的代码。

Vite 能够做到这么快的原因,还有一部分是因为使用了 esbuild 去解析 JavaScript 文件。esbuild 是一个用 Go 语言实现的 JavaScript 打包器,支持 JavaScript 和 TypeScript 语法,现在前端工程化领域的工具也越来越多地使用 Go 和 Rust 等更高效的语言书写,这也是性能优化的一个方向。

构建方式

我们再来看一下,vite与webpack在项目构建上有哪些区别。

webpack

webpack是基于nodejs运行的,但js只能单线程运行,无法利用多核CPU的优势,当项目越来越大时,构建速度也就越来越慢了。

vite

vite预构建按需编译的过程,都是使用esbuild完成的。

esbuild是用go语言编写的,可以充分利用多核CPU的优势,所以vite开发环境下的预构建按需编译速度,都是非常快的。

http2

vite充分利用了http2可以并发请求的优势,这也是速度快的一个主要原因。 接下来,我们了解一下http2的来龙去脉。

在之前http1的时候,浏览器对同一个域名的请求,是有并发限制的,一般为6个,如果并发请求6个以上,就会造成阻塞问题,所以在http1的时代,我们要减少打包产物的文件数量,减少并发请求,来提高项目的加载速度。

2015年以后,http2出现了,他可以并发发送多个请求,不会出现http1的并发限制。这时候,将打包产物分成多个小模块,并行去加载,反而会更快。

vite也充分利用了这一优势,对项目资源进行了合理的拆分,访问项目时,同时加载多个模块,来提升项目访问速度。

热更新

vite速度快的另一个原因是与webpack不同的热更新机制。

我们首先来了解一下什么是HMR。

模块热替换(hot module replacement - HMR),该功能可以实现应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面,也就是我们常说的热更新

vite与webpack虽然都支持HMR,但两个工具的实现原理是不一样的。

webpack

webpack项目中,每次修改文件,都会对整个项目重新进行打包,这对大项目来说,是非常不友好的。

虽然webpack现在有了缓存机制,但还是无法从根本上解决这个问题。

vite

vite项目中,监听到文件变更后,会用websocket通知浏览器,重新发起新的请求,只对该模块进行重新编译,然后进行替换。

并且基于es module的特性,vite利用浏览器的缓存策略,针对源码模块(我们自己写的代码)做了协商缓存处理,针对依赖模块(第三方库)做了强缓存处理,这样我们项目的访问的速度也就更快了。

生产环境

vite生产环境下,为什么使用rollup打包呢?

Rollup 是一款 ES Module 打包器, 从作用上来看,RollupWebpack 非常类似。不过相比于 WebpackRollup要小巧的多,打包生成的文件更小。 因为小巧,自然在这种特定的打包环境下,Rollup的打包速度也要比 Webpack 快很多。

vite正是基于es module的特性实现的,所以使用rollup要更合适一些。

vite生产环境下,为什么不用esbuild打包呢?

尽管esbuild的打包速度比rollup更快,但 Vite 目前的插件 API 与使用 esbuild 作为打包器并不兼容,rollup插件api与基础建设更加完善,所以在生产环境vite使用rollup打包会更稳定一些。

如果后面esbuild基础建设与生态更加完善后,esbuild还是更有优势的。

所以使用vite可能会带来开发环境与生产环境打包结果不一致的问题。

使用成本

除了速度上的区别,我们再分析一下,vite与webpack的使用成本。

webpack

如果我们使用webpack自己去搭建项目脚手架时,需要配置比较多的东西, 比如:跨域代码压缩代码分割css预处理器的代码转换样式兼容性vue/react代码解析图片压缩代码热更新es降级ts转换等等,远不止这些。

概念和配置项太多,我们需要了解各种loader、plugin的使用,并且需要根据项目场景,对配置不断进行优化,心智负担太大。

所以就出现了一些基于webpack上层封装的脚手架,如:vue-clicreate-react-appumi等。

vite

vite对我们常用功能都做了内置,比如:css 预处理器html 预处理器hash 命名异步加载分包压缩HMR等等,我们可以很轻松的通过配置项去配置。

并且vite官方也提供了一些官方模板、社区模板,我们可以快速地创建出一个带有最佳预设项目,不需要关心太多的配置项。

vite的出现,降低了我们的学习成本、增加了开发体验,我们就可以把更多的时间放在业务开发上了,打磨出更好产品。