寫給中高級前端關於性能優化的9大策略和6大指標 | 網易四年實踐

做者:JowayYoung
倉庫:GithubCodePen
博客:官網掘金思否知乎
公衆號:IQ前端
特別聲明:原創不易,未經受權不得轉載或抄襲,如需轉載可聯繫筆者受權css

「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!html

前言

最近太忙,已有五個月未發佈任何文章,好多讀者私信筆者問我最近怎麼啦?最近半年時間筆者不只上班忙下班也忙。工做上,一直在重構項目與兼顧開發新項目,偶爾也寫寫技術專利;生活上,2019年在廣州市區入手的房子和車位在這段時間陸續驗收,一直在裝修買傢俱家電也不停地佈置新家,偶爾也寫寫掘金小冊。全部時間都安排得妥穩當當,所以在工做和生活上也獲得很充實的體驗,全部事情都是本身親力親爲,同時也感謝弟弟和妹妹的幫忙,否則還真的不能這麼快搞掂。前端

如下是筆者新佈置的我的辦公空間,接下來就可安安心心地繼續創做了,以前想寫的文章都會一一發布,感謝你們的支持!vue

我的辦公空間.jpg

筆者近半年一直在參與項目重構,在重構過程當中大量應用性能優化設計模式兩方面的知識。性能優化設計模式兩方面的知識無論在工做仍是面試時都是高頻應用場景,趁着此次參與大規模項目重構的機會,筆者認真梳理出一些常規且必用的性能優化建議,同時結合平常開發經驗整理出筆者在網易四年來實踐到的認爲有用的全部性能優化建議,與你們一塊兒分享分享!(因爲篇幅有限,那設計模式在後面再專門出一篇文章唄)node

可能有些性能優化建議已被你們熟知,不過也不影響此次分享,固然筆者也將一些平時可能不會注意的細節羅列出來。react

平時你們認爲性能優化是一種無序的應用場景,但在筆者看來它是一種有序的應用場景且不少性能優化都是互相鋪墊甚至一帶一路。從過程趨勢來看,性能優化可分爲網絡層面渲染層面;從結果趨勢來看,性能優化可分爲時間層面體積層面。簡單來講就是要在訪問網站時使其快準狠地立馬呈如今用戶眼前webpack

性能優化.png

全部的性能優化都圍繞着兩大層面兩小層面實現,核心層面是網絡層面渲染層面,輔助層面是時間層面體積層面,而輔助層面則充滿在覈心層面裏。因而筆者經過本文整理出關於前端性能優化九大策略六大指標。固然這些策略指標都是筆者本身定義,方便經過某種方式爲性能優化作一些規範。git

所以在工做或面試時結合這些特徵就能完美地詮釋性能優化所延伸出來的知識了。前方高能,不看也得收藏,走起!!!github

全部代碼示例爲了凸顯主題,只展現核心配置代碼,其餘配置並未補上,請自行腦補
複製代碼

九大策略

網絡層面

網絡層面的性能優化,無疑是如何讓資源體積更小加載更快,所以筆者從如下四方面作出建議。web

  • 構建策略:基於構建工具(Webpack/Rollup/Parcel/Esbuild/Vite/Gulp)
  • 圖像策略:基於圖像類型(JPG/PNG/SVG/WebP/Base64)
  • 分發策略:基於內容分發網絡(CDN)
  • 緩存策略:基於瀏覽器緩存(強緩存/協商緩存)

上述四方面都是一步接着一步完成,充滿在整個項目流程裏。構建策略圖像策略處於開發階段,分發策略緩存策略處於生產階段,所以在每一個階段均可檢查是否按順序接入上述策略。經過這種方式就能最大限度增長性能優化應用場景。

構建策略

該策略主要圍繞webpack作相關處理,同時也是接入最廣泛的性能優化策略。其餘構建工具的處理也是大同小異,可能只是配置上不一致。說到webpack性能優化,無疑是從時間層面體積層面入手。

筆者發現目前webpack v5總體兼容性還不是特別好,某些功能配合第三方工具可能出現問題,故暫未升級到v5,繼續使用v4做爲生產工具,故如下配置均基於v4,但整體與v5的配置出入不大
複製代碼

