基础知识-前端工程化篇

149 阅读11分钟

git

最常用的代码版本管理工具,大型项目需要多人协作开发,必须熟用git

提交代码流程

  • 提交本地
  • pull代码
  • 本地解决
  • pull代码
  • 跑一下项目,如果没问题再提交
  • 提交
git add .
git commit -m "完成登录"
git pull
git push

代码撤回

blog.csdn.net/qq_43775179…

sourceTree使用

webpack

webpack构建过程

Webpack 的运⾏流程是⼀个串⾏的过程,从启动到结束会依次执⾏以下流程:

  1. 初始化参数:从配置⽂件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:⽤上⼀步得到的参数初始化 Compiler 对象,加载所有配置的插件,执⾏对象的 run ⽅法开始执⾏编译;
  3. 确定⼊⼝:根据配置中的 entry 找出所有的⼊⼝⽂件;
  4. 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,接着找出该模块依赖的模块,再递归这个步骤直到所有⼊⼝依赖的⽂件都经过了这个步骤的处理;
  5. 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。

在以上过程中,Webpack 会在特定的时间点⼴播出特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并且插件可以调⽤ Webpack 提供的 API 改变 Webpack 的运⾏结果。

如何提⾼webpack的构建速度?

  1. 多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码
  2. 通过 externals 配置来提取常⽤库
  3. 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
  4. 使⽤ Happypack 实现多线程加速编译
  5. 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度
  6. 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码 Webpack5 搭建 react 脚手架

转载

Webpack 5 发布也有挺长时间了,现在在公司用的脚手架还是 v4 版的,听说 v5 版本更新的重点主要是针对构型性能的优化。听起来感觉挺香的,就想去用 Webpack 5 搭建一个架手架 看看整体感觉如何,因为我现在主要是用 React 开发 所以就以 React 为例搭建,对于 Webpack 5 不是很熟的朋友可以 看看这篇文章 Webpack5 上手测评,其实配置内容跟 Webpack 4 变化不大。主要还是性能上的提升,那么废话不多说,开始了。

Webpack 5 对 Node.js 的版本要求至少是 10.13.0 (LTS)

项目初始化

mkdir webpack5-demo
cd webpack5-demo
npm init -y

安装基础的依赖文件

刚刚开始需要安装的包,我直接贴出来吧,可以复制到 package.json 文件上 执行 npm i 或 yarn

package.json

"devDependencies": {
    "@babel/core": "^7.13.8",
    "@babel/preset-env": "^7.13.8",
    "@babel/preset-react": "^7.12.13",
    "babel-loader": "^8.2.2",
    "chalk": "^4.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^5.1.0",
    "html-webpack-plugin": "^5.2.0",
    "ip": "^1.1.5",
    "style-loader": "^2.0.0",
    "progress-bar-webpack-plugin": "^2.1.0",
    "speed-measure-webpack-plugin": "^1.4.2",
    "webpack": "^5.24.2",
    "webpack-cli": "^4.5.0",
    "webpack-dev-server": "^3.11.2"
  },
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  }
复制代码

此时文件目录情况

webpack5-demo
├─ node_modules
├─ package-lock.json
└─ package.json

配置 webpack

为了区分开发生产环境,方便维护和特殊处理,就不把所有内容配置到 webpack.config.js 这一个文件里面了。

# 这里我们新建一个 config 目录用来专门存放 webpack 配置文件
mkdir config
cd config
touch webpack.common.js # 开发环境 和 生产环境 公共配置 存放在这个文件里面
touch webpack.dev.js # 需要针对开发环境特殊处理的配置存放在这里
touch webpack.prod.js # 需要针对生产环境特殊处理的配置存放在这里
复制代码

在配置 Webpack 的时候少不了会与 文件路径打交道,避免路径零乱,单独创建一个路径调用的配置文件,也是参考 CRA 的

touch paths.js

paths.js

const path = require("path");
const fs = require("fs");
// 获取当前工作目录
const appDirectory = fs.realpathSync(process.cwd());
// 从相对路径中解析绝对路径
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
// 默认的模块扩展名
const moduleFileExtensions = ["js", "jsx", "ts", "tsx", "json"];
// 解析模块路径
const resolveModule = (resolveFn, filePath) => {
    // 查看文件存不存在
    const extension = moduleFileExtensions.find((extension) =>
        fs.existsSync(resolveFn(`${filePath}.${extension}`))
    );
    if (extension) {
        return resolveFn(`${filePath}.${extension}`);
    }
    return resolveFn(`${filePath}.js`); // 如果没有默认就是js
};

