前端面试超全整理4(vue react常见项目优化问题及优化逻辑)

23、vue项目常见优化点

1.keep-alive缓存页面

2.使用v-show复用DOM

3.v-for 遍历避免同时使用 v-if

4.事件的销毁

例如组件销毁时清除定时器

5.图片懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现

在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。

6.第三方插件按需引入

像element-ui这样的第三方组件库可以按需引入避免体积太大

**7.**无状态的组件标记为函数式组件

8.路由懒加载

  1. 基于webpack打包优化:屏蔽sourceMap、treeshaking…
  2. 静态资源压缩传输
  3. 资源懒加载、预加载(组件、css、图片、。。)
  4. v-if 和 v-show选择调用
  5. 减少watch的数据,慎用deep watch
  6. SSR(服务端渲染)(根据业务需求)
  7. 骨架屏加载 (通过占位线框元素,渐进式加载数据)
  8. keep-alive 缓存

24、react 项目常见优化点

UI 更新需要昂贵的 DOM 操作,而 React 内部使用几种巧妙的技术以便最小化 DOM 操作次数。对于大部分应用而言,使用 React 时无需专门优化就已拥有高性能的用户界面。尽管如此,你仍然有办法来加速你的 React 应用。

1、使用生产版本

当你需要对你的 React 应用进行 benchmark,或者遇到了性能问题,请确保你正在使用压缩后的生产版本。

React 默认包含了许多有用的警告信息。这些警告信息在开发过程中非常有帮助。然而这使得 React 变得更大且更慢,所以你需要确保部署时使用了生产版本。

如果你不能确定你的编译过程是否设置正确,你可以通过安装 Chrome 的 React 开发者工具 来检查。如果你浏览一个基于 React 生产版本的网站,图标背景会变成深色:

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第1张图片

如果你浏览一个基于 React 开发模式的网站,图标背景会变成红色:

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第2张图片

推荐你在开发应用时使用开发模式,而在为用户部署应用时使用生产模式。

你可以在下面看到几种为应用构建生产版本的操作说明。

Create React App

如果你的项目是通过 Create React App 构建的,运行:

npm run build

这段命令将在你的项目下的 build/ 目录中生成对应的生产版本。

注意只有在生产部署前才需要执行这个命令。正常开发使用 npm start 即可。

单文件构建

我们提供了可以在生产环境使用的单文件版 React 和 React DOM:



注意只有以 .production.min.js 为结尾的 React 文件适用于生产。

Brunch

通过安装 terser-brunch 插件,来获得最高效的 Brunch 生产构建:

# 如果你使用 npm
npm install --save-dev terser-brunch

# 如果你使用 Yarn
yarn add --dev terser-brunch

接着,在 build 命令后添加 -p 参数,以创建生产构建:

brunch build -p

请注意,你只需要在生产构建时这么做。你不需要在开发环境中使用 -p 参数或者应用这个插件,因为这会隐藏有用的 React 警告信息并使得构建速度变慢。

Browserify

为了最高效的生产构建,需要安装一些插件:

# 如果你使用 npm
npm install --save-dev envify terser uglifyify

# 如果你使用 Yarn
yarn add --dev envify terser uglifyify

为了创建生产构建,确保你添加了以下转换器 (顺序很重要)

  • envify 转换器用于设置正确的环境变量。设置为全局 (-g)。
  • uglifyify 转换器移除开发相关的引用代码。同样设置为全局 (-g)。
  • 最后,将产物传给 terser 并进行压缩(为什么要这么做?)。

举个例子:

browserify ./index.js \
  -g [ envify --NODE_ENV production ] \
  -g uglifyify \
  | terser --compress --mangle > ./bundle.js

请注意,你只需要在生产构建时用到它。你不需要在开发环境应用这些插件,因为这会隐藏有用的 React 警告信息并使得构建速度变慢。

Rollup

为了最高效的 Rollup 生产构建,需要安装一些插件:

# 如果你使用 npm
npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser

# 如果你使用 Yarn
yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser

为了创建生产构建,确保你添加了以下插件 (顺序很重要)

  • replace 插件确保环境被正确设置。
  • commonjs 插件用于支持 CommonJS。
  • terser 插件用于压缩并生成最终的产物。
plugins: [
  // ...
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  require('rollup-plugin-terser')(),
  // ...
]

点击查看完整的安装示例。