筆者對兩層面分別作出6個性能優化建議總共12個性能優化建議,爲了方便記憶均使用四字詞語歸納,方便你們消化。⏱表示減小打包時間,📦表示減小打包體積

  • 減小打包時間縮減範圍緩存副本定向搜索提早構建並行構建可視結構
  • 減小打包體積分割代碼搖樹優化動態墊片按需加載做用提高壓縮資源

⏱縮減範圍

配置include/exclude縮小Loader對文件的搜索範圍,好處是避免沒必要要的轉譯node_modules目錄的體積這麼大,那得增長多少時間成本去檢索全部文件啊?

include/exclude一般在各大Loader裏配置,src目錄一般做爲源碼目錄,可作以下處理。固然include/exclude可根據實際狀況修改。

export default {
    // ...
    module: {
        rules: [{
            exclude: /node_modules/,
            include: /src/,
            test: /\.js$/,
            use: "babel-loader"
        }]
    }
};
複製代碼

⏱緩存副本

配置cache緩存Loader對文件的編譯副本,好處是再次編譯時只編譯修改過的文件。未修改過的文件幹嗎要隨着修改過的文件從新編譯呢?

大部分Loader/Plugin都會提供一個可以使用編譯緩存的選項,一般包含cache字眼。以babel-loadereslint-webpack-plugin爲例。

import EslintPlugin from "eslint-webpack-plugin";

export default {
    // ...
    module: {
        rules: [{
            // ...
            test: /\.js$/,
            use: [{
                loader: "babel-loader",
                options: { cacheDirectory: true }
            }]
        }]
    },
    plugins: [
        new EslintPlugin({ cache: true })
    ]
};
複製代碼

⏱定向搜索

配置resolve提升文件的搜索速度,好處是定向指定必須文件路徑。若某些第三方庫以常規形式引入可能報錯或但願程序自動索引特定類型文件均可經過該方式解決。

alias映射模塊路徑,extensions代表文件後綴,noParse過濾無依賴文件。一般配置aliasextensions就足夠。

