万字长文:分享前端性能优化知识体系,我就不信你还听不明白了

  1. 服务器端渲染

  2. 静态资源使用CDN

  3. 资源缓存,不重复加载相同的资源

从上面几个优化点出发,有以下几种实现性能优化的方式。

1.DNS 预解析

DNS 作为互联网的基础协议,其解析的速度似乎容易被网站优化人员忽视。现在大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析耗费20-120毫秒,减少DNS解析时间和次数是个很好的优化方式。DNS Prefetching是具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能减少用户的等待时间,提升用户体验。

浏览器对网站第一次的域名DNS解析查找流程依次为:

浏览器缓存 ->系统缓存 ->路由器缓存 ->ISP DNS缓存 ->递归搜索

DNS预解析的实现:

用meta信息来告知浏览器, 当前页面要做DNS预解析:

在页面header中使用link标签来强制对DNS预解析:

注意:dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数。

2.使用HTTP2

HTTP2带来了非常大的加载优化,所以在做优化上首先就想到了用HTTP2代替HTTP1。

HTTP2相对于HTTP1有这些优点:

解析速度快

服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,因为 HTTP2 是基于帧的协议,每个帧都有表示帧长度的字段。

多路复用

在 HTTP2 上,多个请求可以共用一个 TCP 连接,这称为多路复用。

当然HTTP1.1有一个可选的Pipelining技术,说的意思是当一个HTTP连接在等待接收响应时可以通过这个连接发送其他请求。听起来很棒,其实这里有一个坑,处理响应是按照顺序的,也就是后发的请求有可能被先发的阻塞住,也正因此很多浏览器默认是不开启Pipelining的。

HTTP1 的Pipelining技术会有阻塞的问题,HTTP/2的多路复用可以粗略的理解为非阻塞版的Pipelining。即可以同时通过一个HTTP连接发送多个请求,谁先响应就先处理谁,这样就充分的压榨了TCP这个全双工管道的性能。加载性能会是HTTP1的几倍,需要加载的资源越多越明显。当然多路复用是建立在加载的资源在同一域名下,不同域名神仙也复用不了。

首部压缩

HTTP2 提供了首部压缩功能。(这部分了解一下就行)

HTTP 1.1请求的大小变得越来越大,有时甚至会大于TCP窗口的初始大小,因为它们需要等待带着ACK的响应回来以后才能继续被发送。HTTP/2对消息头采用HPACK(专为http/2头部设计的压缩格式)进行压缩传输,能够节省消息头占用的网络的流量。而HTTP/1.x每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。

服务器推送

服务端可以在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。

3.减少HTTP请求数量

HTTP请求建立和释放需要时间。

HTTP请求从建立到关闭一共经过以下步骤:

  1. 客户端连接到Web服务器

  2. 发送HTTP请求

  3. 服务器接受请求并返回HTTP响应

  4. 释放连接TCP链接

这些步骤都是需要花费时间的,在网络情况差的情况下,花费的时间更长。如果页面的资源非常碎片化,每个HTTP请求只带回来几K甚至不到1K的数据(比如各种小图标)那性能是非常浪费的。

4.压缩、合并文件

  • 压缩文件 -> 减少HTTP请求大小,可以减少请求时间

  • 文件合并 -> 减少HTTP请求数量。

我们可以对html、css、js以及图片资源进行压缩处理,现在可以很方便的使用 webpack 实现文件的压缩:

  • js压缩:UglifyPlugin
  • CSS压缩:MiniCssExtractPlugin
  • HTML压缩:HtmlWebpackPlugin
  • 图片压缩:image-webpack-loader

提取公共代码

合并文件虽然能减少HTTP请求数量, 但是并不是文件合并越多越好,还可以考虑按需加载方式(后面第6点有讲到)。什么样的文件可以合并呢?可以提取项目中多次使用到的公共代码进行提取,打包成公共模块。

可以使用 webpack4 的 splitChunk 插件 cacheGroups 选项。