请注意,你只需要在生产构建时用到它。你不需要在开发中使用 terser 插件或者 replace 插件替换 'production' 变量,因为这会隐藏有用的 React 警告信息并使得构建速度变慢。

webpack

注意:

如果你使用了 Create React App,请跟随上面的说明进行操作。
只有当你直接配置了 webpack 才需要参考以下内容。

在生产模式下,Webpack v4+ 将默认对代码进行压缩:

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimizer: [new TerserPlugin({ /* additional options here */ })],
  },
};

你可以在 webpack 文档中了解更多内容。

请注意,你只需要在生产构建时用到它。你不需要在开发中使用 TerserPlugin 插件,因为这会隐藏有用的 React 警告信息并使得构建速度变慢。

2、使用 Chrome Performance 标签分析组件

开发模式下,你可以通过支持的浏览器可视化地了解组件是如何 挂载、更新以及卸载的。例如:

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第3张图片

在 Chrome 中进行如下操作:

  1. 临时禁用所有的 Chrome 扩展,尤其是 React 开发者工具。他们会严重干扰度量结果!
  2. 确保你是在 React 的开发模式下运行应用。
  3. 打开 Chrome 开发者工具的 Performance 标签并按下 Record
  4. 对你想分析的行为进行复现。尽量在 20 秒内完成以避免 Chrome 卡住。
  5. 停止记录。
  6. User Timing 标签下会显示 React 归类好的事件。

你可以查阅 Ben Schwarz 的文章以获取更详尽的指导。

需要注意的是在生产环境中组件会相对渲染得更快些。当然了,这能帮助你查看是否有不相关的组件被错误地更新,以及 UI 更新的深度和频率。

目前只有 Chrome、Edge 和 IE 支持该功能,但是我们使用的是标准的用户计时 API。我们期待有更多浏览器能支持它。

3、使用开发者工具中的分析器对组件进行分析

react-dom 16.5+ 和 react-native 0.57+ 加强了分析能力。在开发模式下,React 开发者工具会出现分析器标签。 你可以在《介绍 React 分析器》这篇博客中了解概述。 你也可以在 YouTube 上观看分析器的视频指导。

如果你还未安装 React 开发者工具,你可以在这里找到它们:

  • Chrome 浏览器扩展
  • Firefox 浏览器扩展
  • 独立 Node 包

注意

react-dom 的生产分析包也可以在 react-dom/profiling 中找到。 通过查阅 fb.me/react-profiling 来了解更多关于使用这个包的内容。

4、虚拟化长列表

如果你的应用渲染了长列表(上百甚至上千的数据),我们推荐使用“虚拟滚动”技术。这项技术会在有限的时间内仅渲染有限的内容,并奇迹般地降低重新渲染组件消耗的时间,以及创建 DOM 节点的数量。

react-window 和 react-virtualized 是热门的虚拟滚动库。 它们提供了多种可复用的组件,用于展示列表、网格和表格数据。 如果你想要一些针对你的应用做定制优化,你也可以创建你自己的虚拟滚动组件,就像 Twitter 所做的。

5、避免调停

React 构建并维护了一套内部的 UI 渲染描述。它包含了来自你的组件返回的 React 元素。该描述使得 React 避免创建 DOM 节点以及没有必要的节点访问,因为 DOM 操作相对于 JavaScript 对象操作更慢。虽然有时候它被称为“虚拟 DOM”,但是它在 React Native 中拥有相同的工作原理。

当一个组件的 props 或 state 变更,React 会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM。当它们不相同时,React 会更新该 DOM。

即使 React 只更新改变了的 DOM 节点,重新渲染仍然花费了一些时间。在大部分情况下它并不是问题,不过如果它已经慢到让人注意了,你可以通过覆盖生命周期方法 shouldComponentUpdate 来进行提速。该方法会在重新渲染前被触发。其默认实现总是返回 true,让 React 执行更新:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

如果你知道在什么情况下你的组件不需要更新,你可以在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作。

在大部分情况下,你可以继承 React.PureComponent 以代替手写 shouldComponentUpdate()。它用当前与之前 props 和 state 的浅比较覆写了 shouldComponentUpdate() 的实现。

6、shouldComponentUpdate 的作用

这是一个组件的子树。每个节点中,SCU 代表 shouldComponentUpdate 返回的值,而 vDOMEq 代表返回的 React 元素是否相同。最后,圆圈的颜色代表了该组件是否需要被调停。

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第4张图片

