1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        7000字前端性能優(yōu)化總結(jié) | 干貨建議收藏

        共 15155字,需瀏覽 31分鐘

         ·

        2021-07-14 15:11

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

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

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

        • 加載時(shí)優(yōu)化
        • 運(yùn)行時(shí)優(yōu)化

        加載時(shí)性能

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

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

        白屏?xí)r間計(jì)算

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

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

        首屏?xí)r間計(jì)算

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

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

        運(yùn)行時(shí)性能

        運(yùn)行時(shí)性能是指頁(yè)面運(yùn)行時(shí)的性能表現(xiàn),而不是頁(yè)面加載時(shí)的性能??梢酝ㄟ^(guò)chrome開(kāi)發(fā)者工具中的 Performance 面板來(lái)分析頁(yè)面的運(yùn)行時(shí)性能。關(guān)于chrome開(kāi)發(fā)者工具具體如何操作以及如何查看性能,可以看這篇文章性能優(yōu)化篇——運(yùn)行時(shí)性能分析

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

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

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

        這個(gè)過(guò)程中可以提升性能的優(yōu)化的點(diǎn):

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

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

        1.DNS 預(yù)解析

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

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

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

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

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

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

        在頁(yè)面header中使用link標(biāo)簽來(lái)強(qiáng)制對(duì)DNS預(yù)解析:

        <link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />

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

        2.使用HTTP2

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

        HTTP2相對(duì)于HTTP1有這些優(yōu)點(diǎn):

        解析速度快

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

        多路復(fù)用

        在 HTTP2 上,多個(gè)請(qǐng)求可以共用一個(gè) TCP 連接,這稱(chēng)為多路復(fù)用。

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

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

        首部壓縮

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

        HTTP 1.1請(qǐng)求的大小變得越來(lái)越大,有時(shí)甚至?xí)笥赥CP窗口的初始大小,因?yàn)樗鼈冃枰却龓е鳤CK的響應(yīng)回來(lái)以后才能繼續(xù)被發(fā)送。HTTP/2對(duì)消息頭采用HPACK(專(zhuān)為http/2頭部設(shè)計(jì)的壓縮格式)進(jìn)行壓縮傳輸,能夠節(jié)省消息頭占用的網(wǎng)絡(luò)的流量。而HTTP/1.x每次請(qǐng)求,都會(huì)攜帶大量冗余頭信息,浪費(fèi)了很多帶寬資源。

        服務(wù)器推送

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

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

        HTTP請(qǐng)求建立和釋放需要時(shí)間。

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

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

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

        4.壓縮、合并文件

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

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

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

        提取公共代碼

        合并文件雖然能減少HTTP請(qǐng)求數(shù)量, 但是并不是文件合并越多越好,還可以考慮按需加載方式(后面第6點(diǎn)有講到)。什么樣的文件可以合并呢?可以提取項(xiàng)目中多次使用到的公共代碼進(jìn)行提取,打包成公共模塊。

        可以使用 webpack4 的 splitChunk 插件 cacheGroups 選項(xiàng)。

        optimization: {
              runtimeChunk: {
                name'manifest' // 將 webpack 的 runtime 代碼拆分為一個(gè)單獨(dú)的 chunk。
            },
            splitChunks: {
                cacheGroups: {
                    vendor: {
                        name'chunk-vendors',
                        test/[\\/]node_modules[\\/]/,
                        priority-10,
                        chunks'initial'
                    },
                    common: {
                        name'chunk-common',
                        minChunks2,
                        priority-20,
                        chunks'initial',
                        reuseExistingChunktrue
                    }
                },
            }
        },

        5.采用svg圖片或者字體圖標(biāo)

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

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

        按需加載

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

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

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

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

        減少冗余代碼

        一方面避免不必要的轉(zhuǎn)義:babel-loaderincludeexclude 來(lái)幫我們避免不必要的轉(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)化后的代碼想要實(shí)現(xiàn)和原來(lái)代碼一樣的功能需要借助一些幫助函數(shù),比如:

        class Person {}

        會(huì)被轉(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 就是一個(gè) helper 函數(shù),如果在很多文件里都聲明了類(lèi),那么就會(huì)產(chǎn)生很多個(gè)這樣的 helper 函數(shù)。

        這里的 @babel/runtime 包就聲明了所有需要用到的幫助函數(shù),而 @babel/plugin-transform-runtime 的作用就是將所有需要 helper 函數(shù)的文件,從 @babel/runtime包 引進(jìn)來(lái):

        "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);
        };

        這里就沒(méi)有再編譯出 helper 函數(shù) classCallCheck 了,而是直接引用了@babel/runtime 中的 helpers/classCallCheck。

        • 安裝

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

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

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

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

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

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

        8. 使用 Defer 加載JS

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

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

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

        因?yàn)橄燃虞d HTML 再加載 CSS,會(huì)讓用戶(hù)第一時(shí)間看到的頁(yè)面是沒(méi)有樣式的、“丑陋”的,為了避免這種情況發(fā)生,就要將 CSS 文件放在頭部了。

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

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

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

        10. 圖片優(yōu)化

        雪碧圖

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

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

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

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

        圖片懶加載

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

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

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

        通過(guò)監(jiān)聽(tīng)頁(yè)面滾動(dòng),等頁(yè)面可見(jiàn)時(shí)設(shè)置圖片src:

        const img = document.querySelector('img')
        img.src = img.getAttribute("original-src")

        如果想使用懶加載,還可以借助一些已有的工具庫(kù),例如 aFarkas/lazysizes、verlok/lazyload、tuupola/lazyload 等。

        css中圖片懶加載

        除了對(duì)于 <img> 元素的圖片進(jìn)行來(lái)加載,在 CSS 中使用的圖片一樣可以懶加載,最常見(jiàn)的場(chǎng)景就是 background-url。

        .login {
            background-urlurl(/static/img/login.png);
        }

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

        運(yùn)行時(shí)性能優(yōu)化

        1. 減少重繪與重排

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

        1. 解析HTML生成DOM樹(shù)
        2. 解析CSS生成CSSOM規(guī)則樹(shù)
        3. 將DOM樹(shù)與CSSOM規(guī)則樹(shù)合并生成Render(渲染)樹(shù)
        4. 遍歷Render(渲染)樹(shù)開(kāi)始布局, 計(jì)算每一個(gè)節(jié)點(diǎn)的位置大小信息
        5. 將渲染樹(shù)每個(gè)節(jié)點(diǎn)繪制到屏幕上
        瀏覽器渲染過(guò)程

        重排

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

        重繪

        當(dāng)重新生成渲染樹(shù)后, 將要將渲染樹(shù)每個(gè)節(jié)點(diǎn)繪制到屏幕, 這個(gè)過(guò)程叫重繪。

        重排觸發(fā)時(shí)機(jī)

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

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

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

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

        1.1 避免table布局

        • 不要使用table布局,可能很小的一個(gè)改動(dòng)會(huì)造成整個(gè)table重新布局

        1.2 分離讀寫(xiě)操作

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

        // bad 強(qiáng)制刷新 觸發(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)于讀寫(xiě)分離 觸發(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)在大部分瀏覽器有渲染隊(duì)列優(yōu)化,但是在一些老版本的瀏覽器仍然存在效率低下的問(wèn)題:

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

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

        或者可以采用更改類(lèi)名而不是修改樣式的方式。

        1.4 position屬性為absolute或fixed

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

        2. 避免頁(yè)面卡頓

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

        瀏覽器需要做的工作包含下面這個(gè)流程:

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

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

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

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

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

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

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

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

        Virtual List.png

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

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

        除了自己實(shí)現(xiàn)外, 常用的框架也有不錯(cuò)的開(kāi)源實(shí)現(xiàn), 例如:

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

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

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

        對(duì)應(yīng)滾動(dòng)這個(gè)場(chǎng)景,可以采用防抖節(jié)流來(lái)處理。

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

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

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

        5. 使用 Web Workers

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

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

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

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

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

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

        6. 寫(xiě)代碼時(shí)的優(yōu)化點(diǎn)

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

        6.1 使用事件委托

        看一下下面這段代碼:

        <ul>
          <li>字節(jié)跳動(dòng)</li>
          <li>阿里</li>
          <li>騰訊</li>
          <li>京東</li>
        </ul>

        /
        / good
        document.querySelector('ul').onclick = (event) => {
          const target = event.target
          if (target.nodeName === 'LI') {
            console.log(target.innerHTML)
          }
        }

        /
        / bad
        document.querySelectorAll('li').forEach((e) => {
          e.onclick = function() {
            console.log(this.innerHTML)
          }
        }) 

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

        6.2 if-else 對(duì)比 switch

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

        if (state ==0) {
            console.log("待開(kāi)通")
        else if (state == 1) {
            console.log("學(xué)習(xí)中")
        else if (state == 2) {
            console.log("休學(xué)中")
        else if (state == 3) {
            console.log("已過(guò)期")
        } esle if (state ==4){
            console.log("未購(gòu)買(mǎi)")
        }

        switch (state) {
            case 0:

                break
            case 1:

                break
            case 2:

                break
            case 3:

                break
            case 4:

                break
        }

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

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

        6.3 布局上使用flexbox

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

        關(guān)于前端性能優(yōu)化就寫(xiě)到這里了,相信還有很多在代碼細(xì)節(jié)上注意就能進(jìn)行性能優(yōu)化的點(diǎn),大家可以到公眾號(hào)【程序員成長(zhǎng)指北】后臺(tái)留言, 后期也可以繼續(xù)完善文章,謝謝

        參考文章

        https://juejin.cn/post/6844903506906710024#comment

        https://learnku.com/docs/f2e-performance-rules/reduce-the-number-of-http-requests/6369

        最后



        如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

        1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

        2. 歡迎加我微信「 sherlocked_93 」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

        3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


        點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


        瀏覽 124
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            艳妇h圆房~h嗯啊 | 一级a免做一级做a爱性韩国 | 久久香蕉网 | 夜夜夜夜夜操 | 不戴套进入让少妇高潮 | 国产一区播放 | 西西特级444www高清视频 | 内射蜜臀 | 国产精品久久久精品 | 蜜臀一区二区三区 |