optimization: {

runtimeChunk: {

name: ‘manifest’ // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。

},

splitChunks: {

cacheGroups: {

vendor: {

name: ‘chunk-vendors’,

test: /[\/]node_modules[\/]/,

priority: -10,

chunks: ‘initial’

},

common: {

name: ‘chunk-common’,

minChunks: 2,

priority: -20,

chunks: ‘initial’,

reuseExistingChunk: true

}

},

}

},

5.采用svg图片或者字体图标

因为字体图标或者SVG是矢量图,代码编写出来的,放大不会失真,而且渲染速度快。字体图标使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便,还有一个优点是生成的文件特别小。

6.按需加载代码,减少冗余代码

按需加载

在开发SPA项目时,项目中经常存在十几个甚至更多的路由页面, 如果将这些页面都打包进一个JS文件, 虽然减少了HTTP请求数量, 但是会导致文件比较大,同时加载了大量首页不需要的代码,有些得不偿失,这时候就可以使用按需加载, 将每个路由页面单独打包为一个文件,当然不仅仅是路由可以按需加载。

根据文件内容生成文件名,结合 import 动态引入组件实现按需加载:

通过配置 output 的 filename 属性可以实现这个需求。filename 属性的值选项中有一个 [contenthash],它将根据文件内容创建出唯一 hash。当文件内容发生变化时,[contenthash] 也会发生变化。

output: {

filename: ‘[name].[contenthash].js’,

chunkFilename: ‘[name].[contenthash].js’,

path: path.resolve(__dirname, ‘…/dist’),

},

减少冗余代码

一方面避免不必要的转义:babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件,其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'

其次减少ES6 转为 ES5 的冗余代码:Babel 转化后的代码想要实现和原来代码一样的功能需要借助一些帮助函数,比如:

class Person {}

会被转换为:

“use strict”;

function _classCallCheck(instance, Constructor) {

if (!(instance instanceof Constructor)) {

throw new TypeError(“Cannot call a class as a function”);

}

}

var Person = function Person() {

_classCallCheck(this, Person);

};

这里 _classCallCheck 就是一个 helper 函数,如果在很多文件里都声明了类,那么就会产生很多个这样的 helper 函数。

这里的 @babel/runtime 包就声明了所有需要用到的帮助函数,而 @babel/plugin-transform-runtime 的作用就是将所有需要 helper 函数的文件,从 @babel/runtime包 引进来:

“use strict”;

var _classCallCheck2 = require(“@babel/runtime/helpers/classCallCheck”);

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) {

return obj && obj.__esModule ? obj : { default: obj };

}

var Person = function Person() {

(0, _classCallCheck3.default)(this, Person);

};

这里就没有再编译出 helper 函数 classCallCheck 了,而是直接引用了@babel/runtime 中的 helpers/classCallCheck

  • 安装

npm i -D @babel/plugin-transform-runtime @babel/runtime使用 在 .babelrc 文件中

“plugins”: [

“@babel/plugin-transform-runtime”

]

7.服务器端渲染

客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。

服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。

优点:首屏渲染快,SEO 好。缺点:配置麻烦,增加了服务器的计算压力。

8. 使用 Defer 加载JS

尽量将 CSS 放在文件头部,JavaScript 文件放在底部

所有放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染。如果这些 CSS 和 JS 需要加载和解析很久的话,那么页面就空白了。所以 JS 文件要放在底部,等 HTML 解析完了再加载 JS 文件。

那为什么 CSS 文件还要放在头部呢?

因为先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了避免这种情况发生,就要将 CSS 文件放在头部了。

另外,JS 文件也不是不可以放在头部,只要给 script 标签加上 defer 属性就可以了,异步下载,延迟执行。

9. 静态资源使用 CDN

用户与服务器的物理距离对响应时间也有影响。把内容部署在多个地理位置分散的服务器上能让用户更快地载入页面, CDN就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。

69eb561065e30d782c25ba384e990971.png

10. 图片优化

雪碧图

图片可以合并么?当然。最为常用的图片合并场景就是雪碧图(Sprite)。