节点 C2 的 shouldComponentUpdate 返回了 false,React 因而不会去渲染 C2,也因此 C4 和 C5 的 shouldComponentUpdate 不会被调用到。

对于 C1 和 C3,shouldComponentUpdate 返回了 true,所以 React 需要继续向下查询子节点。这里 C6 的 shouldComponentUpdate 返回了 true,同时由于渲染的元素与之前的不同使得 React 更新了该 DOM。

最后一个有趣的例子是 C8。React 需要渲染这个组件,但是由于其返回的 React 元素和之前渲染的相同,所以不需要更新 DOM。

显而易见,你看到 React 只改变了 C6 的 DOM。对于 C8,通过对比了渲染的 React 元素跳过了渲染。而对于 C2 的子节点和 C7,由于 shouldComponentUpdate 使得 render 并没有被调用。因此它们也不需要对比元素了。

示例

如果你的组件只有当 props.color 或者 state.count 的值改变才需要更新时,你可以使用 shouldComponentUpdate 来进行检查:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      
    );
  }
}

在这段代码中,shouldComponentUpdate 仅检查了 props.colorstate.count 是否改变。如果这些值没有改变,那么这个组件不会更新。如果你的组件更复杂一些,你可以使用类似“浅比较”的模式来检查 propsstate 中所有的字段,以此来决定是否组件需要更新。React 已经提供了一位好帮手来帮你实现这种常见的模式 - 你只要继承 React.PureComponent 就行了。所以这段代码可以改成以下这种更简洁的形式:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      
    );
  }
}

大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。当数据结构很复杂时,情况会变得麻烦。例如,你想要一个 ListOfWords 组件来渲染一组用逗号分开的单词。它有一个叫做 WordAdder 的父组件,该组件允许你点击一个按钮来添加一个单词到列表中。以下代码并不正确:

class ListOfWords extends React.PureComponent {
  render() {
    return 
{this.props.words.join(',')}
; } } class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'] }; this.handleClick = this.handleClick.bind(this); } handleClick() { // 这部分代码很糟,而且还有 bug const words = this.state.words; words.push('marklar'); this.setState({words: words}); } render() { return (
); } }

问题在于 PureComponent 仅仅会对新老 this.props.words 的值进行简单的对比。由于代码中 WordAdderhandleClick 方法改变了同一个 words 数组,使得新老 this.props.words 比较的其实还是同一个数组。即便实际上数组中的单词已经变了,但是比较结果是相同的。可以看到,即便多了新的单词需要被渲染, ListOfWords 却并没有被更新。

7、不可变数据的力量

避免该问题最简单的方式是避免更改你正用于 props 或 state 的值。例如,上面 handleClick 方法可以用 concat 重写:

handleClick() {
  this.setState(state => ({
    words: state.words.concat(['marklar'])
  }));
}

ES6 数组支持扩展运算符,这让代码写起来更方便了。如果你在使用 Create React App,该语法已经默认支持了。

handleClick() {
  this.setState(state => ({
    words: [...state.words, 'marklar'],
  }));
};

你可以用类似的方式改写代码来避免可变对象的产生。例如,我们有一个叫做 colormap 的对象。我们希望写一个方法来将 colormap.right 设置为 'blue'。我们可以这么写:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

为了不改变原本的对象,我们可以使用 Object.assign 方法:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

现在 updateColorMap 返回了一个新的对象,而不是修改老对象。Object.assign 是 ES6 的方法,需要 polyfill。

这里有一个 JavaScript 的提案,旨在添加对象扩展属性以使得更新不可变对象变得更方便:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

如果你在使用 Create React App,Object.assign 以及对象扩展运算符已经默认支持了。

当处理深层嵌套对象时,以 immutable (不可变)的方式更新它们令人费解。如遇到此类问题,请参阅 Immer 或 immutability-helper。这些库会帮助你编写高可读性的代码,且不会失去 immutability (不可变性)带来的好处。

8、shouldComponentUpdate & PureComponent 避免重复渲染

9、使用不可突变数据结构

10、组件尽可能的进行拆分、解耦

11、bind函数优化

12、懒加载组件 强烈推荐

13、不错的文章,强烈推荐

14、react性能优化,推荐(新版本不支持了)

25、优化逻辑

50万 10万

1. 知识体系

