web前端 性能優(yōu)化能力

為什么要做性能優(yōu)化?性能優(yōu)化到底有多重要? 網(wǎng)站的性能優(yōu)化對于用戶的留存率、轉(zhuǎn)化率有很大的影響,所以對于前端開發(fā)來說性能優(yōu)化能力也是重要的考察點。

性能優(yōu)化的點非常的多,有的小伙伴覺得記起來非常的麻煩,所以這里主要梳理出一條線來幫助記憶。

可以將性能優(yōu)化分為兩個大的分類:

  • 加載時優(yōu)化
  • 運行時優(yōu)化

加載時性能

顧名思義加載時優(yōu)化 主要解決的就是讓一個網(wǎng)站加載過程更快,比如壓縮文件大小、使用CDN加速等方式可以優(yōu)化加載性能。檢查加載性能的指標(biāo)一般看:白屏?xí)r間和首屏?xí)r間:

  • 白屏?xí)r間:指的是從輸入網(wǎng)址, 到頁面開始顯示內(nèi)容的時間。
  • 首屏?xí)r間:指從輸入網(wǎng)址, 到首屏頁面內(nèi)容渲染完畢的時間。

白屏?xí)r間計算

將代碼腳本放在 </head> 前面就能獲取白屏?xí)r間:

<script>    new Date().getTime() - performance.timing.navigationStart</script>

首屏?xí)r間計算

window.onload事件中執(zhí)行以下代碼,可以獲取首屏?xí)r間:

new Date().getTime() - performance.timing.navigationStart

運行時性能

運行時性能是指頁面運行時的性能表現(xiàn),而不是頁面加載時的性能。可以通過chrome開發(fā)者工具中的 Performance 面板來分析頁面的運行時性能。關(guān)于chrome開發(fā)者工具具體如何操作以及如何查看性能,可以看這篇文章性能優(yōu)化篇——運行時性能分析

接下來就從加載時性能和運行時性能兩個方面來討論網(wǎng)站優(yōu)化具體應(yīng)該怎么做。

加載時性能優(yōu)化

我們知道瀏覽器如果輸入的是一個網(wǎng)址,首先要交給DNS域名解析 -> 找到對應(yīng)的IP地址 -> 然后進行TCP連接 -> 瀏覽器發(fā)送HTTP請求 -> 服務(wù)器接收請求 -> 服務(wù)器處理請求并返回HTTP報文 -> 以及瀏覽器接收并解析渲染頁面。從這一過程中,其實就可以挖出優(yōu)化點,縮短請求的時間,從而去加快網(wǎng)站的訪問速度,提升性能。

這個過程中可以提升性能的優(yōu)化的點:

  1. DNS解析優(yōu)化,瀏覽器訪問DNS的時間就可以縮短
  2. 使用HTTP2
  3. 減少HTTP請求數(shù)量
  4. 減少http請求大小
  5. 服務(wù)器端渲染
  6. 靜態(tài)資源使用CDN
  7. 資源緩存,不重復(fù)加載相同的資源

從上面幾個優(yōu)化點出發(fā),有以下幾種實現(xiàn)性能優(yōu)化的方式。

1.DNS 預(yù)解析

DNS 作為互聯(lián)網(wǎng)的基礎(chǔ)協(xié)議,其解析的速度似乎容易被網(wǎng)站優(yōu)化人員忽視。現(xiàn)在大多數(shù)新瀏覽器已經(jīng)針對DNS解析進行了優(yōu)化,典型的一次DNS解析耗費20-120毫秒,減少DNS解析時間和次數(shù)是個很好的優(yōu)化方式。DNS Prefetching是具有此屬性的域名不需要用戶點擊鏈接就在后臺解析,而域名解析和內(nèi)容載入是串行的網(wǎng)絡(luò)操作,所以這個方式能減少用戶的等待時間,提升用戶體驗。

瀏覽器對網(wǎng)站第一次的域名DNS解析查找流程依次為:

瀏覽器緩存 ->系統(tǒng)緩存 ->路由器緩存 ->ISP DNS緩存 ->遞歸搜索

