Web性能优化_知识点精讲

JavaScript/前端
333
0
0
2023-01-17
❝歌德说:”一旦你信任了你自己,你就会明白怎样生活“ ❞

大家好,我是「柒八九」

今天,我们继续「前端面试」的知识点。我们来谈谈关于「Web性能优化」的相关知识点。

该系列的文章,大部分都是前面文章的知识点汇总,如果想具体了解相关内容,请移步相关系列,进行探讨。

好了,天不早了,干点正事哇。

img

你能所学到的知识点

  1. 延迟和宽带
  2. WebWorker
  3. 关键渲染路径
  4. React 应用中的优化处理
  5. 利用React-Profiler提升应用性能
  6. 从 URL 输入到页面加载整过程分析
  7. SPA 提速
  8. SPA: SEO

延迟和宽带

对所有「网络通信」都有决定性影响的两个方面:

  • 「延迟」 分组从信息源发送到目的地所需的时间 (单位:ms)
  • 「带宽」 逻辑或物理通信路径最大的「吞吐量」 (单位:Mbit/s)

img

延迟和带宽

延迟的构成

「延迟」是消息message 或分组packet从起点到终点经历的时间。

延迟主要分为4类。

  1. 「传播延迟」 :消息从发送端到接收端需要的时间
  2. 「传输延迟」 :把消息中的所有「比特」转移到链路中需要的时间
  3. 「处理延迟」 :处理分组首部、检查位错误及确定分组目标所需的时间
  4. 「排队延迟」 :到来的分组排队等待处理的时间
❝传播延迟/传输延迟/处理延迟/排队延迟的时间总和,就是客户端到服务器的「总延迟时间」

延迟最后一公里

延迟中相当大的一部分往往花在了「最后几公里」,而不是在横跨大洋或大陆时产生的,这就是所谓的「最后一公里」问题。

最后一公里的延迟与提供商、部署方法、网络拓扑,甚至一天中的哪个时段都有很大关系。作为最终用户,如果你想提高自己上网的速度,那选择延迟最短的 网络业务提供商Internet Service Provider(简称ISP)是最关键的。

WebWorker

JavaScript 环境实际上是运行在操作系统(OS)中的「虚拟环境」

在浏览器中每打开一个页面,就会分配一个它「自己的环境」:即每个页面都有自己的内存、事件循环、DOM。并且每个页面就相当于一个「沙盒」,不会干扰其他页面。

而使用「Worker 线程」,浏览器可以在「原始页面环境之外」再分配一个完全独立的「二级子环境」。这个子环境不能与依赖单线程交互的 API(如 DOM)互操作,但「可以与父环境并行」执行代码。

Worker的类型 (DSS)

Worker 线程规范中定义了「三种主要」的工作者线程

  1. 专用工作线程Dedicated Web Worker
  • 专用工作者线程,通常简称为工作者线程、Web WorkerWorker,是一种实用的工具,可以让脚本「单独创建」一个 JS 线程,以执行委托的任务。
  1. 共享工作线程Shared Web Worker
  2. 服务工作线程Service Worker:
  • 主要用途是「拦截」「重定向」「修改页面发出的请求」,充当「网络请求」的仲裁者的角色

专用工作线程Dedicated Web Worker

专用工作线程是最简单的 Web 工作者线程,网页中的脚本可以创建专用工作者线程来执行在「页面线程之外」的其他任务。这样的线程可以与父页面交换信息、发送网络请求、执行文件输入/输出、进行「密集计算」、处理「大量数据」,以及实现其他不适合在页面执行线程里做的任务(否则会导致页面响应迟钝)。

创建专用工作线程方式

  1. 「加载 JS 文件」
  • 即把「文件路径」提供给 Worker 构造函数,然后构造函数再在「后台异步加载」脚本并实例化工作线程
worker.js
// 进行密集计算 bala bala

main.js
const worker = new Worker( 'worker.js');
console.log(worker); // Worker {} // {3}
  1. 行内创建工作线程
  • 通过脚本字符串创建了 Blob
  • 然后又通过 Blob 创建了 URL 对象
  • 最后把URL 对象,传给了 Worker()构造函数
  1. 基于Blob
  2. 基于函数序列化

worker 引用node_module中的包