1.1从输入 URL 到页面加载完成,发生了什么?

首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出我们的 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第5张图片

将这个过程切分为如下的过程片段

DNS 解析

TCP 连接

HTTP 请求抛出

服务端处理请求,HTTP 响应返回

浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户

1.2性能优化思维导图

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第6张图片

2.网络篇(http)

2.1 前端能做的网络优化

输入 URL 到显示页面这个过程中,涉及到网络层面的,有三个主要过程:

DNS 解析

TCP 连接

HTTP 请求/响应

对于 DNS 解析和 TCP 连接两个步骤,我们前端可以做的努力非常有限。相比之下,HTTP 连接这一层面的优化才是我们网络优化的核心

HTTP 优化有两个大的方向

减少请求次数

减少单次请求所花费的时间

2.1 减少请求次数

2.1.1 图片:雪碧图,图标字体文件

雪碧图

多张小图片合并为一张图,利用CSS -background-position调整图片显示位置

图标字体文件

阿里图标

2.1.2 合并JS和CSS文件

webpack,需要斟酌而定

2.1.3 浏览器缓存

如果图片或者脚本,样式文件内容比较固定,不经常被修改,那么,尽可能利用缓存技术,减少HTTP请求次数或文件下载次数

2.2 减少单次请求所花费的时间

主要是减少请求中数据的大小,从而达到减少单次请求所花费的时间

2.2.1 图片

图片在线批量压缩

gzip

如果是vue项目,还有nginx,哪么vue,nginx,服务器都要开启gzip

3.网络篇(图片优化)

3.1不同业务场景下的图片方案选型

3.1.1前置知识:二进制位数与色彩的关系

在计算机中,像素用二进制数来表示。不同的图片格式中像素与二进制位数之间的对应关系是不同的。一个像素对应的二进制位数越多,它可以表示的颜色种类就越多,成像效果也就越细腻,文件体积相应也会越大。

3.2 JPEG/JPG

关键字:有损压缩、体积小、加载快、不支持透明

3.2.1 JPG 的优点

JPG 最大的特点是有损压缩。这种高效的压缩算法使它成为了一种非常轻巧的图片格式。另一方面,即使被称为“有损”压缩,JPG的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉——前提是你用对了业务场景。

3.2.2 使用场景

JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。

两大电商网站对大图的处理,是 JPG 图片应用场景的最佳写照:

打开淘宝首页,我们可以发现页面中最醒目、最庞大的图片,一定是以 .jpg 为后缀的:

使用 JPG 呈现大图,既可以保住图片的质量,又不会带来令人头疼的图片体积,是当下比较推崇的一种方案。

3.2.3 JPG 的缺陷

有损压缩在上文所展示的轮播图上确实很难露出马脚,但当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。
此外,JPEG 图像不支持透明度处理,透明图片需要召唤 PNG 来呈现。

3.3 png

关键字:无损压缩、质量高、体积大、支持透明

3.3.1 PNG 的优点

PNG(可移植网络图形格式)是一种无损压缩的高保真的图片格式。8 和 24,这里都是二进制数的位数。按照我们前置知识里提到的对应关系,8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。

PNG 图片具有比 JPG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。它弥补了上文我们提到的 JPG 的局限性,唯一的 BUG 就是体积太大。

3.3.2PNG-8 与 PNG-24 的选择题

什么时候用 PNG-8,什么时候用 PNG-24,这是一个问题

理论上来说,当你追求最佳的显示效果、并且不在意文件体积大小时,是推荐使用 PNG-24 的。

但实践当中,为了规避体积的问题,我们一般不用PNG去处理较复杂的图像。当我们遇到适合 PNG 的场景时,也会优先选择更为小巧的 PNG-8。

如何确定一张图片是该用 PNG-8 还是 PNG-24 去呈现呢?好的做法是把图片先按照这两种格式分别输出,看 PNG-8 输出的结果是否会带来肉眼可见的质量损耗,并且确认这种损耗是否在我们(尤其是你的 UI 设计师)可接受的范围内,基于对比的结果去做判断。

3.3.3 应用场景

前面我们提到,复杂的、色彩层次丰富的图片,用 PNG 来处理的话,成本会比较高,我们一般会交给 JPG 去存储。

考虑到 PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。

此时我们再次把目光转向性能方面堪称业界楷模的淘宝首页,我们会发现它页面上的 Logo,无论大小,还真的都是 PNG 格式:

3.4 SVG

关键字:文本文件、体积小、不失真、兼容性好

3.4.1 SVG 的使用方式与应用场景

将 SVG 写入 HTML

将 SVG 写入独立文件后引入 HTML:

3.5 Base64

关键字:文本文件、依赖编码、小图标解决方案

3.5.1 Base64 的应用场景

图片的实际尺寸很小(大家可以观察一下掘金页面的 Base64 图,几乎没有超过 2kb 的)

图片无法以雪碧图的形式与其它小图结合(合成雪碧图仍是主要的减少 HTTP 请求的途径,Base64 是雪碧图的补充)

图片的更新频率非常低(不需我们重复编码和修改文件内容,维护成本较低)

3.6 WebP

关键字:年轻的全能型选手
是 Google 专为 Web 开发的一种旨在加快图片加载速度的图片格式,它支持有损压缩和无损压缩。

3.6.1 WebP 的优点

WebP 像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片——它集多种图片文件格式的优点于一身。

3.6.2WebP 的局限性

兼容性

3.7 总结

不同业务场景下的图片方案选型

4.存储篇(浏览器缓存)

4.1 什么是缓存

对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤

浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。

缓存思维导图

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第7张图片

4.2 缓存位置

4.2.1 缓存优先级

从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。

Service Worker

Memory Cache

Disk Cache

Push Cache

4.2.2 Service Worker

不了解
MDN

4.2.3 MemoryCache

MemoryCache,是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。

内存缓存是快的,也是“短命”的。它和渲染进程“生死相依”,当进程结束后,也就是 tab 关闭以后,内存里的数据也将不复存在

那么哪些文件会被放入内存呢?

事实上,这个划分规则,一直以来是没有定论的。不过想想也可以理解,内存是有限的,很多时候需要先考虑即时呈现的内存余量,再根据具体的情况决定分配给内存和磁盘的资源量的比重——资源存放的位置具有一定的随机性

虽然划分规则没有定论,但根据日常开发中观察的结果,包括我们开篇给大家展示的 Network 截图,我们至少可以总结出这样的规律:资源存不存内存,浏览器秉承的是“节约原则”。我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。

4.2.4 Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍

浏览器会把哪些文件丢进内存中?哪些丢进硬盘中

对于大文件来说,大概率是不存储在内存中的,反之优先

当前系统内存使用率高的话,文件优先存储进硬盘

4.2.5 Push Cache

不了解

push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用

4.3 缓存过程分析

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。具体过程如下图:

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第8张图片

上图我们可以知道

浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

4.4 http缓存

HTTP 缓存是我们日常开发中最为熟悉的一种缓存机制。它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

4.5 强缓存

强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

4.5.1 Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。

缓存过期时间,用来指定资源的到期时间,是服务器端的具体的时间点

告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而不用再次请求

max-age的优化级高于expires,当有max-age的时候,会无视expires

当在有效时间内,如果服务器端的文件已经发生改变,但是浏览器端无法感知

4.5.2 Cache-Control

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令

max-age

s-maxage

private

public

no-cache

no-store

max-age

max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效

设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间,

优先级高于Expires

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第9张图片

s-maxage

覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略

能用于public,如CDN

优先级高于max-age

private

所有内容只有客户端可以缓存
表示中间节点不允许缓存,对于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,自己不缓存任何数据。当下次Browser再次请求时proxy会做好请求转发而不是自作主张给自己缓存的数据

表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它),可以缓存响应内容

自己的服务器

public

所有内容都将被缓存(客户端和代理服务器都可缓存)
具体来说响应可被任何中间节点缓存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中间的proxy可以缓存资源,比如下次再请求同一资源proxy1直接把自己缓存的东西给 Browser 而不再向proxy2要。

no-store

所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存

缓存不应存储有关客户端请求或服务器响应的任何内容。

不会使用任何缓存策略

no-cache
客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。表示不使用 Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。需要注意的是,no-cache这个名字有一点误导。设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致

释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证

这个文件不管怎么样,都会向服务器发起请求,去服务器哪边询问,这个文件有没有在缓存策略里

强缓存思维导图

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第10张图片

4.6 协商缓存

4.6.1 什么是协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

协商缓存生效,返回304和Not Modified
协商缓存失效,返回200和请求结果

4.6.2 Last-Modified和If-Modified-Since