DNS預(yù)解析的實現(xiàn):

用meta信息來告知瀏覽器, 當(dāng)前頁面要做DNS預(yù)解析:

<meta http-equiv="x-dns-prefetch-control" content="on" />

在頁面header中使用link標(biāo)簽來強制對DNS預(yù)解析:

<link rel="dns-prefetch"  />

注意:dns-prefetch需慎用,多頁面重復(fù)DNS預(yù)解析會增加重復(fù)DNS查詢次數(shù)。

2.使用HTTP2

HTTP2帶來了非常大的加載優(yōu)化,所以在做優(yōu)化上首先就想到了用HTTP2代替HTTP1。

HTTP2相對于HTTP1有這些優(yōu)點:

解析速度快

服務(wù)器解析 HTTP1.1 的請求時,必須不斷地讀入字節(jié),直到遇到分隔符 CRLF 為止。而解析 HTTP2 的請求就不用這么麻煩,因為 HTTP2 是基于幀的協(xié)議,每個幀都有表示幀長度的字段。

多路復(fù)用

在 HTTP2 上,多個請求可以共用一個 TCP 連接,這稱為多路復(fù)用。

當(dāng)然HTTP1.1有一個可選的Pipelining技術(shù),說的意思是當(dāng)一個HTTP連接在等待接收響應(yīng)時可以通過這個連接發(fā)送其他請求。聽起來很棒,其實這里有一個坑,處理響應(yīng)是按照順序的,也就是后發(fā)的請求有可能被先發(fā)的阻塞住,也正因此很多瀏覽器默認是不開啟Pipelining的。

HTTP1 的Pipelining技術(shù)會有阻塞的問題,HTTP/2的多路復(fù)用可以粗略的理解為非阻塞版的Pipelining。即可以同時通過一個HTTP連接發(fā)送多個請求,誰先響應(yīng)就先處理誰,這樣就充分的壓榨了TCP這個全雙工管道的性能。加載性能會是HTTP1的幾倍,需要加載的資源越多越明顯。當(dāng)然多路復(fù)用是建立在加載的資源在同一域名下,不同域名神仙也復(fù)用不了。

首部壓縮

HTTP2 提供了首部壓縮功能。(這部分了解一下就行)

HTTP 1.1請求的大小變得越來越大,有時甚至?xí)笥赥CP窗口的初始大小,因為它們需要等待帶著ACK的響應(yīng)回來以后才能繼續(xù)被發(fā)送。HTTP/2對消息頭采用HPACK(專為http/2頭部設(shè)計的壓縮格式)進行壓縮傳輸,能夠節(jié)省消息頭占用的網(wǎng)絡(luò)的流量。而HTTP/1.x每次請求,都會攜帶大量冗余頭信息,浪費了很多帶寬資源。

服務(wù)器推送

服務(wù)端可以在發(fā)送頁面HTML時主動推送其它資源,而不用等到瀏覽器解析到相應(yīng)位置,發(fā)起請求再響應(yīng)。

3.減少HTTP請求數(shù)量

HTTP請求建立和釋放需要時間。

HTTP請求從建立到關(guān)閉一共經(jīng)過以下步驟:

  1. 客戶端連接到Web服務(wù)器
  2. 發(fā)送HTTP請求
  3. 服務(wù)器接受請求并返回HTTP響應(yīng)
  4. 釋放連接TCP鏈接

這些步驟都是需要花費時間的,在網(wǎng)絡(luò)情況差的情況下,花費的時間更長。如果頁面的資源非常碎片化,每個HTTP請求只帶回來幾K甚至不到1K的數(shù)據(jù)(比如各種小圖標(biāo))那性能是非常浪費的。

4.壓縮、合并文件

  • 壓縮文件 -> 減少HTTP請求大小,可以減少請求時間
  • 文件合并 -> 減少HTTP請求數(shù)量。