❝通过「行内构建工作线程」有一个弊端,就是无法通过import/require引入一些第三方的包。 ❞

虽然在worker中可以使用importScripts()加载任意脚本,但是那些都是在worker同目录或者是利用绝对路径进行引用。很不方便。

Webpack最为打包工具下,使用指定的loader --worker-loader可以解决上面的问题。

进行本地按照

$ npm install worker-loader --save-dev

配置webpack -config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.worker\.js$/,
        use: { loader: "worker-loader" },
      },
    ],
  },
};

通过如上的配置,我们就可以像写常规的组件或者工具方法一些,「肆无忌惮」的通过import引入第三方包。

longTime.js

const A = require('A')
self.onmessage = function (e) {
     // A处理一些特殊场景
}

服务工作线程Service Worker

服务工作线程Service Worker是一种类似浏览器中「代理服务器」的线程,可以「拦截外出请求」「缓存响应」。这可以让网页在「没有网络连接」的情况下正常使用,因为部分或全部页面可以从服务工作线程缓存中提供服务。

服务工作线程在两个主要任务上最有用:充当「网络请求的缓存层」

❝在某种意义上
  • 服务工作线程就是用于把网页变成像「原生应用程序」一样的「工具」

线程缓存

❝服务工作线程的一个主要能力是可以「通过编程方式实现真正的网络请求缓存机制」

有如下特点:

  • 线程缓存「不自动缓存」任何请求
  • 线程缓存「没有到期失效的概念」
  • 线程缓存必须「手动更新和删除」
  • 缓存「版本」必须「手动管理」
  • 「唯一」的浏览器「强制逐出策略」基于线程缓存占用的空间。

拦截 fetch 事件

❝服务工作线程「最重要」的一个特性就是「拦截网络请求」

服务工作线程作用域中的「网络请求会注册为 fetch 事件」。这种拦截能力「不限于」 fetch()方法发送的请求,也能拦截对 JavaScriptCSS、图片和HTML(包括对主 HTML 文档本身)等资源发送的请求。

这些请求可以来自 JavaScript,也可以通过 <script><link><img>标签创建。

让服务工作线程能够决定如何处理 fetch 事件的方法是 event.respondWith()。该方法接收Promise,该Promise会解决为一个 Response 对象。该 Response对象实际上来自哪里完全由服务工作线程决定。可以来自「网络」,来自「缓存」,或者「动态创建」

从网络返回

❝这个策略就是「简单地转发」 fetch 事件 ❞

那些绝对「需要发送到服务器的请求」例如 POST 请求就适合该策略。

self.onfetch = (fetchEvent) => {
 fetchEvent.respondWith(fetch(fetchEvent.request));
};

从缓存返回

❝这个策略其实就是「缓存检查」

对于任何肯定有缓存的资源(如在安装阶段缓存的资源),可以采用该策略。

self.onfetch = (fetchEvent) => {
 fetchEvent.respondWith(caches.match(fetchEvent.request));
};

从网络返回,缓存作后备

从缓存返回,网络作后备

关键渲染路径

通常一个页面有「三个阶段」

  1. 「加载阶段」
  • 是指从「发出请求到渲染出完整页面」的过程
  • 影响到这个阶段的主要因素有「网络」「JavaScript 脚本」
  1. 「交互阶段」
  • 主要是从页面加载完成到「用户交互」的整个过程
  • 影响到这个阶段的主要因素是 「JavaScript 脚本」
  1. 「关闭阶段」
  • 主要是用户发出关闭指令后页面所做的一些「清理操作」

加载阶段关键数据

文档对象模型Document Object Model

「DOM」:是HTML页面在解析后,基于对象的表现形式。

每个浏览器都「需要一些时间解析HTML」。并且,「清晰的语义标记」有助于减少浏览器解析HTML所需的时间。(不完整或者错误的语义标记,还需要浏览器根据上下文去分析和判断)

CSSOM Tree

CSSOM也是一个基于对象的树。它「负责处理与DOM树相关的样式」

❝一般来说,CSS被认为是一种渲染阻断Render-Blocking资源。 ❞

什么是「渲染阻断」?渲染阻塞资源是一个组件,它将「不允许浏览器渲染整个DOM树,直到给定的资源被完全加载」CSS 是一种渲染阻断资源,因为在CSS完全加载之前,你无法渲染树。