ast-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-Since 或 If-Unmodified-Since 首部的条件请求会使用这个字段。

基于客户端和服务端协商的缓存机制

Last-Modified ----response header

If-Modified-Since----request header

需要与cache-control共同使用

max-age的优先级高于Last-Modified

缺点

某些服务端不能获取精确的修改时间

文件修改时间改了,但文件内容却没有变

4.6.3 Etag/If-None-Match

ETagHTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)

文件内容的hash值

etag–response header

if-none-match – request header

要与cache-control共同使用

4.6.4两者对比

首先在精确度上,Etag要优于Last-Modified。

第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。

第三在优先级上,服务器校验优先考虑Etag

4.6.5 思维导图

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第11张图片

4.7 参考

浏览器缓存机制介绍与缓存策略剖析
一文读懂前端缓存
深入理解浏览器的缓存机制

5. 存储篇(本地存储) 5.1 Cookie

Cookie 的本职工作并非本地存储,而是“维持状态”。

Cookie 不够大

同一个域名下的所有请求,都会携带 Cookie

5.2 Local Storage,Session Storage 5.2.1 概述

存储容量大: Web Storage 根据浏览器的不同,存储容量可以达到 5-10M 之间

仅位于浏览器端,不与服务端发生通信。

5.2.2 Local Storage 与 Session Storage 的区别

生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。

作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。

5.2.3 应用场景

Local Storage

理论上 Cookie 无法胜任的、可以用简单的键值对来存取的数据存储任务,都可以交给 Local Storage 来做。

存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串

存储一些不经常更新的 CSS、JS 等静态资源

Session Storage

微博的 Session Storage 就主要是存储你本次会话的浏览足迹

5.3 IndexedDB

IndexedDB 是没有存储上限的(一般来说不会小于 250M)

IndexedDB 可以看做是 LocalStorage 的一个升级,当数据的复杂度和规模上升到了 LocalStorage 无法解决的程度,我们毫无疑问可以请出 IndexedDB 来帮忙。

6. CDN 6.1 什么是CDN

CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求。 CDN 提供快速服务,较少受高流量影响。

6.2 为什么要用 CDN

缓存、本地存储带来的性能提升,是不是只能在“获取到资源并把它们存起来”这件事情发生之后?也就是说,首次请求资源的时候,这些招数都是救不了我们的。要提升首次请求的响应能力,我们还需要借助 CDN 的能力

6.3 CDN 如何工作

假设我的根服务器在杭州,同时在图示的五个城市里都有自己可用的机房

此时有一位北京的用户向我请求资源。在网络带宽小、用户访问量大的情况下,杭州的这一台服务器或许不那么给力,不能给用户非常快的响应速度。于是我灵机一动,把这批资源 copy 了一批放在北京的机房里。当用户请求资源时,就近请求北京的服务器,北京这台服务器低头一看,这个资源我存了,离得这么近,响应速度肯定噌噌的!那如果北京这台服务器没有 copy 这批资源呢?它会再向杭州的根服务器去要这个资源。在这个过程中,北京这台服务器就扮演着 CDN 的角色。

前端面试超全整理4(vue react常见项目优化问题及优化逻辑)_第12张图片

6.4 CDN的核心功能

CDN 的核心点有两个,一个是缓存,一个是回源。

缓存

“缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程

回源

就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。

6.5 CDN 与前端性能优化

CDN 往往被用来存放静态资源

“根服务器”本质上是业务服务器,它的核心任务在于生成动态页面或返回非纯静态页面,这两种过程都是需要计算的。业务服务器仿佛一个车间,车间里运转的机器轰鸣着为我们产出所需的资源;相比之下,CDN 服务器则像一个仓库,它只充当资源的“栖息地”和“搬运工”。

所谓“静态资源”,就是像 JS、CSS、图片等不需要业务服务器进行计算即得的资源。而“动态资源”,顾名思义是需要后端实时动态生成的资源,较为常见的就是 JSP、ASP 或者依赖服务端渲染得到的 HTML 页面。

什么是“非纯静态资源”呢?它是指需要服务器在页面之外作额外计算的 HTML 页面。具体来说,当我打开某一网站之前,该网站需要通过权限认证等一系列手段确认我的身份、进而决定是否要把 HTML 页面呈现给我。这种情况下 HTML 确实是静态的,但它和业务服务器的操作耦合,我们把它丢到CDN 上显然是不合适的。