module.exports = {
    appBuild: resolveApp("build"), // 打包路径
    appPublic: resolveApp("public"), // 静态资源路径
    appHtml: resolveApp("public/index.html"), // html 模板路径
    appIndexJs: resolveModule(resolveApp, "src/index"), // 打包入口路径
    appNodeModules: resolveApp("node_modules"), // node_modules 路径
    appSrc: resolveApp("src"), // 主文件入口路径
    moduleFileExtensions, // 模块扩展名
};
复制代码

此时文件目录情况

demo
├─ config
├─ paths.js
│  ├─ webpack.common.js
│  ├─ webpack.dev.js
│  └─ webpack.prod.js
├─ node_modules
├─ package-lock.json
└─ package.json

webpack.common.js

const paths = require("./paths");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = function (options) {
    return {
        mode: options.mode,
        entry: paths.appSrc,
        output: {
            path: paths.appBuild,
            publicPath: "/",
        },
        cache: {
            // 使用持久化缓存
            type: "filesystem", //memory:使用内容缓存 filesystem:使用文件缓存
        },
        devtool: false,
        module: {
            rules: [
                {
                    test: /.js$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: "babel-loader",
                            options: {
                                presets: [
                                    "@babel/preset-env",
                                    "@babel/preset-react",
                                ],
                            },
                        },
                    ],
                },
            ],
        },
        devServer: {},
        plugins: [
            new HtmlWebpackPlugin({
                template: "./public/index.html",
            }),
            ...options.plugins,
        ],
        stats: options.stats, // 打包日志发生错误和新的编译时输出
    };
};

entry 是 入口,默认寻找路径就是我们写的这个,这里我们需要提供一个入口文件

# 回到webpack5-demo 根目录
# 创建一个src 目录开发文件

mkdir src
touch index.js # 创建一个入口文件
import React from "react";
import ReactDOM from "react-dom";

const App = () => {
    return <div> App入口 </div>;
};

ReactDOM.render(<App />, document.querySelector("#root"));

在打包的时候,使用了HtmlWebpackPlugin,所以得去创建一个 html 模板提供足够插件使用

# 回到webpack5-demo 根目录
# 创建一个public 目录专门存放 静态资源

mkdir public
touch index.html # 创建一个html 模板

index.html 模板

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Webpack-React-Cli</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>

webpack.dev.js 开发配置

module.exports = require("./webpack.common")({
    mode: "development",
    plugins: [],
    stats: "errors-only", //只在发生错误或有新的编译时输出
});

webpack.prod.js 生产配置

const { CleanWebpackPlugin } = require("clean-webpack-plugin"); //打包前清空build目录文件
const ProgressBarPlugin = require("progress-bar-webpack-plugin"); // 打包进度条美化
const chalk = require("chalk");

module.exports = require("./webpack.common")({
    mode: "production",
    devtool: "source-map",
    plugins: [
        new CleanWebpackPlugin(),
        new ProgressBarPlugin({
            format:
                `${chalk.green.bold("build[:bar]")} ` +
                chalk.green.bold(":percent") +
                " (:elapsed seconds)",
            clear: false,
            width: 60,
        }),
    ],
    stats: "normal", //标准输出
});

此时文件目录情况

webpack5-demo
├─ config
│  ├─ paths.js
│  ├─ webpack.common.js
│  ├─ webpack.dev.js
│  └─ webpack.prod.js
├─ node_modules
├─ public
│  └─ index.html
├─ src
│  └─ index.js
├─ package-lock.json
└─ package.json

启动服务

现在我们的 Webpack 基础配置已经 OK 了,就差启动了

配置 pageage.json 脚本

{
  "name": "webpack5-demo",
  "version": "1.0.0",
  "description": "基于webpack5 的React架手架",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config config/webpack.prod.js",
    "start": "webpack serve --config config/webpack.dev.js", // v5
    "dev": "webpack-dev-server --config config/webpack.dev.js" //v4
  },
  "keywords": [],
  "author": "Jason",
  "license": "ISC",
  ...
}

这里有点小改动:

在 Webpack 4 里面通过 webpack-dev-server起服务 在 Webpack 5 里面启动服务里通过 webpack serve

现在已经成功启动了,但是这个启动日志不是很喜欢,我得改造一下(实现自定义输出编译内容),需要重新配置一下开发服务器(webpack-dev-server)