我們可以對html、css、js以及圖片資源進行壓縮處理,現(xiàn)在可以很方便的使用 webpack 實現(xiàn)文件的壓縮:

  • js壓縮:UglifyPlugin
  • CSS壓縮:MiniCssExtractPlugin
  • HTML壓縮:HtmlWebpackPlugin
  • 圖片壓縮:image-webpack-loader

提取公共代碼

合并文件雖然能減少HTTP請求數(shù)量, 但是并不是文件合并越多越好,還可以考慮按需加載方式(后面第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圖片或者字體圖標(biāo)

因為字體圖標(biāo)或者SVG是矢量圖,代碼編寫出來的,放大不會失真,而且渲染速度快。字體圖標(biāo)使用時就跟字體一樣,可以設(shè)置屬性,例如 font-size、color 等等,非常方便,還有一個優(yōu)點是生成的文件特別小。

6.按需加載代碼,減少冗余代碼

按需加載

在開發(fā)SPA項目時,項目中經(jīng)常存在十幾個甚至更多的路由頁面, 如果將這些頁面都打包進一個JS文件, 雖然減少了HTTP請求數(shù)量, 但是會導(dǎo)致文件比較大,同時加載了大量首頁不需要的代碼,有些得不償失,這時候就可以使用按需加載, 將每個路由頁面單獨打包為一個文件,當(dāng)然不僅僅是路由可以按需加載。

根據(jù)文件內(nèi)容生成文件名,結(jié)合 import 動態(tài)引入組件實現(xiàn)按需加載:

通過配置 output 的 filename 屬性可以實現(xiàn)這個需求。filename 屬性的值選項中有一個 [contenthash],它將根據(jù)文件內(nèi)容創(chuàng)建出唯一 hash。當(dāng)文件內(nèi)容發(fā)生變化時,[contenthash] 也會發(fā)生變化。

output: {    filename: '[name].[contenthash].js',    chunkFilename: '[name].[contenthash].js',    path: path.resolve(__dirname, '../dist'),},

減少冗余代碼

一方面避免不必要的轉(zhuǎn)義:babel-loaderincludeexclude 來幫我們避免不必要的轉(zhuǎn)譯,不轉(zhuǎn)譯node_moudules中的js文件,其次在緩存當(dāng)前轉(zhuǎn)譯的js文件,設(shè)置loader: 'babel-loader?cacheDirectory=true'

其次減少ES6 轉(zhuǎn)為 ES5 的冗余代碼:Babel 轉(zhuǎn)化后的代碼想要實現(xiàn)和原來代碼一樣的功能需要借助一些幫助函數(shù),比如:

class Person {}

會被轉(zhuǎn)換為:

"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 函數(shù),如果在很多文件里都聲明了類,那么就會產(chǎn)生很多個這樣的 helper 函數(shù)。

這里的 @babel/runtime 包就聲明了所有需要用到的幫助函數(shù),而 @babel/plugin-transform-runtime 的作用就是將所有需要 helper 函數(shù)的文件,從 @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 函數(shù) classCallCheck 了,而是直接引用了@babel/runtime 中的 helpers/classCallCheck。

  • 安裝

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

"plugins": [        "@babel/plugin-transform-runtime"]

7.服務(wù)器端渲染

客戶端渲染: 獲取 HTML 文件,根據(jù)需要下載 JavaScript 文件,運行文件,生成 DOM,再渲染。

服務(wù)端渲染:服務(wù)端返回 HTML 文件,客戶端只需解析 HTML。

優(yōu)點:首屏渲染快,SEO 好。缺點:配置麻煩,增加了服務(wù)器的計算壓力。

8. 使用 Defer 加載JS

盡量將 CSS 放在文件頭部,JavaScript 文件放在底部

所有放在 head 標(biāo)簽里的 CSS 和 JS 文件都會堵塞渲染。如果這些 CSS 和 JS 需要加載和解析很久的話,那么頁面就空白了。所以 JS 文件要放在底部,等 HTML 解析完了再加載 JS 文件。

那為什么 CSS 文件還要放在頭部呢?

因為先加載 HTML 再加載 CSS,會讓用戶第一時間看到的頁面是沒有樣式的、“丑陋”的,為了避免這種情況發(fā)生,就要將 CSS 文件放在頭部了。

另外,JS 文件也不是不可以放在頭部,只要給 script 標(biāo)簽加上 defer 屬性就可以了,異步下載,延遲執(zhí)行。

9. 靜態(tài)資源使用 CDN

用戶與服務(wù)器的物理距離對響應(yīng)時間也有影響。把內(nèi)容部署在多個地理位置分散的服務(wù)器上能讓用戶更快地載入頁面, CDN就是為了解決這一問題,在多個位置部署服務(wù)器,讓用戶離服務(wù)器更近,從而縮短請求時間。

圖片

10. 圖片優(yōu)化

雪碧圖

圖片可以合并么?當(dāng)然。最為常用的圖片合并場景就是雪碧圖(Sprite)。

在網(wǎng)站上通常會有很多小的圖標(biāo),不經(jīng)優(yōu)化的話,最直接的方式就是將這些小圖標(biāo)保存為一個個獨立的圖片文件,然后通過 CSS 將對應(yīng)元素的背景圖片設(shè)置為對應(yīng)的圖標(biāo)圖片。這么做的一個重要問題在于,頁面加載時可能會同時請求非常多的小圖標(biāo)圖片,這就會受到瀏覽器并發(fā) HTTP 請求數(shù)的限制。

雪碧圖的核心原理在于設(shè)置不同的背景偏移量,大致包含兩點:

  • 不同的圖標(biāo)元素都會將 background-url 設(shè)置為合并后的雪碧圖的 uri;
  • 不同的圖標(biāo)通過設(shè)置對應(yīng)的 background-position 來展示大圖中對應(yīng)的圖標(biāo)部分。你可以用 Photoshop 這類工具自己制作雪碧圖。當(dāng)然比較推薦的還是將雪碧圖的生成集成到前端自動化構(gòu)建工具中,例如在 webpack 中使用 webpack-spritesmith,或者在gulp 中使用 gulp.spritesmith。它們兩者都是基于 spritesmith 這個庫。

圖片懶加載

一般來說,我們訪問網(wǎng)站頁面時,其實很多圖片并不在首屏中,如果我們都加載的話,相當(dāng)于是加載了用戶不一定會看到圖片, 這顯然是一種浪費。解決的核心思路就是懶加載:實現(xiàn)方式就是先不給圖片設(shè)置路徑,當(dāng)圖片出現(xiàn)在瀏覽器可視區(qū)域時才設(shè)置真正的圖片路徑。

實現(xiàn)上就是先將圖片路徑設(shè)置給original-src,當(dāng)頁面不可見時,圖片不會加載:

<img original-src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9eb06680a16044feb794f40fc3b1ac3d~tplv-k3u1fbpfcp-watermark.image" />

通過監(jiān)聽頁面滾動,等頁面可見時設(shè)置圖片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);}