起初,页面中所有CSS信息都被存放在一个文件中 。现在,开发人员通过一些技术手段,能够将CSS文件「分割」开来,「只在渲染的早期阶段提供关键样式」

JS

JavaScript 是一种用来操作DOM的语言」。这些「操作花费时间」,并增加网站的整体加载时间。所有,

JavaScript 代码被称为 解析器阻塞Parser Blocking资源。 ❞

什么是「解析器阻塞」?当需要「下载」「执行」JavaScript代码时,浏览器会「暂停执行和构建DOM树」。当JavaScript代码被执行完后,DOM树的构建才继续进行。

所以才有, JavaScript是一种昂贵的资源」的说法。

记住,

❝关键渲染路径Critical Rendering Path都是「关于HTMLCSSJavascript的」

关键路径相关术语

  • 关键资源Critical Resource:所有可能「阻碍页面渲染」的资源
  • 关键路径长度Critical Path Length:获取构建页面所需的所有关键资源所需的 「RTT」(Round Trip Time)
  • 关键字节Critical Bytes:作为完成和构建页面的一部分而传输的「字节总数」

优化关键渲染路径

如果你希望优化任何框架中的关键渲染路径,你需要在上述指标上下功夫并加以改进。

  • 优化关键资源
  • JavaScriptCSS 改成内联的形式 (性能提升不是很大)
  • 如果 JavaScript 代码没有 DOM 或者 CSSOM 的操作,则可以改成 sync 或者 defer 属性
  • 首屏内容可以优先加载,非首屏内容采用「滚动加载」
  • 优化关键路径长度
  • 「压缩」 CSSJavaScript 资源
  • 移除 HTMLCSSJavaScript 文件中一些「注释内容」
  • 优化关键字节
  • 通过减少关键资源的「个数」和减少关键资源的「大小」搭配来实现
  • 使用 CDN 来减少每次 RTT 时长

处理关键资源

懒加载

加载的关键是 "懒加载"。任何媒体资源、CSSJavaScript、图像、甚至HTML都可以被懒加载。每次加载「有限的页面的内容」,可以提高关键渲染路径。

  • 不要在加载页面时加载这个整个页面的 CSSJavaScriptHTML
  • 相反,可以为一个button添加一个事件监听,只有在用户点击按钮时才加载脚本。
  • 使用Webpack来完成懒加载功能。

Async, Defer, Preload

img

当使用Preload时,它被用于HTML文件中没有的文件,但在渲染或解析JavaScript或CSS文件的时候。有了Preload,浏览器就会下载资源,在资源可用的时候就会执行。

  • 「只有在首屏页面需要的文件才可以预载」
  • 「预加载只用于<link>标签」

编写原生(Vanilla) JS,避免使用第三方脚本

优化关键路径长度

HTTP缓存

  1. 强缓存
  • ExpiresCache-control:max-age=x(强缓存)
  1. 协商缓存
  • EtagIf-None-Match (协商缓存)

JS层面做缓存处理(ServerWorker)

React 应用中的优化处理

优化被分成两个阶段。

  • 路由级别懒加载
  • React.lazy + Suspense
  1. 在应用程序被加载之前
  • 合理使用useState/setState- 防止回流
  • 利用shouldComponentUpdate()生命周期方法做浅对比
  • 使用正确的状态管理方法
  • 利用React.Memo
  1. 第二阶段是在应用加载后进行优化

利用React-Profiler提升应用性能

Profiler UI 界面

Profiler的UI界面在逻辑上可分为4个主要部分。

img

  1. 「图表类型」
  • 火焰图
  • 排序图
  1. 「图表区域」--在应用程序的剖析切片中,代表某次commit对应的组件渲染时间的相关信息。
  2. 「提交区域」--每个条形图代表应用程序在整个录制阶段所有的commit操作。每当你通过点击选择一个commit「图表区域」「提交信息」就会相应地更新。
  3. 「提交信息面板」--关于单个选定的commit阶段或单个选定组件的细节。

从 URL 输入到页面加载整过程分析

整个过程大致可以分为「三个阶段」

  1. 客户端发起请求阶段
  2. 服务端数据处理请求阶段
  3. 客户端页面渲染阶段