6.6 CDN 的实际应用

静态资源本身具有访问频率高、承接流量大的特点,因此静态资源加载速度始终是前端性能的一个非常关键的指标。CDN 是静态资源提速的重要手段,在许多一线的互联网公司,“静态资源走 CDN”并不是一个建议,而是一个规定

https://www.taobao.com/

可以看到业务服务器确实是返回给了我们一个尚未被静态资源加持过的简单 HTML 页面

随便点开一个静态资源,可以看到它都是从 CDN 服务器上请求来的

6.7 cdn与cookie

Cookie 是紧跟域名的。同一个域名下的所有请求,都会携带 Cookie。大家试想,如果我们此刻仅仅是请求一张图片或者一个 CSS 文件,我们也要携带一个 Cookie 跑来跑去(关键是 Cookie 里存储的信息我现在并不需要),这是一件多么劳民伤财的事情。Cookie 虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie 带来的开销将是无法想象的

同一个域名下的请求会不分青红皂白地携带 Cookie,而静态资源往往并不需要 Cookie 携带什么认证信息。把静态资源和主页面置于不同的域名下,完美地避免了不必要的 Cookie 的出现!

看起来是一个不起眼的小细节,但带来的效用却是惊人的。以电商网站静态资源的流量之庞大,如果没把这个多余的 Cookie 拿下来,不仅用户体验会大打折扣,每年因性能浪费带来的经济开销也将是一个非常恐怖的数字。

7.渲染篇(服务端渲染) 7.1 客户端渲染

客户端渲染模式下,服务端会把渲染需要的静态文件发送给客户端,客户端加载过来之后,自己在浏览器里跑一遍 JS,根据 JS 的运行结果,生成相应的 DOM



  
    我是客户端渲染的页面
  
  
    

根节点下到底是什么内容呢?你不知道,我不知道,只有浏览器把 index.js 跑过一遍后才知道,这就是典型的客户端渲染。

页面上呈现的内容,你在 html 源文件里里找不到——这正是它的特点。

7.2 服务端渲染

服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。

使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。

7.3 服务端渲染解决了什么性能问题

事实上,很多网站是出于效益(seo)的考虑才启用服务端渲染,性能倒是在其次。

假设 A 网站页面中有一个关键字叫“前端性能优化”,这个关键字是 JS 代码跑过一遍后添加到 HTML 页面中的。那么客户端渲染模式下,我们在搜索引擎搜索这个关键字,是找不到 A 网站的——搜索引擎只会查找现成的内容,不会帮你跑 JS 代码。A 网站的运营方见此情形,感到很头大:搜索引擎搜不出来,用户找不到我们,谁还会用我的网站呢?为了把“现成的内容”拿给搜索引擎看,A 网站不得不启用服务端渲染。

但性能在其次,不代表性能不重要。服务端渲染解决了一个非常关键的性能问题——首屏加载速度过慢。在客户端渲染模式下,我们除了加载 HTML,还要等渲染所需的这部分 JS 加载完,之后还得把这部分 JS 在浏览器上再跑一遍。这一切都是发生在用户点击了我们的链接之后的事情,在这个过程结束之前,用户始终见不到我们网页的庐山真面目,也就是说用户一直在等!相比之下,服务端渲染模式下,服务器给到客户端的已经是一个直接可以拿来呈现给用户的网页,中间环节早在服务端就帮我们做掉了,用户岂不“美滋滋”?

7.4 服务端渲染的应用场景

服务端渲染本质上是本该浏览器做的事情,分担给服务器去做。这样当资源抵达浏览器时,它呈现的速度就快了

但仔细想想,在这个网民遍地的时代,几乎有多少个用户就有多少台浏览器。用户拥有的浏览器总量多到数不清,那么一个公司的服务器又有多少台呢?我们把这么多台浏览器的渲染压力集中起来,分散给相比之下数量并不多的服务器,服务器肯定是承受不住的

除非网页对性能要求太高了,以至于所有的招式都用完了,性能表现还是不尽人意,这时候我们就可以考虑向老板多申请几台服务器,把服务端渲染搞起来了~

8.渲染篇(浏览器渲染) 8.1 浏览器内核

浏览器内核可以分成两部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎

渲染引擎又包括了 HTML 解释器、CSS 解释器、布局、网络、存储、图形、音视频、图片解码器等等零部件。