對于上面這個樣式規(guī)則,如果不應(yīng)用到具體的元素,瀏覽器不會去下載該圖片。所以你可以通過切換 className 的方式,放心得進行 CSS 中圖片的懶加載。

運行時性能優(yōu)化

1. 減少重繪與重排

有前端經(jīng)驗的開發(fā)者對這個概念一定不會陌生,瀏覽器下載完頁面需要的所有資源后, 就開始渲染頁面,主要經(jīng)歷這5個過程:

  1. 解析HTML生成DOM樹
  2. 解析CSS生成CSSOM規(guī)則樹
  3. 將DOM樹與CSSOM規(guī)則樹合并生成Render(渲染)樹
  4. 遍歷Render(渲染)樹開始布局, 計算每一個節(jié)點的位置大小信息
  5. 將渲染樹每個節(jié)點繪制到屏幕上
圖片
<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; 
      box-sizing: border-box !important; word-wrap: break-word !important; 
      text-align: center; color: rgb(136, 136, 136); font-size: 12px; font-family: PingFangSC-Light;">
瀏覽器渲染過程
</figcaption>

重排

當(dāng)改變DOM元素位置或者大小時, 會導(dǎo)致瀏覽器重新生成Render樹, 這個過程叫重排

重繪

當(dāng)重新生成渲染樹后, 將要將渲染樹每個節(jié)點繪制到屏幕, 這個過程叫重繪。