客户端请求阶段的瓶颈点

客户端发起请求阶段

  1. 用户在浏览器输入 URL
  2. 经过本地缓存确认是否已经存在这个网站
  3. 如果没有,接着会由 DNS 查询从域名服务器获取这个 IP 地址
  4. 客户端通过 TCP 的三次握手和TLS协商向服务器发起 HTTP 请求建立连接的过程

在这个过程中

  1. 本地缓存
  2. DNS查询
  3. HTTP 请求

很容易成为影响前端性能的瓶颈点

本地缓存

本地缓存可以让静态资源加载更快,想要让本地缓存发挥作用,就需要「先在服务器上进行配置」

本地缓存一般包括强缓存和协商缓存两种形式

「强缓存」是指浏览器在加载资源时,根据请求头的 expires/cache-control,判断是否命中客户端缓存。

  • 如果命中,则直接从缓存读取资源。

「协商缓存」是指,浏览器会先发送一个请求到服务器,通过 etag/last-modified,验证资源是否命中客户端缓存。

  • 如果命中,服务器会将这个请求返回,但不会返回这个资源的数据,依然是从缓存中读取资源;
  • 如果没有命中,无论是资源过期或者没有相关资源,都需要向服务器发起请求,等待服务器返回这个资源

DNS 查询

每进行一次 DNS 查询,都要经历从手机到移动信号塔,再到认证 DNS 服务器的过程。要节省时间,一个办法就是让 DNS 查询走缓存,浏览器提供了 DNS 预获取的接口。

HTTP 请求

HTTP 请求阶段,最大的瓶颈点来源于「请求阻塞」。所谓请求阻塞,就是浏览器为保证访问速度,会默认对同一域下的资源保持一定的连接数,请求过多就会进行阻塞

浏览器同域名的连接数限制是一般是 6 个,如果当前请求书多于 6 个,只能 6 个并发,其余的得等最先返回的请求后,才能做下一次请求

解决方式

  1. 域名规划
  • 当前页面需要用到哪些域名,最关键的首屏中需要用到哪些域名
  • 规划一下这些域名发送的顺序
  1. 域名散列
  • 通过不同的域名,增加请求并行连接数
  • 将静态服务器地址 pic.google.com,做成支持 pic0-5 的 6 个域名
  • 每次请求时随机选一个域名地址进行请求
  • 有 6 个域名同时可用,最多可以并行 36 个连接
  • 域名个数不是越多越好,太分散的话,又会涉及多域名之间无法缓存的问题

服务端数据处理阶段的瓶颈点

服务端数据处理阶段,是指 WebServer 接收到请求后,从数据存储层取到数据,再返回给前端的过程。

这个过程中的瓶颈点,就在于是否做了

  1. 数据缓存处理
  2. Gzip 压缩
  3. 重定向

数据缓存

数据缓存分为两种

  1. 接口缓存
  • 借助 Service Worker 的数据接口缓存
  • 借助本地存储的接口缓存
  1. CDN(Content Delivery Network,内容分发网络)

接口缓存

Service Worker 是浏览器的一个高级属性,本质上是一个「请求代理层」。它存在的目的就是拦截和处理网络数据请求

借助本地存储的接口缓存,在一些对数据时效性要求不高的页面,第一次请求到数据后,程序将数据存储到本地存储

  1. localStorage
  2. 客户端本身的存储

下一次请求的时候,先去缓存里面取将取数据,如果没有的话,再向服务器发起请求

CDN

通过在网络各处放置节点服务器,构造一个「智能虚拟网络」。将用户的请求导向离用户最近的服务节点上

Gzip

Gzip 压缩是一种压缩技术,「服务器端通过使用 Gzip」,传输到浏览器端的文本类资源的大小可以变为原来的 1/3 左右

重定向

所谓重定向,是指网站资源迁移到其他位置后,用户访问站点时,程序自动将用户请求从一个页面转移到另外一个页面的过程。

在服务端处理阶段,重定向分为三类

  1. 服务端发挥的302重定向
  2. META 标签实现的重定向
  3. 前端 Javasript 通过window.location 实现的重定向

它们都会引发新的 DNS 查询,导致新的 TCP 三次握手和 TLS 协商,以及产生新的 HTTP 请求。