在网站上通常会有很多小的图标,不经优化的话,最直接的方式就是将这些小图标保存为一个个独立的图片文件,然后通过 CSS 将对应元素的背景图片设置为对应的图标图片。这么做的一个重要问题在于,页面加载时可能会同时请求非常多的小图标图片,这就会受到浏览器并发 HTTP 请求数的限制。

雪碧图的核心原理在于设置不同的背景偏移量,大致包含两点:

  • 不同的图标元素都会将 background-url 设置为合并后的雪碧图的 uri;

  • 不同的图标通过设置对应的 background-position 来展示大图中对应的图标部分。你可以用 Photoshop 这类工具自己制作雪碧图。当然比较推荐的还是将雪碧图的生成集成到前端自动化构建工具中,例如在 webpack 中使用 webpack-spritesmith,或者在 gulp 中使用 gulp.spritesmith。它们两者都是基于 spritesmith 这个库。

图片懒加载

一般来说,我们访问网站页面时,其实很多图片并不在首屏中,如果我们都加载的话,相当于是加载了用户不一定会看到图片, 这显然是一种浪费。解决的核心思路就是懒加载:实现方式就是先不给图片设置路径,当图片出现在浏览器可视区域时才设置真正的图片路径。

实现上就是先将图片路径设置给original-src,当页面不可见时,图片不会加载:

通过监听页面滚动,等页面可见时设置图片src:

const img = document.querySelector(‘img’)

img.src = img.getAttribute(“original-src”)

如果想使用懒加载,还可以借助一些已有的工具库,例如 aFarkas/lazysizes、verlok/lazyload、tuupola/lazyload 等。

css中图片懒加载

除了对于 <img> 元素的图片进行来加载,在 CSS 中使用的图片一样可以懒加载,最常见的场景就是 background-url

.login {

background-url: url(/static/img/login.png);

}

对于上面这个样式规则,如果不应用到具体的元素,浏览器不会去下载该图片。所以你可以通过切换 className 的方式,放心得进行 CSS 中图片的懒加载。

运行时性能优化


1. 减少重绘与重排

有前端经验的开发者对这个概念一定不会陌生,浏览器下载完页面需要的所有资源后, 就开始渲染页面,主要经历这5个过程:

  1. 解析HTML生成DOM树

  2. 解析CSS生成CSSOM规则树

  3. 将DOM树与CSSOM规则树合并生成Render(渲染)树

  4. 遍历Render(渲染)树开始布局, 计算每一个节点的位置大小信息

  5. 将渲染树每个节点绘制到屏幕上

922aae4c0b2fbcc96b1f20cae8907ab3.png

浏览器渲染过程

重排

当改变DOM元素位置或者大小时, 会导致浏览器重新生成Render树, 这个过程叫重排

重绘

当重新生成渲染树后, 将要将渲染树每个节点绘制到屏幕, 这个过程叫重绘。

重排触发时机

重排发生后的根本原理就是元素的几何属性发生改变, 所以从能够改变几何属性的角度入手:

  • 添加|删除可见的DOM元素

  • 元素位置发生改变

  • 元素本省的尺寸发生改变

  • 内容变化

  • 页面渲染器初始化

  • 浏览器窗口大小发生改变

二者关系:重排会导致重绘, 但是重绘不会导致重排

了解了重排和重绘这两个概念,我们还要知道重排和重绘的开销都是非常昂贵的,如果不停的改变页面的布局,就会造成浏览器消耗大量的开销在进行页面的计算上,这样容易造成页面卡顿。那么回到我们的问题如何减少重绘与重排呢?

1.1 避免table布局
  • 不要使用table布局,可能很小的一个改动会造成整个table重新布局
1.2 分离读写操作

DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。

// bad 强制刷新 触发四次重排+重绘

div.style.left = div.offsetLeft + 1 + ‘px’;

div.style.top = div.offsetTop + 1 + ‘px’;

div.style.right = div.offsetRight + 1 + ‘px’;

div.style.bottom = div.offsetBottom + 1 + ‘px’;

// good 缓存布局信息 相当于读写分离 触发一次重排+重绘