重排觸發(fā)時機

重排發(fā)生后的根本原理就是元素的幾何屬性發(fā)生改變, 所以從能夠改變幾何屬性的角度入手:

  • 添加|刪除可見的DOM元素
  • 元素位置發(fā)生改變
  • 元素本省的尺寸發(fā)生改變
  • 內(nèi)容變化
  • 頁面渲染器初始化
  • 瀏覽器窗口大小發(fā)生改變

二者關(guān)系:重排會導(dǎo)致重繪, 但是重繪不會導(dǎo)致重排

了解了重排和重繪這兩個概念,我們還要知道重排和重繪的開銷都是非常昂貴的,如果不停的改變頁面的布局,就會造成瀏覽器消耗大量的開銷在進行頁面的計算上,這樣容易造成頁面卡頓。那么回到我們的問題如何減少重繪與重排呢?

1.1 避免table布局

  • 不要使用table布局,可能很小的一個改動會造成整個table重新布局

1.2 分離讀寫操作

DOM 的多個讀操作(或多個寫操作),應(yīng)該放在一起。不要兩個讀操作之間,加入一個寫操作。

// bad 強制刷新 觸發(fā)四次重排+重繪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 緩存布局信息 相當(dāng)于讀寫分離 觸發(fā)一次重排+重繪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 樣式集中改變

不要頻發(fā)的操作樣式,雖然現(xiàn)在大部分瀏覽器有渲染隊列優(yōu)化,但是在一些老版本的瀏覽器仍然存在效率低下的問題:

// 三次重排div.style.left = '10px';div.style.top = '10px';div.style.width = '20px';// 一次重排el.style.cssText = 'left: 10px;top: 10px; width: 20px';

或者可以采用更改類名而不是修改樣式的方式。

1.4 position屬性為absolute或fixed

使用絕對定位會使的該元素單獨成為渲染樹中 body 的一個子元素,重排開銷比較小,不會對其它節(jié)點造成太多影響。當(dāng)你在這些節(jié)點上放置這個元素時,一些其它在這個區(qū)域內(nèi)的節(jié)點可能需要重繪,但是不需要重排。

2. 避免頁面卡頓

我們目前大多數(shù)屏幕的刷新率-60次/s,瀏覽器渲染更新頁面的標(biāo)準(zhǔn)幀率也為60次/s --60FPS(frames/pre second), 那么每一幀的預(yù)算時間約為16.6ms ≈ 1s/60,瀏覽器在這個時間內(nèi)要完成所有的整理工作,如果無法符合此預(yù)算, 幀率將下降,內(nèi)容會在屏幕抖動, 此現(xiàn)象通常稱為卡頓。

瀏覽器需要做的工作包含下面這個流程:
圖片

首先你用js做了些邏輯,還觸發(fā)了樣式變化,style把應(yīng)用的樣式規(guī)則計算好之后,把影響到的頁面元素進行重新布局,叫做layout,再把它畫到內(nèi)存的一個畫布里面,paint成了像素,最后把這個畫布刷到屏幕上去,叫做composite,形成一幀。

這幾項的任何一項如果執(zhí)行時間太長了,就會導(dǎo)致渲染這一幀的時間太長,平均幀率就會掉。假設(shè)這一幀花了50ms,那么此時的幀率就為1s / 50ms = 20fps.

當(dāng)然上面的過程并不一定每一步都會執(zhí)行,例如:

  • 你的js只是做一些運算,并沒有增刪DOM或改變CSS,那么后續(xù)幾步就不會執(zhí)行
  • style只改了顏色等不需要重新layout的屬性就不用執(zhí)行layout這一步
  • style改了transform屬性,在blink和edge瀏覽器里面不需要layout和paint

3. 長列表優(yōu)化