页面解析和渲染阶段的瓶颈点

所谓解析,就是 HTML 解析器把页面内容转换为 DOM 树和 CSSOM树的过程

解析阶段

  1. DOM树
  • DOM 树全称为 Document Object Model 即文档对象模型
  • 它描述了标签之间的层次和结构
  • HTML 解析器通过词法分析获得开始和结束标签
  • 生成相应的节点和创建节点之间的父子关系结构
  • 直到完成 DOM 树的创建
  1. CSSOM树
  • 即 CSS 对象模型
  • 主要描述样式集的层次和结构
  • HTML 解析器遇到内联的 style 标签时,会触发 CSS 解析器对样式内容进行解析
  • CSS 解析器遍历其中每个规则,将 CSS 规则解析浏览器可解析和处理的样式集合
  • 最终结合浏览器里面的默认样式,汇总形成具有父子关系的 CSSOM 树

渲染阶段

主线程会计算 DOM 节点的最终样式,生成布局树。布局树会记录参与页面布局的节点和样式 。完成布局后,紧跟着就是绘制。

绘制就是把各个节点绘制到屏幕上的过程,绘制结果以层的方式保存

构建 DOM 树的瓶颈点

解析器构建 DOM 树的过程中, 有三点会严重影响前端性能

  1. HTML 标签不满足 Web 语义化时
  • 浏览器就需要更多时间去解析 DOM 标签的含义
  • 比如将 <br> 写成了 </br>,又或者表格嵌套不标准,标签层次结构复杂等
  1. DOM 节点的数量多
  2. 文档中包含<SCRIPT> 标签时的情况
  • 无论是 DOM 或者 CSSOM 都可以被 JavaScript 所访问并修改
  • 一旦在页面解析时遇到 <SCRIPT> 标签,DOM 的构造过程就会暂停,等待服务器请求脚本
  • 在脚本加载完成后,还要等取回所有的 CSS 及完成 CSSOM 之后才继续执行
  • 可以通过使用 deferasync,告诉浏览器在等待脚本下载期间不阻止解析过程

布局中的瓶颈点--重排

SPA 提速

监控 SPA 性能

  1. Lighthouse:一个开源的「自动化工具」,用于改进网络应用的质量
  2. React Performance Devtools:针对 React.js 项目的优化插件

这些工具的弊端是,他们不能准确的测出 SPA 应用的「加载速度」

为了能够真正的测出 SPA 的真实加载速度,在Chrome 中也存在一些「子工具」(如:Speed Index)用于模拟用户真正的上网过程。

但是,真实的用户操作受各种设备和网络影响,很难利用单一的插件和工具进行模拟。

所以,我们可以使用 真实用户模拟Real User Monitoring(RUM)对应用就行处理。他能很好的跟踪用户在网页中的各种操作并且能够给出网站的实时加载数据情况。

这里列出一些针对SPARUM工具

  1. Sentry: 日志、性能收集 (多平台)
  2. Dynatrace
  3. Catchpoint
  4. ....

提升 SPA 性能(6种)

  1. 优先渲染「首屏」页面信息
  2. 非必要数据的「懒加载」
  3. 「缓存静态内容」
  4. 对实时性较强的应用使用WebSocket
  5. 使用JSONP/CORS绕过同源策略
  6. CDN处理

img

优先渲染首屏页面信息

  1. 针对非首屏页面的「惰性渲染」
  2. 每个组件赋予不同的「渲染优先级」

提高Frist Meaningful Paint (FMP)的指标。 => 「缩短」了用户能够看到页面「核心内容」的时间。

通过对「不可见元素的过滤渲染」也能提高Time to Interactive(TTL)的性能指标。

非必要数据的懒加载

发现「转换阶段」也可能存在性能瓶颈。在此阶段,SPA加载数据并且对数据进行序列化Normalizes处理,然后将处理完的数据「存入到内存」中。

可以使用一个「高优先级」调用来获取First Meaningful Paint所需的数据,并使用另一个回调来「惰性加载」页面所需的其余数据。

一些SPA框架,例如(React/Vue)是允许开发者将应用「代码分割」成很多bundles

❝所以,对一些非必要的bundles进行「按需加载」或者「延迟处理」。该方法可以加速「第一次导航」。 ❞