var curLeft = div.offsetLeft;

var curTop = div.offsetTop;

var curRight = div.offsetRight;

var curBottom = div.offsetBottom;

div.style.left = curLeft + 1 + ‘px’;

div.style.top = curTop + 1 + ‘px’;

div.style.right = curRight + 1 + ‘px’;

div.style.bottom = curBottom + 1 + ‘px’;

1.3 样式集中改变

不要频发的操作样式,虽然现在大部分浏览器有渲染队列优化,但是在一些老版本的浏览器仍然存在效率低下的问题:

// 三次重排

div.style.left = ‘10px’;

div.style.top = ‘10px’;

div.style.width = ‘20px’;

// 一次重排

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

文末

逆水行舟不进则退,所以大家要有危机意识。

同样是干到35岁,普通人写业务代码划水,榜样们深度学习拓宽视野晋升管理。

这也是为什么大家都说35岁是程序员的门槛,很多人迈不过去,其实各行各业都是这样都会有个坎,公司永远都缺的高级人才,只用这样才能在大风大浪过后,依然闪耀不被公司淘汰不被社会淘汰。

为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师核心知识笔记》电子稿文件。

内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

269页《前端大厂面试宝典》

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端面试题汇总

习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-hXpq1gDt-1712049761282)]

文末

逆水行舟不进则退,所以大家要有危机意识。

同样是干到35岁,普通人写业务代码划水,榜样们深度学习拓宽视野晋升管理。

这也是为什么大家都说35岁是程序员的门槛,很多人迈不过去,其实各行各业都是这样都会有个坎,公司永远都缺的高级人才,只用这样才能在大风大浪过后,依然闪耀不被公司淘汰不被社会淘汰。

为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师核心知识笔记》电子稿文件。

内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

269页《前端大厂面试宝典》

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端面试题汇总

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL多数据源是指在一个应用程序中同时使用多个不同的MySQL数据库来存储和管理数据的技术。它可以帮助开发人员更灵活地处理各种数据库操作,提高程序的性能和可扩展性。下面是一个完整的MySQL多数据源教程。 一、设置数据库连接信息 1. 在应用程序的配置件中,创建多个数据库连接的配置项。例如,可以为每个数据源创建一个配置项,分别命名为db1、db2等。 2. 在配置项中,设置每个数据源的连接信息,包括数据库地址、用户名、密码等。 二、创建数据源管理器 1. 创建一个数据源管理器类,用于管理多个数据源。该类需要实现数据源的动态切换和获取。 2. 使用Java的线程安全的数据结构,如ConcurrentHashMap来存储数据源信息。将配置件中的数据库连接信息加载到数据结构中。 3. 实现方法来切换不同的数据源,通过传入数据源的名称来切换到对应的数据库。 三、实现数据源切换 1. 在应用程序中,根据业务需求选择需要使用的数据源。可以通过调用数据源管理器的方法来切换数据源。 2. 在DAO层的代码中,根据当前使用的数据源名称,选择对应的数据源进行数据库操作。 四、使用多数据源进行数据库操作 1. 在DAO层的代码中,区分不同的数据源,并将数据库操作的代码包装在对应的数据源中。 2. 在业务层的代码中,调用DAO层的方法来进行数据库操作。不同的数据源会自动切换。 五、处理事务 1. 如果需要在一个事务中操作多个数据源,可以使用分布式事务的方式来处理。 2. 可以使用开源的分布式事务框架,如Atomikos、Bitronix等来实现多数据源的事务管理。 六、监控和维护 1. 使用监控工具来监控多个数据源的使用情况,包括连接数、查询次数等。 2. 定期对数据库进行维护,包括索引优化、数据清理等工作,以保证数据库的性能和稳定性。 通过以上步骤,我们可以实现MySQL多数据源的配置和使用。使用多数据源可以更好地管理和处理不同的数据库操作,在提高程序性能和可扩展性的同时,也提供了更灵活的数据操作方式。同时,需要注意合理选择和配置数据源,以及监控和维护数据库,以保证系统的运行效率和数据的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值