有時會有這樣的需求, 需要在頁面上展示包含上百個元素的列表(例如一個Feed流)。每個列表元素還有著復(fù)雜的內(nèi)部結(jié)構(gòu),這顯然提高了頁面渲染的成本。當(dāng)你使用了React時,長列表的問題就會被進一步的放大。那么怎么來優(yōu)化長列表呢?

1.1 實現(xiàn)虛擬列表

虛擬列表是一種用來優(yōu)化長列表的技術(shù)。它可以保證在列表元素不斷增加,或者列表元素很多的情況下,依然擁有很好的滾動、瀏覽性能。它的核心思想在于:只渲染可見區(qū)域附近的列表元素。下圖左邊就是虛擬列表的效果,可以看到只有視口內(nèi)和臨近視口的上下區(qū)域內(nèi)的元素會被渲染。

圖片

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px; font-family: PingFangSC-Light;">Virtual List.png</figcaption>

具體實現(xiàn)步驟如下所示:

  • 首先確定長列表所在父元素的大小,父元素的大小決定了可視區(qū)的寬和高
  • 確定長列表每一個列表元素的寬和高,同時初始的條件下計算好長列表每一個元素相對于父元素的位置,并用一個數(shù)組來保存所有列表元素的位置信息
  • 首次渲染時,只展示相對于父元素可視區(qū)內(nèi)的子列表元素,在滾動時,根據(jù)父元素的滾動的offset重新計算應(yīng)該在可視區(qū)內(nèi)的子列表元素。這樣保證了無論如何滾動,真實渲染出的dom節(jié)點只有可視區(qū)內(nèi)的列表元素。
  • 假設(shè)可視區(qū)內(nèi)能展示5個子列表元素,及時長列表總共有1000個元素,但是每時每刻,真實渲染出來的dom節(jié)點只有5個。
  • 補充說明,這種情況下,父元素一般使用position:relative,子元素的定位一般使用:position:absolutesticky

除了自己實現(xiàn)外, 常用的框架也有不錯的開源實現(xiàn), 例如:

  • 基于React的 react-virtualized
  • 基于Vue 的 vue-virtual-scroll-list
  • 基于Angular的 ngx-virtual-scroller

4. 滾動事件性能優(yōu)化

前端最容易碰到的性能問題的場景之一就是監(jiān)聽滾動事件并進行相應(yīng)的操作。由于滾動事件發(fā)生非常頻繁,所以頻繁地執(zhí)行監(jiān)聽回調(diào)就容易造成JavaScript執(zhí)行與頁面渲染之間互相阻塞的情況。

對應(yīng)滾動這個場景,可以采用防抖節(jié)流來處理。

當(dāng)一個事件頻繁觸發(fā),而我們希望間隔一定的時間再觸發(fā)相應(yīng)的函數(shù)時, 就可以使用節(jié)流(throttle)來處理。比如判斷頁面是否滾動到底部,然后展示相應(yīng)的內(nèi)容;就可以使用節(jié)流,在滾動時每300ms進行一次計算判斷是否滾動到底部的邏輯,而不用無時無刻地計算。

當(dāng)一個事件頻繁觸發(fā),而我們希望在事件觸發(fā)結(jié)束一段時間后(此段時間內(nèi)不再有觸發(fā))才實際觸發(fā)響應(yīng)函數(shù)時會使用防抖(debounce)。例如用戶一直點擊按鈕,但你不希望頻繁發(fā)送請求,你就可以設(shè)置當(dāng)點擊后 200ms 內(nèi)用戶不再點擊時才發(fā)送請求。

對節(jié)流和防抖不太了解的可以看這篇文章:老生常談的防抖與節(jié)流https://mp.weixin.qq.com/s/HVkV7F1U77GvXbEI9MWA6g

5. 使用 Web Workers

前面提到了大量數(shù)據(jù)的渲染環(huán)節(jié)我們可以采用虛擬列表的方式實現(xiàn),但是大量數(shù)據(jù)的計算環(huán)節(jié)依然會產(chǎn)生瀏覽器假死或者卡頓的情況.

通常情況下我們CPU密集型的任務(wù)都是交給后端計算的,但是有些時候我們需要處理一些離線場景或者解放后端壓力,這個時候此方法就不奏效了.