8.2 浏览器渲染过程解析

8.3 基于渲染流程的 CSS 优化建议 8.3.1 CSS 选择符是从右到左进行匹配的

#myList  li {}

浏览器必须遍历页面上每个 li 元素,并且每次都要去确认这个 li 元素的父元素 id 是不是 myList

8.3.2 具体优化

避免使用通配符,只对需要用到的元素进行选择

关注可以通过继承实现的属性,避免重复匹配重复定义。

少用标签选择器。如果可以,用类选择器替代

不要画蛇添足,id 和 class 选择器不应该被多余的标签选择器拖后腿

减少嵌套。后代选择器的开销是最高的,因此我们应该尽量将选择器的深度降到最低(最高不要超过三层),尽可能使用类来关联每一个标签元素

8.4 告别阻塞:CSS 与 JS 的加载顺序优化

HTML、CSS 和 JS,都具有阻塞渲染的特性。
HTML 阻塞,天经地义——没有 HTML,何来 DOM?没有 DOM,渲染和优化,都是空谈。

8.4.1 CSS 的阻塞

在刚刚的过程中,我们提到 DOM 和 CSSOM 合力才能构建渲染树。这一点会给性能造成严重影响:默认情况下,CSS 是阻塞的资源。浏览器在构建 CSSOM 的过程中,不会渲染任何已处理的内容。即便 DOM 已经解析完毕了,只要 CSSOM 不 OK,那么渲染这个事情就不 OK(这主要是为了避免没有 CSS 的 HTML 页面丑陋地“裸奔”在用户眼前)。

只有当我们开始解析 HTML 后、解析到 link 标签或者 style 标签时,CSS 才登场,CSSOM 的构建才开始。很多时候,DOM 不得不等待 CSSOM。因此我们可以这样总结:

CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。

尽早(将 CSS 放在 head 标签里)和尽快(启用 CDN 实现静态资源加载速度的优化)

8.4.2 JS 的阻塞

JS 的作用在于修改,它帮助我们修改网页的方方面面:内容、样式以及它如何响应用户交互。这“方方面面”的修改,本质上都是对 DOM 和 CSSDOM 进行修改。因此 JS 的执行会阻止 CSSOM,在我们不作显式声明的情况下,它也会阻塞 DOM。

JS 引擎是独立于渲染引擎存在的。我们的 JS 代码在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎。JS 引擎对内联的 JS 代码会直接执行,对外部 JS 文件还要先获取到脚本、再进行执行。等 JS 引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续 CSSOM 和 DOM 的构建。 因此与其说是 JS 把 CSS 和 HTML 阻塞了,不如说是 JS 引擎抢走了渲染引擎的控制权。

8.4.3 JS的三种加载方式

正常模式

这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。


async 模式

async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。


defer 模式

efer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。


从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。

9. 渲染篇(dom优化) 10. 渲染篇(Event Loop与异步更新策略(vue)) 10.1 什么是异步更新?

当我们使用 Vue 或 React 提供的接口去更新数据时,这个更新并不会立即生效,而是会被推入到一个队列里。待到适当的时机,队列中的更新任务会被批量触发。这就是异步更新。

异步更新可以帮助我们避免过度渲染,是我们上节提到的“让 JS 为 DOM 分压”的典范之一。

10.2 异步更新的优越性

异步更新的特性在于它只看结果,因此渲染引擎不需要为过程买单

有时我们会遇到这样的情况

// 任务一
this.content = '第一次测试'
// 任务二
this.content = '第二次测试'
// 任务三
this.content = '第三次测试'

我们在三个更新任务中对同一个状态修改了三次,如果我们采取传统的同步更新策略,那么就要操作三次 DOM。但本质上需要呈现给用户的目标内容其实只是第三次的结果,也就是说只有第三次的操作是有意义的——我们白白浪费了两次计算。

但如果我们把这三个任务塞进异步更新队列里,它们会先在 JS 的层面上被批量执行完毕。当流程走到渲染这一步时,它仅仅需要针对有意义的计算结果操作一次 DOM——这就是异步更新的妙处。

10.3 Vue状态更新手法:nextTick

Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后,在下一个事件循环tick中,Vue刷新队列并执行实际工作

$nextTick就是用来知道什么时候DOM更新完成的

11. 渲染篇(回流与重绘)

你可能感兴趣的:(interview,vue.js,reactjs)