配置开发服务器

虽然建议通过 CLI 运行 webpack-dev-server,但我们也可以选择通过 API 启动服务器。

# 回到webpack5-demo 根目录
# 创建一个server目录

mkdir server
touch index.js # 服务入口
touch appConfig.js # 基础服务配置 自定义服务端口,ip, 代理地址
touch logger.js # 控制台输出的日志

appConfig.js

module.exports = {
    deployUrl: "127.0.0.0:8080", // 本地代码推推送到指定服务器
    proxyUrlMap: {
        "/api": "localtion:3000", // 代理的接口
        "/api2": "localtion:4000", // 代理的接口
    },
    port: 9000, //端口号,
    host: "localhost", //主机号
};

logger.js

const chalk = require('chalk'); // 粉笔
const ip = require('ip');

const divider = chalk.gray('\n-----------------------------------');

const logger = {
    error: (err) => {
        console.error(chalk.red(err));
    },
    appStarted: (port, host, tunnelStarted) => {
        console.log(`Server started ! ${chalk.green('✓')}`);

        if (tunnelStarted) {
            console.log(`Tunnel initialised ${chalk.green('✓')}`);
        }
        console.log(`
${chalk.bold('Access URLs:')}${divider}
Localhost: ${chalk.magenta(`http://${host}:${port}`)}
LAN: ${chalk.magenta(`http://${ip.address()}:${port}`)
            + (tunnelStarted ? `\n    Proxy: ${chalk.magenta(tunnelStarted)}` : '')
}${divider}
${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
    `);
    },
};

module.exports = logger;

index.js

const Webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
// webpack开发 配置文件
const webpackConfig = require("../config/webpack.dev");
// 自定义日志输出
const logger = require("./logger");
// 服务配置
const appConfig = require("./appConfig");

const { port, host } = appConfig; // 监听的端口号
//编译器
const compiler = Webpack(webpackConfig);
//  devServer 参数
const devServerOptions = Object.assign({}, webpackConfig.devServer, {
    // open: true, // 自动打开浏览器
    compress: true, // gzip 压缩
    stats: "minimal",
});
const server = new WebpackDevServer(compiler, devServerOptions);

server.listen(port, host, async (err) => {
    if (err) {
        return logger.error(err.message);
    }
    logger.appStarted(port, "localhost");
});

package.json 配置启动命令

"scripts": {
    "build": "webpack --config config/webpack.prod.js",
    "start": "node server"
  },

此时文件目录情况

webpack5-demo
├─ config
│  ├─ paths.js
│  ├─ webpack.common.js
│  ├─ webpack.dev.js
│  └─ webpack.prod.js
├─ node_modules
├─ public
│  └─ index.html
├─ server
│  ├─ appConfig.js
│  ├─ index.js
│  └─ logger.js
├─ src
│  └─ index.js
├─ package-lock.json
└─ package.json

resolve 解析

resolve 配置以下这几个就可以了,其他使用默认就行

  • modules: 使用第三模块 第一反应去 根目录下的 node_modules 寻找
  • extensions : 在 import 的时候不加文件扩展名,会依次遍历extensions 添加扩展名进行匹配
  • alias: 创建别名, 在import 或 require 的别名,来确保模块引入变得更简单
 resolve: {
    modules: [paths.appNodeModules],
    extensions: ['.js', '.jsx', '.css'],
    alias: {
        moment$: 'moment/moment.js',
        '@src': paths.appSrc,
        '@public': paths.appPublic,
    },
},

基础 loader 配置

css 和 sass

安装

npm i -D style-loader css-loader
npm i -D node-sass sass-loader postcss postcss-loader postcss-preset-env
复制代码

css 和 sass 的 loader 配置很简单 考虑到兼容性问题,还需要 postcss-loader 添加浏览器厂家标识头

用到的插件单独放在根目录的 postcss.config.js 配置文件里头

postcss.config.js

module.exports = {
    plugins: {
        "postcss-preset-env": {},
    },
};

模块资源

Webpack 4 在处理图片或文本类文件的时候用的都是 file-loader 或者 url-loader 现在对于 Webpack 5 来说可以用 Asset Modules (资源模块),就不需要配置 loader 了

  • asset/source 导出资源的源代码 (相当于 raw-loader)
  • asset/resource 发送一个单独的文件并导出 URL(相当于 file-loader)
  • asset/inline 导出一个资源的 data URI(相当于 url-loader)
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择,之前通过使用 url-loader,并且配置资源体积限制实现
// 设置 常量
const cssRegex = /.css$/;
const cssModuleRegex = /.module.css$/;
const sassRegex = /.(scss|sass)$/;
const sassModuleRegex = /.module.(scss|sass)$/;
const imageInlineSizeLimit = 4 * 1024;

module.exports = function (options) {

    return {

        ...

        module: {
            rules: [
                {
                    oneOf: [

                        ...

                        {
                            test: cssRegex,
                            exclude: cssModuleRegex,
                            use: ['style-loader', {
                                loader: 'css-loader',
                                options: {
                                    importLoaders: 1 // 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, sass-loader
                                }
                            },'postcss-loader'],
                        },
                        {
                            test: sassRegex,
                            exclude: sassModuleRegex,
                            use: ['style-loader', {
                                loader: 'css-loader',
                                options: {
                                    importLoaders: 1 // 查询参数 importLoaders,用于配置「css-loader 作用于 @import 的资源之前」有多少个 loader
                                }
                            }, 'postcss-loader', 'sass-loader'],
                        },
                        {
                            test: [/.bmp$/, /.gif$/, /.jpe?g$/, /.png$/],
                            type: 'asset',
                            parser: {
                                dataUrlCondition: {
                                    maxSize: imageInlineSizeLimit // 4kb
                                }
                            }
                        },
                        {
                            test: /.(eot|svg|ttf|woff|woff2?)$/,
                            type: 'asset/resource'
                        },
                    ]
                }

            ]
        },
    }

测试一下 css 和图片

# 在src 目录下新一个 style.scss 文件

touch style.scss

先写入点内容

* {
    margin: 0;
    padding: 0;
}

div {
    color: red;
}

引入样式和图片

import React from "react";
import ReactDOM from "react-dom";

import npm from "@public/assets/imgs/npm.png";

import "./style.scss";

const App = () => {
    return (
        <div>
            App入口
            <img src={npm} />
        </div>
    );
};

ReactDOM.render(<App />, document.querySelector("#root"));

再看看打包情况

由于设置了持久化缓存,第二次速度就很快

此时文件目录情况

webpack5-demo
├─ build
├─ config
│  ├─ paths.js
│  ├─ webpack.common.js
│  ├─ webpack.dev.js
│  └─ webpack.prod.js
├─ node_modules
├─ public
│  ├─ assets
│  │  └─ imgs
│  │     └─ npm.png
│  └─ index.html
├─ server
│  ├─ appConfig.js
│  ├─ index.js
│  └─ logger.js
├─ src
│  ├─ index.js
│  └─ style.css
├─ package-lock.json
└─ package.json

webpack注入变量

转载前端西瓜哥juejin.cn/post/707154…
今天介绍 webpack 的一个最常用的插件:HTML Webpack Plugin。

说它是使用 webpack 开发前端项目必不可少的插件也不为过,因为它可以自动帮我们将 webpack 打包生成的文件(比如 js 文件、css 文件)嵌入到 html 文件中。

这在生成的文件带有哈希串时尤为有用。

在 webpack 配置文件引入 HtmlWebpackPlugin 插件,然后在 plugins 数组中通过 new HtmlWebpackPlugin() 加入 HtmlWebpackPlugin 实例对象即可。

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'app.[contenthash:8].js',
  },
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin()
  ],
}

我们执行 npx webpack 命令后,webpack 额外给我们生成了一个 dist/index.html 文件。该 html 文件格式化后得到的内容为:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Webpack App</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <script defer="defer" src="app.c8b961ec13a790ae7d15.js"></script>
</head>
<body></body>
</html>

可以看到将打包好的 app.js 文件被自动嵌入到 head 元素下最后一个子元素位置。

这里打包文件名尾部被添加了内容哈希串,这意味着每次项目的内容发生变化,哈希串的值都不同。

试想下,如果你自己管理 html 文件,每次都要改这个 js 文件名,是要多累,还好有 HtmlWebpackPlugin 帮忙。

当然前面这种只是 HtmlWebpackPlugin 插件的默认用法,我们可以做更具体的定制化。

一些常用的属性

我们需要传入一个配置对象来进行模板渲染定制化。

HtmlWebpackPlugin 的配置非常丰富,不过常用的就几个。

plugins: [
  new HtmlWebpackPlugin({
    title: '前端西瓜哥的博客',
    favicon: 'static/favicon.ico',
    minify: {
      //压缩HTML文件
      removeComments: false, //保留HTML中的注释
      collapseWhitespace: true, //删除空白符与换行符
    },
  }),
],
  • titile:设置网页标题;
  • filename:生成 html 文件名,默认值为 index/html
  • template:使用自己的模板,这里填这个模板的路径,使用了之后一些配置项就无效了,比如 title;
  • favicon:指定网站图标路径,除了会在 html 上填充 favicon 相关内容,还会将该文件拷贝到打包文件夹下,非常好用;
  • minify:是否压缩 html 文件。不设置时,如果 webpack 的 mode 为 production,就会压缩 html,移除多余的空格和注释之类的。
使用自定义 html 模板

在实际开发中,通常是创建一个 index.html 提供给 HtmlWebpackPlugin 插件作为模板。

这样的话,title 等配置和一些更细碎的内容就可以直接写到 html 上。相比配置,直接在 html 上编辑要更直观些。

我们在根目录创建一个 index.html 作为模板:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>前端西瓜哥</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body></body>
</html>

这样就可以直接在 html 模板上添加 title,以及一些 cdn 形式的第三方库。

webpack.config.js 配置改为:

plugins: [
  new HtmlWebpackPlugin({
    template: 'index.html'
  }),
],

生成的 html 为:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>前端西瓜哥</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <script defer src="app.d02c9155f73c92f51bf5.js"></script>
</head>
<body></body>
</html>

第三方库建议使用自己本地项目的,会更稳定和安全些,比如上面就建议改为 <script src="static/jquery-3.6.0.min.js"></script>

这里会用到一个 copy-webpack-plugin 插件将一些文件或文件夹拷贝到打包目录下。关于这个插件我会另外专门写一篇文章讲解,这里不展开。

自定义 html 注入变量

HtmlWebpackPlugin 支持通过使用 lodash.template() 的方式注入变量。

我们将模板 html 改为下面这样:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <%= htmlWebpackPlugin.options.saySomething %>
</body>
</html>

配置改为:

plugins: [
  new HtmlWebpackPlugin({
    template: 'index.html',
    title: '前端西瓜哥的博客',
    // 下面这个是自定义属性
    saySomething: 'Stay hungry, stay foolish',
  }),
],

将传入给 HtmlWebpackPlugin 的配置属性会成为 htmlWebpackPlugin.options 对象下的属性,嵌入到模板 html 下。

所以这里的生成结果是:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>前端西瓜哥的博客</title>
<script defer src="app.d02c9155f73c92f51bf5.js"></script></head>
<body>
  Stay hungry, stay foolish
</body>
</html>

因为使用了 lodash.template 模板渲染丰富,除了可以嵌入变量的值,还支持判断条件、循环等特性,基本上可以满足我们的绝大多数场景

这里提供一个使用了相当多特性的 index.html 模板给大家参考参考:github.com/jaketrent/h…

性能优化

综合性问题,没有标准答案,但要求尽量全面,手写防抖、节流

性能优化原则

  1. 多使用内存、缓存或其他方法
  2. 减少CPU计算量、减少网络加载耗时
  3. 适用于所有编程的性能优化-空间换时间

从何入手

让加载更快

  • 减少资源体积:压缩代码
  • 减少访问次数:合并代码,SSR服务器端渲染,缓存
  • 使用更快的网络:CDN(根据区域连上服务器下载更快) 让渲染更快
  • CSS放在head,js放在body最下面
  • 尽早开始执行js,用DOMContentLoaded触发
  • 懒加载(图片懒加载,上滑加载更多)
  • 对DOM查询进行缓存
  • 频繁DOM操作,合并到一起插入DOM结构
  • 节流throttle,防抖debounce

缓存

image.png

  1. 静态资源加hash后缀,根据文件内容计算hash
  2. 文件内容不变,则hash不变,则url不变
  3. url和文件不变,则会自动触发http缓存机制,返回304
  4. 比如功能上线之后,老的文件hash不变,新文件hash变化,这种情况下只有新文件需要从新下载

CDN

  1. 专门做静态文件

SSR

  1. 服务器端渲染:将网页和数据一起加载,一起渲染
  2. 非SSR(前后端分离):先加载网页,在加载数据,在渲染数据

懒加载

image.png

  1. 先加载体积很小的预览图
  2. 当屏幕滑动到该图片时,在将data-realsrc的值赋给src

其他

image.png image.png image.png

题目

29e98babe9732f219587b6049c8e27f.png