export default {
    // ...
    resolve: {
        alias: {
            "#": AbsPath(""), // 根目錄快捷方式
            "@": AbsPath("src"), // src目錄快捷方式
            swiper: "swiper/js/swiper.min.js"
        }, // 模塊導入快捷方式
        extensions: [".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] // import路徑時文件可省略後綴名
    }
};
複製代碼

⏱提早構建

配置DllPlugin將第三方依賴提早打包,好處是將DLL與業務代碼徹底分離且每次只構建業務代碼。這是一個古老配置,在webpack v2時已存在,不過如今webpack v4+已不推薦使用該配置,由於其版本迭代帶來的性能提高足以忽略DllPlugin所帶來的效益。

DLL意爲動態連接庫,指一個包含可由多個程序同時使用的代碼庫。在前端領域裏可認爲是另類緩存的存在,它把公共代碼打包爲DLL文件並存到硬盤裏,再次打包時動態連接DLL文件就無需再次打包那些公共代碼,從而提高構建速度,減小打包時間。

配置DLL整體來講相比其餘配置複雜,配置流程可大體分爲三步。

首先告知構建腳本哪些依賴作成DLL並生成DLL文件DLL映射表文件

import { DefinePlugin, DllPlugin } from "webpack";

export default {
    // ...
    entry: {
        vendor: ["react", "react-dom", "react-router-dom"]
    },
    mode: "production",
    optimization: {
        splitChunks: {
            cacheGroups: {
                vendor: {
                    chunks: "all",
                    name: "vendor",
                    test: /node_modules/
                }
            }
        }
    },
    output: {
        filename: "[name].dll.js", // 輸出路徑和文件名稱
        library: "[name]", // 全局變量名稱:其餘模塊會今後變量上獲取裏面模塊
        path: AbsPath("dist/static") // 輸出目錄路徑
    },
    plugins: [
        new DefinePlugin({
            "process.env.NODE_ENV": JSON.stringify("development") // DLL模式下覆蓋生產環境成開發環境(啓動第三方依賴調試模式)
        }),
        new DllPlugin({
            name: "[name]", // 全局變量名稱:減少搜索範圍,與output.library結合使用
            path: AbsPath("dist/static/[name]-manifest.json") // 輸出目錄路徑
        })
    ]
};
複製代碼

而後在package.json裏配置執行腳本且每次構建前首先執行該腳本打包出DLL文件

{
    "scripts": {
        "dll": "webpack --config webpack.dll.js"
    }
}
複製代碼

最後連接DLL文件並告知webpack可命中的DLL文件讓其自行讀取。使用html-webpack-tags-plugin在打包時自動插入DLL文件

import { DllReferencePlugin } from "webpack";
import HtmlTagsPlugin from "html-webpack-tags-plugin";

export default {
    // ...
    plugins: [
        // ...
        new DllReferencePlugin({
            manifest: AbsPath("dist/static/vendor-manifest.json") // manifest文件路徑
        }),
        new HtmlTagsPlugin({
            append: false, // 在生成資源後插入
            publicPath: "/", // 使用公共路徑
            tags: ["static/vendor.dll.js"] // 資源路徑
        })
    ]
};
複製代碼

爲了那幾秒鐘的時間成本,筆者建議配置上較好。固然也可以使用autodll-webpack-plugin代替手動配置。

⏱並行構建

配置Thread將Loader單進程轉換爲多進程,好處是釋放CPU多核併發的優點。在使用webpack構建項目時會有大量文件需解析和處理,構建過程是計算密集型的操做,隨着文件增多會使構建過程變得越慢。

運行在Node裏的webpack是單線程模型,簡單來講就是webpack待處理的任務需一件件處理,不能同一時刻處理多件任務。

文件讀寫計算操做沒法避免,能不能讓webpack同一時刻處理多個任務,發揮多核CPU電腦的威力以提高構建速度呢?thread-loader來幫你,根據CPU個數開啓線程。

在此需注意一個問題,若項目文件不算多就不要使用該性能優化建議,畢竟開啓多個線程也會存在性能開銷。

import Os from "os";

export default {
    // ...
    module: {
        rules: [{
            // ...
            test: /\.js$/,
            use: [{
                loader: "thread-loader",
                options: { workers: Os.cpus().length }
            }, {
                loader: "babel-loader",
                options: { cacheDirectory: true }
            }]
        }]
    }
};
複製代碼

⏱可視結構

配置BundleAnalyzer分析打包文件結構,好處是找出致使體積過大的緣由。從而經過分析緣由得出優化方案減小構建時間。BundleAnalyzerwebpack官方插件,可直觀分析打包文件的模塊組成部分、模塊體積佔比、模塊包含關係、模塊依賴關係、文件是否重複、壓縮體積對比等可視化數據。

可以使用webpack-bundle-analyzer配置,有了它,咱們就能快速找到相關問題。

import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";

export default {
    // ...
    plugins: [
        // ...
        BundleAnalyzerPlugin()
    ]
};
複製代碼

📦分割代碼

分割各個模塊代碼,提取相同部分代碼,好處是減小重複代碼的出現頻率webpack v4使用splitChunks替代CommonsChunksPlugin實現代碼分割。

splitChunks配置較多,詳情可參考官網,在此筆者貼上經常使用配置。

export default {
    // ...
    optimization: {
        runtimeChunk: { name: "manifest" }, // 抽離WebpackRuntime函數
        splitChunks: {
            cacheGroups: {
                common: {
                    minChunks: 2,
                    name: "common",
                    priority: 5,
                    reuseExistingChunk: true, // 重用已存在代碼塊
                    test: AbsPath("src")
                },
                vendor: {
                    chunks: "initial", // 代碼分割類型
                    name: "vendor", // 代碼塊名稱
                    priority: 10, // 優先級
                    test: /node_modules/ // 校驗文件正則表達式
                }
            }, // 緩存組
            chunks: "all" // 代碼分割類型:all所有模塊,async異步模塊,initial入口模塊
        } // 代碼塊分割
    }
};
複製代碼

📦搖樹優化

刪除項目中未被引用代碼,好處是移除重複代碼和未使用代碼搖樹優化首次出現於rollup,是rollup的核心概念,後來在webpack v2裏借鑑過來使用。

搖樹優化只對ESM規範生效,對其餘模塊規範失效。搖樹優化針對靜態結構分析,只有import/export才能提供靜態的導入/導出功能。所以在編寫業務代碼時必須使用ESM規範才能讓搖樹優化移除重複代碼和未使用代碼。

webpack裏只需將打包環境設置成生產環境就能讓搖樹優化生效,同時業務代碼使用ESM規範編寫,使用import導入模塊,使用export導出模塊。

export default {
    // ...
    mode: "production"
};
複製代碼

📦動態墊片

經過墊片服務根據UA返回當前瀏覽器代碼墊片,好處是無需將繁重的代碼墊片打包進去。每次構建都配置@babel/preset-envcore-js根據某些需求將Polyfill打包進來,這無疑又爲代碼體積增長了貢獻。

@babel/preset-env提供的useBuiltIns可按需導入Polyfill

  • false:無視target.browsers將全部Polyfill加載進來
  • entry:根據target.browsers將部分Polyfill加載進來(僅引入有瀏覽器不支持的Polyfill,需在入口文件import "core-js/stable")
  • usage:根據target.browsers和檢測代碼裏ES6的使用狀況將部分Polyfill加載進來(無需在入口文件import "core-js/stable")

在此推薦你們使用動態墊片動態墊片可根據瀏覽器UserAgent返回當前瀏覽器Polyfill,其思路是根據瀏覽器的UserAgentbrowserlist查找出當前瀏覽器哪些特性缺少支持從而返回這些特性的Polyfill。對這方面感興趣的同窗可參考polyfill-librarypolyfill-service的源碼。

在此提供兩個動態墊片服務,可在不一樣瀏覽器裏點擊如下連接看看輸出不一樣的Polyfill。相信IExplore仍是最多Polyfill的,它自豪地說:我就是我,不同的煙火

使用html-webpack-tags-plugin在打包時自動插入動態墊片

import HtmlTagsPlugin from "html-webpack-tags-plugin";

export default {
    plugins: [
        new HtmlTagsPlugin({
            append: false, // 在生成資源後插入
            publicPath: false, // 使用公共路徑
            tags: ["https://polyfill.alicdn.com/polyfill.min.js"] // 資源路徑
        })
    ]
};
複製代碼

📦按需加載

將路由頁面/觸發性功能單獨打包爲一個文件,使用時才加載,好處是減輕首屏渲染的負擔。由於項目功能越多其打包體積越大,致使首屏渲染速度越慢。

首屏渲染時只需對應JS代碼而無需其餘JS代碼,因此可以使用按需加載webpack v4提供模塊按需切割加載功能,配合import()可作到首屏渲染減包的效果,從而加快首屏渲染速度。只有當觸發某些功能時纔會加載當前功能的JS代碼

webpack v4提供魔術註解命名切割模塊,若無註解則切割出來的模塊沒法分辨出屬於哪一個業務模塊,因此通常都是一個業務模塊共用一個切割模塊的註解名稱。

const Login = () => import( /* webpackChunkName: "login" */ "../../views/login");
const Logon = () => import( /* webpackChunkName: "logon" */ "../../views/logon");
複製代碼

運行起來控制檯可能會報錯,在package.jsonbabel相關配置裏接入@babel/plugin-syntax-dynamic-import便可。

{
    // ...
    "babel": {
        // ...
        "plugins": [
            // ...
            "@babel/plugin-syntax-dynamic-import"
        ]
    }
}
複製代碼

📦做用提高

分析模塊間依賴關係,把打包好的模塊合併到一個函數中,好處是減小函數聲明和內存花銷做用提高首次出現於rollup,是rollup的核心概念,後來在webpack v3裏借鑑過來使用。

在未開啓做用提高前,構建後的代碼會存在大量函數閉包。因爲模塊依賴,經過webpack打包後會轉換成IIFE,大量函數閉包包裹代碼會致使打包體積增大(模塊越多越明顯)。在運行代碼時建立的函數做用域變多,從而致使更大的內存開銷。

在開啓做用提高後,構建後的代碼會按照引入順序放到一個函數做用域裏,經過適當重命名某些變量以防止變量名衝突,從而減小函數聲明和內存花銷。

webpack裏只需將打包環境設置成生產環境就能讓做用提高生效,或顯式設置concatenateModules

export default {
    // ...
    mode: "production"
};
// 顯式設置
export default {
    // ...
    optimization: {
        // ...
        concatenateModules: true
    }
};
複製代碼

📦壓縮資源

壓縮HTML/CSS/JS代碼,壓縮字體/圖像/音頻/視頻,好處是更有效減小打包體積。極致地優化代碼都有可能不及優化一個資源文件的體積更有效。

針對HTML代碼,使用html-webpack-plugin開啓壓縮功能。

import HtmlPlugin from "html-webpack-plugin";

export default {
    // ...
    plugins: [
        // ...
        HtmlPlugin({
            // ...
            minify: {
                collapseWhitespace: true,
                removeComments: true
            } // 壓縮HTML
        })
    ]
};
複製代碼

針對CSS/JS代碼,分別使用如下插件開啓壓縮功能。其中OptimizeCss基於cssnano封裝,UglifyjsTerser都是webpack官方插件,同時需注意壓縮JS代碼需區分ES5ES6

import OptimizeCssAssetsPlugin from "optimize-css-assets-webpack-plugin";
import TerserPlugin from "terser-webpack-plugin";
import UglifyjsPlugin from "uglifyjs-webpack-plugin";

const compressOpts = type => ({
    cache: true, // 緩存文件
    parallel: true, // 並行處理
    [`${type}Options`]: {
        beautify: false,
        compress: { drop_console: true }
    } // 壓縮配置
});
const compressCss = new OptimizeCssAssetsPlugin({
    cssProcessorOptions: {
        autoprefixer: { remove: false }, // 設置autoprefixer保留過期樣式
        safe: true // 避免cssnano從新計算z-index
    }
});
const compressJs = USE_ES6
    ? new TerserPlugin(compressOpts("terser"))
    : new UglifyjsPlugin(compressOpts("uglify"));

export default {
    // ...
    optimization: {
        // ...
        minimizer: [compressCss, compressJs] // 代碼壓縮
    }
};
複製代碼

針對字體/音頻/視頻文件,還真沒相關Plugin供咱們使用,就只能拜託你們在發佈項目到生產服前使用對應的壓縮工具處理了。針對圖像文件,大部分Loader/Plugin封裝時均使用了某些圖像處理工具,而這些工具的某些功能又託管在國外服務器裏,因此致使常常安裝失敗。具體解決方式可回看筆者曾經發布的《聊聊NPM鏡像那些險象環生的坑》一文尋求答案。

鑑於此,筆者花了一點小技巧開發了一個Plugin用於配合webpack壓縮圖像,詳情請參考tinyimg-webpack-plugin

import TinyimgPlugin from "tinyimg-webpack-plugin";

export default {
    // ...
    plugins: [
        // ...
        TinyimgPlugin()
    ]
};
複製代碼

上述構建策略都集成到筆者開源的bruce-cli裏,它是一個React/Vue應用自動化構建腳手架,其零配置開箱即用的優勢很是適合入門級、初中級、快速開發項目的前端同窗使用,還可經過建立brucerc.js文件覆蓋其默認配置,只需專一業務代碼的編寫無需關注構建代碼的編寫,讓項目結構更簡潔。詳情請戳這裏,使用時記得查看文檔,支持一個Star哈!

圖像策略

該策略主要圍繞圖像類型作相關處理,同時也是接入成本較低的性能優化策略。只需作到如下兩點便可。

  • 圖像選型:瞭解全部圖像類型的特色及其何種應用場景最合適
  • 圖像壓縮:在部署到生產環境前使用工具或腳本對其壓縮處理

圖像選型必定要知道每種圖像類型的體積/質量/兼容/請求/壓縮/透明/場景等參數相對值,這樣才能迅速作出判斷在何種場景使用何種類型的圖像。

類型 體積 質量 兼容 請求 壓縮 透明 場景
JPG 有損 不支持 背景圖、輪播圖、色彩豐富圖
PNG 無損 支持 圖標、透明圖
SVG 無損 支持 圖標、矢量圖
WebP 兼備 支持 看兼容狀況
Base64 看狀況 無損 支持 圖標

圖像壓縮可在上述構建策略-壓縮資源裏完成,也可自行使用工具完成。因爲如今大部分webpack圖像壓縮工具不是安裝失敗就是各類環境問題(你懂的),因此筆者仍是推薦在發佈項目到生產服前使用圖像壓縮工具處理,這樣運行穩定也不會增長打包時間。

好用的圖像壓縮工具無非就是如下幾個,如有更好用的工具麻煩在評論裏補充喔!

工具 開源 收費 API 免費體驗
QuickPicture ✖️ ✔️ ✖️ 可壓縮類型較多,壓縮質感較好,有體積限制,有數量限制
ShrinkMe ✖️ ✖️ ✖️ 可壓縮類型較多,壓縮質感通常,無數量限制,有體積限制
Squoosh ✔️ ✖️ ✔️ 可壓縮類型較少,壓縮質感通常,無數量限制,有體積限制
TinyJpg ✖️ ✔️ ✔️ 可壓縮類型較少,壓縮質感很好,有數量限制,有體積限制
TinyPng ✖️ ✔️ ✔️ 可壓縮類型較少,壓縮質感很好,有數量限制,有體積限制
Zhitu ✖️ ✖️ ✖️ 可壓縮類型通常,壓縮質感通常,有數量限制,有體積限制

若不想在網站裏來回拖動圖像文件,可以使用筆者開源的圖像批處理工具img-master代替,不只有壓縮功能,還有分組功能、標記功能和變換功能。目前筆者負責的所有項目都使用該工具處理,一直用一直爽!

圖像策略也許處理一張圖像就能完爆全部構建策略,所以是一種很廉價但極有效的性能優化策略

分發策略

該策略主要圍繞內容分發網絡作相關處理,同時也是接入成本較高的性能優化策略,需足夠資金支持。

雖然接入成本較高,但大部分企業都會購買一些CDN服務器,因此在部署的事情上就不用過度擔心,儘管使用就好。該策略儘可能遵循如下兩點就能發揮CDN最大做用。

  • 全部靜態資源走CDN:開發階段肯定哪些文件屬於靜態資源
  • 把靜態資源與主頁面置於不一樣域名下:避免請求帶上Cookie

內容分發網絡簡稱CDN,指一組分佈在各地存儲數據副本並可根據就近原則知足數據請求的服務器。其核心特徵是緩存回源,緩存是把資源複製到CDN服務器裏,回源是資源過時/不存在就向上層服務器請求並複製到CDN服務器裏。

使用CDN可下降網絡擁塞,提升用戶訪問響應速度和命中率。構建在現有網絡基礎上的智能虛擬網絡,依靠部署在各地服務器,經過中心平臺的調度、負載均衡、內容分發等功能模塊,使用戶就近獲取所需資源,這就是CDN的終極使命。

基於CDN就近原則所帶來的優勢,可將網站全部靜態資源所有部署到CDN服務器裏。那靜態資源包括哪些文件?一般來講就是無需服務器產生計算就能獲得的資源,例如不常變化的樣式文件腳本文件多媒體文件(字體/圖像/音頻/視頻)等。

若需單獨配置CDN服務器,可考慮阿里雲OSS網易樹帆NOS七牛雲Kodo,固然配置起來還需購買該產品對應的CDN服務。因爲篇幅問題,這些配置在購買後會有相關教程,可自行體會,在此就再也不敘述了。

筆者推薦你們首選網易樹帆NOS,畢竟對自家產品仍是挺有信心的,不當心給自家產品打了個小廣告了,哈哈!

緩存策略

該策略主要圍繞瀏覽器緩存作相關處理,同時也使接入成本最低的性能優化策略。其顯著減小網絡傳輸所帶來的損耗,提高網頁訪問速度,是一種很值得使用的性能優化策略

經過下圖可知,爲了讓瀏覽器緩存發揮最大做用,該策略儘可能遵循如下五點就能發揮瀏覽器緩存最大做用。

  • 考慮拒絕一切緩存策略Cache-Control:no-store
  • 考慮資源是否每次向服務器請求Cache-Control:no-cache
  • 考慮資源是否被代理服務器緩存Cache-Control:public/private
  • 考慮資源過時時間Expires:t/Cache-Control:max-age=t,s-maxage=t
  • 考慮協商緩存Last-Modified/Etag

緩存判斷機制

同時瀏覽器緩存也是高頻面試題之一,筆者以爲上述涉及到的名詞在不一樣語序串聯下也能徹底理解才能真正弄懂瀏覽器緩存性能優化裏起到的做用。

緩存策略經過設置HTTP報文實現,在形式上分爲強緩存/強制緩存協商緩存/對比緩存。爲了方便對比,筆者將某些細節使用圖例展現,相信你有更好的理解。

強緩存.png

協商緩存.png

整個緩存策略機制很明瞭,先走強緩存,若命中失敗才走協商緩存。若命中強緩存,直接使用強緩存;若未命中強緩存,發送請求到服務器檢查是否命中協商緩存;若命中協商緩存,服務器返回304通知瀏覽器使用本地緩存,不然返回最新資源

有兩種較經常使用的應用場景值得使用緩存策略一試,固然更多應用場景均可根據項目需求制定。

  • 頻繁變更資源:設置Cache-Control:no-cache,使瀏覽器每次都發送請求到服務器,配合Last-Modified/ETag驗證資源是否有效
  • 不常變化資源:設置Cache-Control:max-age=31536000,對文件名哈希處理,當代碼修改後生成新的文件名,當HTML文件引入文件名發生改變纔會下載最新文件

渲染層面

渲染層面的性能優化,無疑是如何讓代碼解析更好執行更快。所以筆者從如下五方面作出建議。

  • CSS策略:基於CSS規則
  • DOM策略:基於DOM操做
  • 阻塞策略:基於腳本加載
  • 迴流重繪策略:基於迴流重繪
  • 異步更新策略:基於異步更新

上述五方面都是編寫代碼時完成,充滿在整個項目流程的開發階段裏。所以在開發階段需時刻注意如下涉及到的每一點,養成良好的開發習慣,性能優化也天然而然被使用上了。

渲染層面性能優化更多表如今編碼細節上,而並不是實體代碼。簡單來講就是遵循某些編碼規則,才能將渲染層面性能優化發揮到最大做用。

迴流重繪策略渲染層面性能優化裏佔比較重,也是最常規的性能優化之一。上年筆者發佈的掘金小冊《玩轉CSS的藝術之美》使用一整章講解迴流重繪,本章已開通試讀,更多細節請戳這裏

CSS策略
  • 避免出現超過三層的嵌套規則
  • 避免爲ID選擇器添加多餘選擇器
  • 避免使用標籤選擇器代替類選擇器
  • 避免使用通配選擇器,只對目標節點聲明規則
  • 避免重複匹配重複定義,關注可繼承屬性
DOM策略
  • 緩存DOM計算屬性
  • 避免過多DOM操做
  • 使用DOMFragment緩存批量化DOM操做
阻塞策略
  • 腳本與DOM/其它腳本的依賴關係很強:對<script>設置defer
  • 腳本與DOM/其它腳本的依賴關係不強:對<script>設置async
迴流重繪策略
  • 緩存DOM計算屬性
  • 使用類合併樣式,避免逐條改變樣式
  • 使用display控制DOM顯隱,將DOM離線化
異步更新策略
  • 異步任務中修改DOM時把其包裝成微任務

六大指標

筆者根據性能優化的重要性和實際性劃分出九大策略六大指標,其實它們都是一條條活生生的性能優化建議。有些性能優化建議接不接入影響都不大,所以筆者將九大策略定位高於六大指標。針對九大策略仍是建議在開發階段和生產階段接入,在項目覆盤時可將六大指標的條條框框根據實際應用場景接入。

六大指標基本囊括大部分性能優化細節,可做爲九大策略的補充。筆者根據每條性能優化建議的特徵將指標劃分爲如下六方面。

  • 加載優化:資源在加載時可作的性能優化
  • 執行優化:資源在執行時可作的性能優化
  • 渲染優化:資源在渲染時可作的性能優化
  • 樣式優化:樣式在編碼時可作的性能優化
  • 腳本優化:腳本在編碼時可作的性能優化
  • V8引擎優化:針對V8引擎特徵可作的性能優化
加載優化

六大指標-加載優化.png

執行優化

六大指標-執行優化.png

渲染優化

六大指標-渲染優化.png

樣式優化

六大指標-樣式優化.png

腳本優化

六大指標-腳本優化.png

V8引擎優化

六大指標-V8引擎優化.png

總結

性能優化做爲老生常談的知識,必然會在工做或面試時趕上。不少時候不是想到某條性能優化建議就去作或答,而是要對這方面有一個總體認知,知道爲什麼這樣設計,這樣設計的目的能達到什麼效果。

性能優化不是經過一篇文章就能所有講完,若詳細去講可能要寫兩本書的篇幅才能講完。本文能到給你們的就是一個方向一種態度,學以至用唄,但願閱讀完本文會對你有所幫助。

最後,筆者將本文全部內容整理成一張高清腦圖,因爲體積太大沒法上傳,可關注筆者我的公衆號IQ前端並回復性能優化獲取口袋知識圖譜吧!

往期超過5萬閱讀量的掘金爆文

結語

❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更多高質量文章

關注公衆號IQ前端,一個專一於CSS/JS開發技巧的前端公衆號,更多前端小乾貨等着你喔

  • 關注後回覆資料免費領取學習資料
  • 關注後回覆進羣拉你進技術交流羣
  • 歡迎關注IQ前端,更多CSS/JS開發技巧只在公衆號推送
相關文章
相關標籤/搜索