還有一種方法是計算切片,使用 setTimeout 拆分密集型任務(wù),但是有些計算無法利用此方法拆解,同時還可能產(chǎn)生副作用,這個方法需要視具體場景而動.

最后一種方法也是目前比較奏效的方法就是利用Web Worker 進行多線程編程.

Web Worker 是一個獨立的線程(獨立的執(zhí)行環(huán)境),這就意味著它可以完全和 UI 線程(主線程)并行的執(zhí)行 js 代碼,從而不會阻塞 UI,它和主線程是通過 onmessage 和 postMessage 接口進行通信的。

Web Worker 使得網(wǎng)頁中進行多線程編程成為可能。當(dāng)主線程在處理界面事件時,worker 可以在后臺運行,幫你處理大量的數(shù)據(jù)計算,當(dāng)計算完成,將計算結(jié)果返回給主線程,由主線程更新 DOM 元素。

6. 寫代碼時的優(yōu)化點

提升性能,有時候在我們寫代碼時注意一些細節(jié)也是有效果的。

6.1 使用事件委托

看一下下面這段代碼:

<ul>  <li>字節(jié)跳動</li>  <li>阿里</li>  <li>騰訊</li>  <li>京東</li></ul>// gooddocument.querySelector('ul').onclick = (event) => {  const target = event.target  if (target.nodeName === 'LI') {    console.log(target.innerHTML)  }}// baddocument.querySelectorAll('li').forEach((e) => {  e.onclick = function() {    console.log(this.innerHTML)  }}) 

綁定的事件越多, 瀏覽器內(nèi)存占有就越多,從而影響性能,利用事件代理的方式就可節(jié)省一些內(nèi)存。

6.2 if-else 對比 switch

當(dāng)判定條件越來越多時, 越傾向于使用switch,而不是if-else:

if (state ==0) {    console.log("待開通")} else if (state == 1) {    console.log("學(xué)習(xí)中")} else if (state == 2) {    console.log("休學(xué)中")} else if (state == 3) {    console.log("已過期")} esle if (state ==4){    console.log("未購買")}switch (state) {    case 0:        break    case 1:        break    case 2:        break    case 3:        break    case 4:        break}

向上面這種情況使用switch更好, 假設(shè)state為4,那么if-else語句就要進行4次判定,switch只要進行一次即可。

但是有的情況下switch也做不到if-else的事情, 例如有多個判斷條件的情況下,無法使用switch

6.3 布局上使用flexbox

在早期的 CSS 布局方式中我們能對元素實行絕對定位、相對定位或浮動定位。而現(xiàn)在,我們有了新的布局方式 flexbox,它比起早期的布局方式來說有個優(yōu)勢,那就是性能比較好。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前端需要性能優(yōu)化么? 性能優(yōu)化一直以來都是前端工程領(lǐng)域中的一個重要部分。很多資料表明,網(wǎng)站應(yīng)用的性能優(yōu)化對于提高用...
    木簫簫閱讀 362評論 0 0
  • 1. 用戶體驗 軟件是用戶通往資源的通道。例如:滴滴對應(yīng)司機、乘客;淘寶、天貓對應(yīng)購物;餓了么、美團對應(yīng)周邊生活;...
    nimw閱讀 601評論 0 1
  • 你愿意為打開一個網(wǎng)頁等待多長時間?我一秒也不愿意等。但是事實上大多數(shù)網(wǎng)站在響應(yīng)速度方面都讓人失望?,F(xiàn)在越來越多的人...
    前端楊肖閱讀 677評論 0 0
  • 速記筆記markdown工具: https://pandao.github.io/editor.md/實驗服務(wù)器:...
    桃花島主閱讀 2,304評論 0 34
  • Html部分 1.語義化HTML:好處在于可以使代碼簡潔清晰,支持不同設(shè)備,利于搜索引擎,便于團隊開發(fā);2.減少D...
    泉泉泉泉泉泉閱讀 773評論 0 1

友情鏈接更多精彩內(nèi)容