缓存静态内容

对你的SPA进行审查,从中甄别出可以在用户设备中被「缓存」的图片或者其他的静态资源。

  1. 使用某种类型的分页并依赖于服务器来实现持久性
  2. 编写LRU算法来从存储中删除多余的项
  3. 使用Service Workers在SPA中缓存静态内容
  4. 使用IndexedDB API缓存大量「结构化」的数据

对实时性较强的应用使用WebSocket

WebSocket 可以实现客户端与服务器间双向、基于消息的文本或二进制数据传输。它是浏览器中最靠近套接字的 API。 ❞

与HTTP不同,客户端不必不断地向服务器发送请求以获取新消息。相反,浏览器只需监听服务器,并在准备好时接收消息。

img

使用JSONP/CORS绕过同源策略

大部分应用需要「从第三方获取数据」

但是,由于同源策略,不能对非同源的第三方服务进行AJAX调用。

为了能够访问第三方网站,应用需要利用origin server作为代理。

❝额外的「往返」意味着更多的延迟。

img

如果不处理检索到的数据,也不将其存储在系统中,则可以直接请求资源。为此,可以使用JSONP或跨来源资源共享(CORS)进行数据获取。

JSONP

  1. 第一步
  • 「网页」添加一个<script>元素,向服务器请求一个脚本
  • 请求的脚本网址有一个callback参数(?callback=bar),用来告诉服务器,客户端的回调函数名称(bar
<script src="http://XX.com?callback=bar"></script>
  1. 第二步
  • 服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(bar({...})
  1. 第三步
  • 客户端会将服务器返回的字符串,作为「代码解析」,因为浏览器认为,这是<script>标签请求的脚本内容。
  • 这时,客户端只要定义了bar()函数,就能在该函数体内,拿到服务器返回的 JSON 数据。
JSONP 只能是GET请求 ❞

同时,我们可以使用asyncdefer 属性来对<script>进行优化处理。

img

属性

解释

没有 defer 或 async

浏览器会「立即加载并执行」指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素「之前」,也就是说不等待后续载入的文档元素,「读到就加载并执行」

async

加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)

defer

加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的「执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成」

CORS

CORS 是跨源资源分享Cross-Origin Resource Sharing的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。

但是,「除了」GETHEADPOST之外,使用任何方法的请求都会发起一个预检请求Preflight Check,以确认服务器已经为跨源请求做好了准备。

=> 预备请求
OPTIONS /resource.js HTTP/1.1  
     Host: thirdparty.com 
     Origin: http://example.com 
     Access-Control-Request-Method: POST 
     Access-Control-Request-Headers: My-Custom-Header 
     ...
     
<= 预备响应
HTTP/1.1 200 OK    
     Access-Control-Allow-Origin: http://example.com 
     Access-Control-Allow-Methods: GET, POST, PUT 
     Access-Control-Allow-Headers: My-Custom-Header 
     ...
(正式的 HTTP 请求)  

  • ① 验证许可的预备 「OPTIONS」 请求
  • ② 第三方源的成功预备响应
  • ③ 实际的 CORS 请求

「预检请求」多了一次往返时间,无形中加大了请求的延迟时间。

CDN处理

CDN 是内容交付网络Content Delivery Networks 的英文首字母缩写,是一组分布在「不同地理位置」的服务器,它「将 Web 内容存放在更靠近用户的位置,从而加速 Web 内容的交付」

CDN 将网页、图像和视频等内容缓存在靠近你的实际地点的「代理服务器」中。

❝把 CDN 想成是一部 ATM 机。如今几乎每个街角都有提款机,让我们可以快速高效地提取现金。 ❞

「为SPA使用CDN意味着更快地加载脚本和减少交互时间」

SPA: SEO

  1. JS框架 + SSR
  2. 使用渐进增强和特性探测
  3. 列出网站完整的页面列表 Sitemap.xml
  4. 使用rel=canonical的连接
  5. TDK 的优化处理
  • tilte/keywords/description可以在HTML的<meta>标签内定义。
  • title「权重最高」,利用title提高页面权重
  • keywords 相对权重较低,作为页面的「辅助关键词」搜索
  • description 的描述一般会直接显示在搜索结果的介绍中

后记

「分享是一种态度」

「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」