hexo個(gè)人網(wǎng)站優(yōu)化探索

>> 原文鏈接

Contents


前言


1. hexo 是什么练链?

hexo 是一個(gè)為了不依賴于后端進(jìn)行界面實(shí)時(shí)數(shù)據(jù)查詢展示而設(shè)計(jì)的網(wǎng)站開發(fā)工具携丁。比如之前曾使用 Node.js 作為后臺(tái)開發(fā)過一個(gè)博客網(wǎng)站,自己實(shí)現(xiàn)后臺(tái)邏輯的話需要考慮數(shù)據(jù)庫(kù)存儲(chǔ)焚碌、前后端交互畦攘、后端 Server 部署啥的。整個(gè)流程比較繁雜十电,在初期可以作為前端開發(fā)者個(gè)人建站學(xué)習(xí)的一種方式念搬。hexo 簡(jiǎn)化了這一流程,它將數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)獲取這兩方面都通過編譯構(gòu)建然后本地化集成到前端靜態(tài)資源里摆出。一個(gè)例子就是博客網(wǎng)站通常需要翻頁(yè)功能來(lái)獲取博客文章,傳統(tǒng)開發(fā)方式下首妖,獲取下一頁(yè)這個(gè)操作由前端腳本發(fā)起袖外,并由后端 Server 處理請(qǐng)求并返回,但是使用 hexo 之后這整個(gè)過程都是在本地一次完成的,hexo 將所有靜態(tài)資源在本地建立了索引。

使用 hexo 通常寫手只需要關(guān)注 markdown 格式文章的編寫,其余的網(wǎng)站編譯、構(gòu)建和發(fā)布的流程都可以交由框架進(jìn)行處理,所有的網(wǎng)站內(nèi)容均會(huì)被打包成靜態(tài)的 html/css/js 文件。hexo 支持自定義插件身腻,也有一個(gè)插件社區(qū)产还,如果寫手同時(shí)具備前端能力的話也可以發(fā)布自己的插件到社區(qū)里進(jìn)行開源共享。

2. 何為優(yōu)化嘀趟?

我們通常所講的性能高低可能側(cè)重于對(duì)網(wǎng)站運(yùn)行速度快慢的評(píng)估脐区,其包括靜態(tài)資源及腳本獲取的速度和網(wǎng)站UI界面是否運(yùn)行流暢。其實(shí)廣義上的優(yōu)化應(yīng)包括:網(wǎng)站性能優(yōu)化她按、網(wǎng)站可訪性優(yōu)化牛隅、網(wǎng)站SEO優(yōu)化、網(wǎng)站最佳實(shí)踐等酌泰。

性能優(yōu)化指標(biāo)


1. 整體運(yùn)行性能

  • FCP (First Contentful Paint):從用戶開始發(fā)起網(wǎng)站請(qǐng)求到瀏覽器第一次開始渲染網(wǎng)站數(shù)據(jù)的所用時(shí)長(zhǎng)媒佣。其中提及的第一次開始渲染的網(wǎng)站數(shù)據(jù)包含網(wǎng)頁(yè)的文字、圖片陵刹、HTML DOM 結(jié)構(gòu)等默伍,而不包含位于 iframe 中的網(wǎng)頁(yè)數(shù)據(jù)。該指標(biāo)通常用于衡量本地和服務(wù)器首次建立網(wǎng)絡(luò)通訊的速度衰琐。

  • TTI (Time To Interactive):從用戶開始導(dǎo)航至網(wǎng)站到頁(yè)面變?yōu)橥耆山换ニㄙM(fèi)的時(shí)間也糊。網(wǎng)站可交互的衡量標(biāo)準(zhǔn)就是:網(wǎng)站展示了實(shí)際可用的內(nèi)容、界面上可見元素的網(wǎng)頁(yè)事件已經(jīng)被成功綁定(比如點(diǎn)擊羡宙、拖動(dòng)等事件)狸剃、用戶和頁(yè)面交互的反饋時(shí)間低于 50 ms。

  • SI (Speed Index):衡量頁(yè)面加載過程中內(nèi)容可視化顯示的速度狗热。通俗來(lái)講就是網(wǎng)站界面元素的繪制和呈現(xiàn)速度钞馁,如果使用 lighthouse 測(cè)量工具的話它會(huì)捕獲瀏覽器中處于加載中的頁(yè)面的多個(gè)圖片幀,然后計(jì)算幀之間的視覺渲染進(jìn)度匿刮。

  • TBT (Total Blocking Time):衡量從頁(yè)面首次開始渲染(FCP)之后到頁(yè)面實(shí)際可交互(TTI)的時(shí)間僧凰。通常我們?cè)L問一個(gè)網(wǎng)站時(shí)網(wǎng)站整體呈現(xiàn)后,有一段較短的時(shí)間我們不能和界面進(jìn)行交互熟丸,比如鼠標(biāo)點(diǎn)擊训措、鍵盤按鍵等,這段時(shí)間瀏覽器在進(jìn)行腳本及樣式的加載和執(zhí)行虑啤。

  • LCP (Largest Contentful Paint):測(cè)量視口中最大的內(nèi)容元素何繪制到屏幕所需的時(shí)間。通常包含這個(gè)元素的下載架馋、解析和渲染整個(gè)過程狞山。

  • CLS (Cumulative Layout Shift):一個(gè)衡量網(wǎng)站加載時(shí)整體布局抖動(dòng)情況的數(shù)值指標(biāo)。如果一個(gè)網(wǎng)站在加載過程中用戶界面多次抖動(dòng)和閃爍的話會(huì)可能引起用戶的輕度不適叉寂,因此應(yīng)該盡量減少網(wǎng)站的重排和重繪次數(shù)萍启。

2. 網(wǎng)站可訪問性

  • 網(wǎng)頁(yè)背景色和網(wǎng)站文字前景的對(duì)比度不能太低,否則會(huì)影響用戶閱讀。
  • 網(wǎng)頁(yè)鏈接標(biāo)簽 <a> 最好包含對(duì)鏈接的描述信息勘纯,比如:<a >[描述- nojsja 的 github 個(gè)人界面]</a>局服。
  • html 元素存在 lang 屬性指定當(dāng)前語(yǔ)言環(huán)境。
  • 正確的 html 語(yǔ)義化標(biāo)簽?zāi)茏屾I盤和讀屏器正常工作驳遵,通常一個(gè)網(wǎng)頁(yè)的結(jié)構(gòu)可以用語(yǔ)義化標(biāo)簽描述為:
<html lang="en">
  <head>
    <title>Document title</title>
    <meta charset="utf-8">
  </head>
  <body>
    <a class="skip-link" href="#maincontent">Skip to main</a>
    <h1>Page title</h1>
    <nav>
      <ul>
        <li>
          <a >Nav link</a>
        </li>
      </ul>
    </nav>
    <header>header</header>
    <main id="maincontent">
      <section>
        <h2>Section heading</h2>
          <p>text info</p>
        <h3>Sub-section heading</h3>
          <p>texgt info</p>
      </section>
    </main>
    <footer>footer</footer>
  </body>
</html>
  • 界面元素的 id 唯一性淫奔。
  • img 標(biāo)簽的 alt 屬性聲明。它指定了替代文本堤结,用于在圖像無(wú)法顯示或者用戶禁用圖像顯示時(shí)唆迁,代替圖像顯示在瀏覽器中的內(nèi)容。
  • form 元素內(nèi)部聲明 label 標(biāo)簽以讓讀屏器正確工作竞穷。
  • iframe 元素聲明 title 屬性來(lái)描述其內(nèi)部?jī)?nèi)容以便于讀屏器工作唐责。
  • aria 無(wú)障礙屬性和標(biāo)簽的使用,相關(guān)參考 >> aria reference
  • input[type=image]瘾带、object 標(biāo)簽添加 alt 屬性聲明:
<input type="image" alt="Sign in" src="./sign-in-button.png">
<object alt="report.pdf type="application/pdf" data="/report.pdf">
Annual report.
</object>
  • 需要使用 tab 按鍵聚焦特性的元素可以聲明 tabindex 屬性鼠哥,當(dāng)我們按 tab 鍵時(shí)焦點(diǎn)會(huì)依次切換。并且根據(jù)鍵盤序列導(dǎo)航的順序看政,值為 0 朴恳、非法值、或者沒有 tabindex 值的元素應(yīng)該放置在 tabindex 值為正值的元素后面:
1) tabindex=負(fù)值 (通常是tabindex=“-1”)帽衙,表示元素是可聚焦的菜皂,但是不能通過鍵盤導(dǎo)航來(lái)訪問到該元素,用JS做頁(yè)面小組件內(nèi)部鍵盤導(dǎo)航的時(shí)候非常有用厉萝。
2) tabindex="0" 恍飘,表示元素是可聚焦的,并且可以通過鍵盤導(dǎo)航來(lái)聚焦到該元素谴垫,它的相對(duì)順序是當(dāng)前處于的DOM結(jié)構(gòu)來(lái)決定的章母。
3) tabindex=正值,表示元素是可聚焦的翩剪,并且可以通過鍵盤導(dǎo)航來(lái)訪問到該元素乳怎;它的相對(duì)順序按照tabindex 的數(shù)值遞增而滯后獲焦。如果多個(gè)元素?fù)碛邢嗤?tabindex前弯,它們的相對(duì)順序按照他們?cè)诋?dāng)前DOM中的先后順序決定蚪缀。
  • table 元素中正確使用 thscope 讓行表頭和列表頭與其數(shù)據(jù)域一一對(duì)應(yīng):
<table>
  <caption>My marathon training log</caption>
  <thead>
    <tr>
      <th scope="col">Week</th>
      <th scope="col">Total miles</th>
      <th scope="col">Longest run</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">1</th>
      <td>14</td>
      <td>5</td>
    </tr>
    <tr>
      <th scope="row">2</th>
      <td>16</td>
      <td>6</td>
    </tr>
  </tbody>
</table>
  • video 元素指定 track文本字幕資源以方便聽障人士使用(需要有字幕資源文件):
<video width="300" height="200">
    <source src="marathonFinishLine.mp4" type="video/mp4">
    <track src="captions_en.vtt" kind="captions" srclang="en" label="english_captions">
    <track src="audio_desc_en.vtt" kind="descriptions" srclang="en" label="english_description">
    <track src="captions_es.vtt" kind="captions" srclang="es" label="spanish_captions">
    <track src="audio_desc_es.vtt" kind="descriptions" srclang="es" label="spanish_description">
</video>
  • li 列表標(biāo)簽放在容器組件 ulol 中使用。
  • heading 標(biāo)簽嚴(yán)格按照升序聲明恕出,配合 section 或其它元素(例如p標(biāo)簽)正確反映界面內(nèi)容結(jié)構(gòu):
<h1>Page title</h1>
<section>
  <h2>Section Heading</h2>
  …
    <h3>Sub-section Heading</h3>
</section>
  • 使用 <meta charset="UTF-8"> 指定網(wǎng)站字符集編碼询枚。
  • img 元素引用的圖片資源長(zhǎng)寬比應(yīng)該和 img 當(dāng)前應(yīng)用的長(zhǎng)寬比相同,不然可能造成圖片扭曲浙巫。
  • 添加 <!DOCTYPE html> 以防止瀏覽界面渲染異常金蜀。

3. 網(wǎng)站是否應(yīng)用了最佳實(shí)踐策略

> 1) 使用 target="_blank"<a> 鏈接如果沒有聲明 rel="noopener noreferrer" 存在安全風(fēng)險(xiǎn)刷后。

當(dāng)頁(yè)面鏈接至使用 target="_blank" 的另一個(gè)頁(yè)面時(shí),新頁(yè)面將與舊頁(yè)面在同一個(gè)進(jìn)程上運(yùn)行渊抄。如果新頁(yè)面正在執(zhí)行開銷極大的 JavaScript尝胆,舊頁(yè)面性能可能會(huì)受影響。并且新的頁(yè)面可以通過 window.opener 訪問舊的窗口對(duì)象护桦,比如它可以使用 window.opener.location = url 將舊頁(yè)面導(dǎo)航至不同的網(wǎng)址含衔。

> 2) 檢查瀏覽器端控制臺(tái)是否有告警和錯(cuò)誤提示,通過指示定位問題并解決嘶炭。

> 3) http 和 https 協(xié)議地址不混用

瀏覽器已經(jīng)逐漸開始禁止不用協(xié)議資源的混用抱慌,比如使用 http 協(xié)議的 web 服務(wù)器加載 https 協(xié)議開頭的資源,因此可能出現(xiàn)以下幾種情況:

  • 加載了混合內(nèi)容眨猎,但會(huì)出現(xiàn)警告抑进;
  • 不加載混合內(nèi)容,直接會(huì)顯示空白內(nèi)容睡陪;
  • 在加載混合內(nèi)容之前寺渗,會(huì)出現(xiàn)類似是否“顯示”,或存在不安全風(fēng)險(xiǎn)而被“阻止”的提示兰迫!

應(yīng)該考慮以下方式進(jìn)行優(yōu)化:

  • 將站點(diǎn)加載的部分協(xié)議混用外部資源放入自己的服務(wù)器進(jìn)行托管信殊;
  • 對(duì)于部署了 https 的網(wǎng)站,在網(wǎng)頁(yè)聲明 <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"將 http 請(qǐng)求轉(zhuǎn)換成 https 請(qǐng)求汁果;
  • 對(duì)于部署了 https 的網(wǎng)站涡拘,在服務(wù)器端添加請(qǐng)求首部:header("Content-Security-Policy: upgrade-insecure-requests")也可以達(dá)到一樣的效果;
  • 對(duì)于同時(shí)支持 http 和 https 訪問的網(wǎng)站,考慮使用相對(duì)協(xié)議据德,而不直接明文指定協(xié)議:<script src="http://path/to/js">鳄乏,瀏覽器發(fā)送請(qǐng)求時(shí)會(huì)根據(jù)當(dāng)前頁(yè)面協(xié)議進(jìn)行動(dòng)態(tài)切換。
  • 類同于使用相對(duì)協(xié)議棘利,使用相對(duì)URL也能達(dá)到目的橱野,不過增加了資源之間的耦合度:<script src="./path/to/js"></script>

> 4) 避免使用 AppCache

AppCache已被廢棄 考慮使用service worker的Cache API,

> 5) 避免使用 document.write()

對(duì)于網(wǎng)速較慢(2G善玫、3G或較慢的WLAN)的用戶水援,外部腳本通過document.write()動(dòng)態(tài)注入會(huì)使頁(yè)面內(nèi)容的顯示延遲數(shù)十秒。

> 6) 避免使用 mutation events

以下mutation events會(huì)損害性能茅郎,在DOM事件規(guī)范中已經(jīng)棄用:

  • DOMAttrModified
  • DOMAttributeNameChanged
  • DOMCharacterDataModified
  • DOMElementNameChanged
  • DOMNodeInserted
  • DOMNodeInsertedIntoDocument
  • DOMNodeRemoved
  • DOMNodeRemovedFromDocument
  • DOMSubtreeModified

建議使用 MutationObserver 替代

> 7) 避免使用 Web SQL

建議替換為IndexedDB

> 8) 避免加載過于龐大的 DOM 樹

大型的DOM樹會(huì)以多種方式降低頁(yè)面性能:

  • 網(wǎng)絡(luò)效率和負(fù)載性能蜗元,如果你的服務(wù)器發(fā)送一個(gè)大的DOM樹,你可能會(huì)運(yùn)送大量不必要的字節(jié)系冗。這也可能會(huì)減慢頁(yè)面加載時(shí)間奕扣,因?yàn)闉g覽器可能會(huì)解析許多沒有顯示在屏幕上的節(jié)點(diǎn)。
  • 運(yùn)行時(shí)性能毕谴。當(dāng)用戶和腳本與頁(yè)面交互時(shí)成畦,瀏覽器必須不斷重新計(jì)算節(jié)點(diǎn)的位置和樣式。一個(gè)大的DOM樹與復(fù)雜的樣式規(guī)則相結(jié)合可能會(huì)嚴(yán)重減慢渲染速度涝开。
  • 內(nèi)存性能循帐。如果使用通用查詢選擇器(例如,document.querySelectorAll('li') 您可能會(huì)無(wú)意中將引用存儲(chǔ)到大量的節(jié)點(diǎn))舀武,這可能會(huì)壓倒用戶設(shè)備的內(nèi)存功能拄养。

一個(gè)最佳的DOM樹:

  • 總共少于1500個(gè)節(jié)點(diǎn)。
  • 最大深度為32個(gè)節(jié)點(diǎn)银舱。
  • 沒有超過60個(gè)子節(jié)點(diǎn)的父節(jié)點(diǎn)瘪匿。
  • 一般來(lái)說,只需要在需要時(shí)尋找創(chuàng)建DOM節(jié)點(diǎn)的方法寻馏,并在不再需要時(shí)將其銷毀棋弥。

> 9) 允許用戶粘貼密碼

密碼粘貼提高了安全性,因?yàn)樗褂脩裟軌蚴褂妹艽a管理器诚欠。密碼管理員通常為用戶生成強(qiáng)密碼顽染,安全地存儲(chǔ)密碼,然后在用戶需要登錄時(shí)自動(dòng)將其粘貼到密碼字段中轰绵。
刪除阻止用戶粘貼到密碼字段的代碼粉寞。使用事件斷點(diǎn)中的Clipboard paste來(lái)打斷點(diǎn),可以快速找到阻止粘貼密碼的代碼左腔。比如下列這種阻止粘貼密碼的代碼:

var input = document.querySelector('input');
input.addEventListener('paste', (e) => {
  e.preventDefault();
});

4. 網(wǎng)站搜索引擎優(yōu)化SEO

  • 添加視口聲明 <meta name="viewport"> 并指定 with 和 device-width 來(lái)優(yōu)化移動(dòng)端顯示唧垦。
  • document 添加 title 屬性以讓讀屏器和搜索引擎正確識(shí)別網(wǎng)站內(nèi)容。
  • 添加 meta desctiption 標(biāo)簽來(lái)描述網(wǎng)站內(nèi)容 <meta name="description" content="Put your description here.">液样。
  • 為鏈接標(biāo)簽添加描述文本 <a href="videos.html">basketball videos</a>振亮,以清楚傳達(dá)這個(gè)超鏈接的指向內(nèi)容。
  • 使用正確的 href 鏈接地址以讓搜索引擎正確跟蹤實(shí)際網(wǎng)址蓄愁,以下是反例:<a onclick="goto('https://example.com')">
  • 不要使用 meta 標(biāo)簽來(lái)禁用搜索引擎爬蟲爬取你的網(wǎng)頁(yè)双炕,以下是反例:<meta name="robots" content="noindex"/>,相對(duì)的可以特殊的排除一些爬蟲爬却樽ァ:<meta name="AdsBot-Google" content="noindex"/>妇斤。
  • image 圖片元素中使用具有明確意圖和含義的 alt 屬性文字來(lái)說明圖片信息,并且避免使用一些非特定指代詞比如: "chart", "image", "diagram"(圖表丹拯、圖片)站超。
  • 不要使用插件來(lái)顯示您的內(nèi)容,即避免使用 embed乖酬、object死相、applet等標(biāo)簽來(lái)引入資源。
  • 正確放置 robots.txt 到網(wǎng)站根目錄下咬像,它描述了此網(wǎng)站中的哪些內(nèi)容是不應(yīng)被搜索引擎的獲取的算撮,哪些是可以被獲取的生宛。通常一些后臺(tái)文件(如css、js)和用戶隱私的文件不用被搜索引擎抓取肮柜,另外有些文件頻繁被蜘蛛抓取陷舅,但是這些文件又是不重要的,那么可以用robots.txt進(jìn)行屏蔽审洞。
    一個(gè)實(shí)例:
User-agent: *
Allow: /
Disallow: /wp-admin/
Disallow: /wp-includes/
Disallow: /wp-content/plugins/
Disallow: /?r=*
  • 向搜索引擎提交網(wǎng)站地圖 sitemap.xml莱睁,xml格式的文本就是專門拿來(lái)給電腦進(jìn)行閱讀的語(yǔ),而 sitemap.xml就是搜尋引擎利用這個(gè)規(guī)范芒澜,讓網(wǎng)站主可以使用它來(lái)制作一個(gè)包含了網(wǎng)站內(nèi)所有網(wǎng)頁(yè)的目錄檔案仰剿,提供給搜尋引擎的爬蟲閱讀。就像是一個(gè)地圖一樣痴晦,讓搜尋引擎可以知道網(wǎng)站內(nèi)到底有些什么網(wǎng)頁(yè)南吮。

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  
  <url>
    <loc>https://nojsja.github.io/blogs/2019/10/26/63567fa4.html/</loc>
    
    <lastmod>2021-04-29T10:02:04.853Z</lastmod>
    
  </url>
  
  <url>
    <loc>https://nojsja.github.io/blogs/2020/03/26/7507699.html/</loc>
    
    <lastmod>2021-04-29T10:01:30.661Z</lastmod>
    
  </url>

</urlset>

性能測(cè)試工具 lighthouse


以上提到的性能監(jiān)測(cè)指標(biāo)通過 lighthouse 網(wǎng)站性能監(jiān)測(cè)工具可以自動(dòng)化分析和生成性能測(cè)試結(jié)果數(shù)據(jù),較新版本的 chrome 和 采用 chromium 架構(gòu)的 edge 瀏覽器都已經(jīng)自帶了誊酌,較低版本的 chrome 瀏覽器可以通過在商店搜索插件安裝旨袒。安裝后使用 F12 打開控制臺(tái),切換到 lighthouse 一欄即可直接使用了:

如圖术辐,可以自行決定勾選測(cè)試項(xiàng)目和針對(duì)測(cè)試平臺(tái)(桌面/移動(dòng)端)砚尽,最后點(diǎn)擊生成報(bào)告即可開始運(yùn)行自動(dòng)化測(cè)試任務(wù),并在測(cè)試完成后打開一個(gè)分析結(jié)果頁(yè)面:

lighthouse

結(jié)果界面:

lighthouse-test

至此我們就能對(duì)網(wǎng)站的整體性能進(jìn)行一些評(píng)估了辉词,上圖中的Performance必孤、AccessibilityBest Practices瑞躺、和 SEO 依次對(duì)應(yīng)上文我們提及的網(wǎng)站整體性能敷搪、網(wǎng)站可訪問性、網(wǎng)站最佳實(shí)踐幢哨、搜索引擎SEO優(yōu)化等指標(biāo)赡勘。我們可以點(diǎn)擊每一個(gè)具體項(xiàng)目來(lái)查看測(cè)試工具給出的優(yōu)化建議:

image

測(cè)試結(jié)果很大程度上取決于你的 http 靜態(tài)資源服務(wù)器的資源加載速度,比如在不使用代理的情況下捞镰,使用 github pages 服務(wù)來(lái)托管靜態(tài)資源會(huì)比使用國(guó)內(nèi)的 gitee pages 托管服務(wù)稍慢闸与,而且可能出現(xiàn)部分資源加載失敗的問題。因此國(guó)內(nèi)用戶可以使用 gitee pages 來(lái)替代 github pages岸售,不過 gitee 非付費(fèi)用戶沒有代碼自動(dòng)構(gòu)建部署功能践樱,需要使用下文提到的 github action 來(lái)進(jìn)行自動(dòng)化登錄并觸發(fā)構(gòu)建和部署。

注意: 某些瀏覽器上安裝的插件會(huì)影響測(cè)試結(jié)果凸丸,因?yàn)椴寮赡軙?huì)發(fā)起請(qǐng)求加載其它腳本啥的拷邢。這種情況下可以直接使用 npm 包管理器全局安裝 npm install -g lighthouse,然后使用命令行啟動(dòng)測(cè)試流程:lighthouse https://nojsja.gitee.io/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'屎慢。最終會(huì)根據(jù)指定的地址生成一個(gè)可直接瀏覽器測(cè)試結(jié)果網(wǎng)頁(yè)文件 lighthouse.html瞭稼,可以直接打開進(jìn)行性能排查忽洛。

基于 hexo 框架的網(wǎng)站優(yōu)化


之前寫的一篇文章 《前端性能優(yōu)化技巧詳解(1)》,詳細(xì)的描述了前端性能優(yōu)化的一些方面环肘,這篇文章不會(huì)再羅列每一點(diǎn)脐瑰,只會(huì)對(duì)博客中實(shí)際應(yīng)用的優(yōu)化手段進(jìn)行說明,大致被劃分成這幾個(gè)方面:

  1. 優(yōu)化資源加載時(shí)間
  2. 優(yōu)化界面運(yùn)行性能
  3. 網(wǎng)站最佳實(shí)踐
  4. 網(wǎng)站 SEO 優(yōu)化

1. 優(yōu)化資源加載時(shí)間

常見的加載時(shí)間優(yōu)化方式包含以下:

  • 提高網(wǎng)頁(yè)資源并行加載能力
  • 延遲加載不必要的外部資源
  • 減少碎片化的外部資源請(qǐng)求,小文件做合并
  • 增加網(wǎng)站帶寬

? 使用 defer/async 異步下載 script 腳本資源

HTML在解析時(shí)遇到聲明的<script>腳本會(huì)立即下載和執(zhí)行荠商,往往會(huì)延遲界面剩余部分的解析莱没,造成界面白屏的情況酷鸦。比較古老的優(yōu)化方式之一就是將腳本放到HTML文檔末尾饰躲,這樣子解決了白屏的問題,可是在DOM文檔結(jié)構(gòu)復(fù)雜冗長(zhǎng)時(shí)臼隔,也會(huì)造成一定的界面腳本下載和執(zhí)行延遲嘹裂,script標(biāo)簽新屬性async和defer可以解決此類問題:

  • defer腳本:聲明 defer 屬性的外部<script>腳本下載時(shí)不會(huì)阻塞HTML的解析和渲染,并且會(huì)在HTML渲染完成并且可實(shí)際操作之后開始執(zhí)行(DOMContentLoaded事件被觸發(fā)之前)摔握,各個(gè)腳本解析執(zhí)行順序?qū)?yīng)聲明時(shí)的位置順序寄狼,執(zhí)行完成后會(huì)觸發(fā)頁(yè)面DOMContentLoaded事件。
  • async腳本:聲明 async 屬性的外部<script>腳本下載時(shí)不會(huì)阻塞HTML的解析和渲染氨淌,各個(gè)腳本的下載和執(zhí)行完全獨(dú)立泊愧,下載完成后即開始執(zhí)行,所以執(zhí)行順序不固定盛正,與DOMContentLoaded事件的觸發(fā)沒有關(guān)聯(lián)性删咱。

在我的博客網(wǎng)站中有使用 Bootstrap 外部依賴的樣式和js腳本,但是需要確保他們的聲明順序在靠前的位置豪筝,因?yàn)槭褂卯惒郊夹g(shù)之后痰滋,內(nèi)聯(lián)同步執(zhí)行的其它<script>腳本的執(zhí)行順序就不能保證了,因此不能使用 defer/async 屬性進(jìn)行優(yōu)化续崖。

通常 async/defer 會(huì)用于優(yōu)化一些獨(dú)立的子組件依賴腳本的加載袜刷,比如用于博客文章中的導(dǎo)航條跳轉(zhuǎn)的腳本墩蔓,它的執(zhí)行順序完全不收到其它部分的制約昏名,因此可以獨(dú)立出來(lái)使用 async 屬性進(jìn)行優(yōu)化样刷。但是需要確保該腳本作用的 導(dǎo)航條 DOM元素聲明位于腳本引入位置之前镇饮,以防止出現(xiàn)腳本執(zhí)行時(shí) DOM 元素還未渲染的狀態(tài),引起腳本錯(cuò)誤。

? 使用 async 函數(shù)異步加載外部資源

以下 async 函數(shù)作用就是根據(jù)傳入的 url 創(chuàng)建 link/script 標(biāo)簽并添加到 <head> 標(biāo)簽中以動(dòng)態(tài)加載外部資源肺缕,并且在存在回調(diào)函數(shù)時(shí)監(jiān)聽資源加載情況,加載完畢后再執(zhí)行回調(diào)函數(shù)彤路。值得注意的是本方法與直接通過script 聲明依賴資源的不同之處在于不會(huì)阻塞界面,腳本的下載和執(zhí)行都是異步的。

博客中常用于在某個(gè)特殊的情況下利用編程方法動(dòng)態(tài)載入外部依賴庫(kù)丽涩,并在回調(diào)函數(shù)內(nèi)進(jìn)行外部庫(kù)的初始化惑惶。例如我的博客使用了一個(gè)音樂播放器組件打瘪,在網(wǎng)頁(yè)可視區(qū)域滾動(dòng)到包含這個(gè)組件的尚未初始化的 DOM 元素時(shí)崔泵,就是用 async 來(lái)請(qǐng)求服務(wù)器的 js 腳本資源,加載完成后在回調(diào)函數(shù)里初始化這個(gè)音樂播放器。

/**
  * async [異步腳本加載]
  * @author nojsja
  * @param  {String} u [資源地址]
  * @param  {Function} c [回調(diào)函數(shù)]
  * @param  {String} tag [加載資源類型 link | script]
  * @param  {Boolean} async [加載 script 資源時(shí)是否需要聲明 async 屬性]
  */
function async(u, c, tag, async) {
    var head = document.head ||
      document.getElementsByTagName('head')[0] ||
      document.documentElement;
    var d = document, t = tag || 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    async = ['async', 'defer'].includes(async) ? async : !!async;
    
    switch(t) {
      case 'script':
        o.src = u;
        if (async) o[async] = true;
        break;
      case 'link':
        o.type = "text/css";
        o.href = u;
        o.rel = "stylesheet";
        break;
      default:
        o.src = u;
        break;
    }

    /* callback */

    if (c) { 
      if (o.readyState) {//IE
        o.onreadystatechange = function (e) {
          if (o.readyState == "loaded"
            || o.readyState == "complete") {
            o.onreadystatechange = null;
            c(null, e)();
          }
        };
      } else {//其他瀏覽器
        o.onload = function (e) {
          c(null, e);
        };
      }
    }

    s.parentNode.insertBefore(o, head.firstChild);
  }

? 使用瀏覽器 onfocus 事件延遲外部資源加載

通過用戶和界面交互觸發(fā)一些事件后,滿足了外部資源加載的條件,再觸發(fā)外部資源的加載塞弊,也屬于延遲加載的一種優(yōu)化。

例如我的博客中右側(cè)導(dǎo)航條區(qū)域有個(gè)搜索框可以搜索博客文章诀黍,本身這個(gè)搜索是通過查找本地預(yù)先生成的一個(gè)資源索引靜態(tài)XML文件來(lái)實(shí)現(xiàn)的。文章和內(nèi)容較多這個(gè)這個(gè)XML文件就會(huì)變得龐大,如果在網(wǎng)頁(yè)首次加載時(shí)就下載它一定會(huì)造成網(wǎng)頁(yè)帶寬和網(wǎng)絡(luò)請(qǐng)求數(shù)量的占用郁轻。因此考慮在用戶點(diǎn)擊搜索框?qū)⒔裹c(diǎn)集中到上面時(shí)再觸發(fā)XML的異步下載故觅,同時(shí)為了不影響使用體驗(yàn),可以在加載過程中設(shè)置 loading 效果以指示用戶延遲操作。

? 使用 preload/prefetch/preconnect 進(jìn)行預(yù)加載優(yōu)化

  • preload 用來(lái)預(yù)加載當(dāng)前頁(yè)面所需的資源它浅,如圖像,CSS镊折,JavaScript 和字體文件,它的加載優(yōu)先級(jí)比 prefetch 更高,同時(shí)也要注意 preload 并不會(huì)阻塞 window 的 onload 事件升熊。博客中有使用它來(lái)預(yù)加載 css 中引用的字體:<link href="/blogs/fonts/fontawesome-webfont.woff2?v=4.3.0" rel=preload as="font">,針對(duì)不同的資源類型需要添加不同的 as 標(biāo)記信息,如果是跨域加載的話注意添加 crossorigin 屬性聲明渊抽。
  • prefetch 是一個(gè)低優(yōu)先級(jí)的資源提示,一旦一個(gè)頁(yè)面加載完畢就會(huì)開始下載其他的預(yù)加載資源帮辟,并且將他們存儲(chǔ)在瀏覽器的緩存中。其中 prefretch 又包含:link蔓榄、DNS 和 prerendering 三中類型的預(yù)加載請(qǐng)求。link prefetch 比如:<link rel="prefetch" href="/path/to/pic.png"> 允許瀏覽器獲取資源并將他們存儲(chǔ)在緩存中;DNS prefetch 允許瀏覽器在用戶瀏覽頁(yè)面時(shí)在后臺(tái)運(yùn)行 DNS prerender 和 prefetch 非常相似勉躺,它們都優(yōu)化了下一頁(yè)資源的未來(lái)請(qǐng)求柳弄,區(qū)別是 prerender 在后臺(tái)渲染了整個(gè)頁(yè)面嚣伐,因此應(yīng)該小心使用,可能會(huì)造成網(wǎng)絡(luò)帶寬的浪費(fèi)基茵。
  • preconnect 允許瀏覽器在一個(gè) HTTP 請(qǐng)求正式發(fā)給服務(wù)器前預(yù)先執(zhí)行一些操作,這包括 DNS 解析,TLS 協(xié)商烙肺,TCP 握手氏堤,這消除了往返延遲并為用戶節(jié)省了時(shí)間。比如博客中:不算子統(tǒng)計(jì)庫(kù)的網(wǎng)頁(yè)預(yù)連接 <link rel="preconnect" crossorigin>脚祟。

? 使用 hexo 插件壓縮代碼文件和圖片文件

壓縮靜態(tài)資源也是一種節(jié)省網(wǎng)絡(luò)帶寬,提高請(qǐng)求響應(yīng)速度的方式。通常采用工程化的方式進(jìn)行配置娃循,而不用手動(dòng)對(duì)每張圖片進(jìn)行壓縮。我的博客中使用了 hexo 的一款壓縮插件 Hexo-all-minifier捞蚂,通過壓縮 HTML、CSS、JS 和圖片來(lái)優(yōu)化博客訪問速度解寝。

安裝:

npm install hexo-all-minifier --save

config.yml 配置文件中啟用:

# ---- 代碼和資源壓縮
html_minifier:
  enable: true
  exclude:

css_minifier:
  enable: true
  exclude: 
    - '*.min.css'

js_minifier:
  enable: true
  mangle: true
  compress: true
  exclude: 
    - '*.min.js'

image_minifier:
  enable: true
  interlaced: false
  multipass: false
  optimizationLevel: 2 # 壓縮等級(jí)
  pngquant: false
  progressive: true # 是否啟用漸進(jìn)式圖片壓縮

資源的壓縮比較消耗性能和時(shí)間与学,可以考慮在開發(fā)環(huán)境下不啟用這些插件以加快開發(fā)環(huán)境啟動(dòng)抑片。比如單獨(dú)指定一個(gè) _config.dev.yml 然后把上述插件全部關(guān)閉即可截汪,參考 package.json 中的腳本字段聲明:

{
...
"scripts": {
    "prestart": "hexo clean --config config.dev.yml; hexo generate --config config.dev.yml",
    "prestart:prod": "hexo clean; hexo generate",
    "predeploy": "hexo clean; hexo generate",
    "start": "hexo generate --config config.dev.yml; hexo server --config config.dev.yml",
    "start:prod": "hexo generate --config config.dev.yml; hexo server",
    "performance:prod": "lighthouse https://nojsja.gitee.io/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'",
    "performance": "lighthouse http://localhost:4000/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'",
    "deploy": "hexo deploy"
  }
}

? 編寫 hexo-img-lazyload 插件增加圖片懶加載特性

在進(jìn)行博客優(yōu)化的時(shí)候?yàn)榱藢W(xué)習(xí) hexo 自己的插件系統(tǒng)焰枢,使用 IntersectionObserver API 來(lái)編寫了一個(gè)圖片懶加載插件:hexo-img-lazyload暑椰,可以通過 npm 命令進(jìn)行安裝:npm i hexo-img-lazyload

效果預(yù)覽:

hexo-immg-lazyload

插件主要原理就是監(jiān)聽博客構(gòu)建流程的鉤子事件召夹,拿到構(gòu)建好的代碼字符串昏兆,然后代碼中原生的圖片聲明比如:<img src="path/to/xx.jpg">通過正則全局匹配并替換為:<img src="path/to/loading" data-src="path/to/xx.jpg">

function lazyProcessor(content, replacement) {
    
    return content.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2, p3) {
        if (/data-loaded/gi.test(str)) {
            return str;
        }

        if (/no-lazy/gi.test(str)) {
            return str;
        }
        return `<img ${p1} src="${emptyStr}" lazyload data-loading="${replacement}" data-src="${p2}" ${p3}>`;
    });  
}

替換之后還需要使用 hexo 的代碼注入功能將我們自己編寫的代碼注入到每個(gè)構(gòu)建好的界面中。

hexo 代碼注入:

/* registry scroll listener */
hexo.extend.injector.register('body_end', function() {
  const script = `
    <script>
      ${observerStr}
    </script>`;

  return script;
}, injectionType)

被注入的用于監(jiān)聽待加載圖片元素是否進(jìn)入可視區(qū)域以進(jìn)行動(dòng)態(tài)加載的部分代碼曲梗,使用了 IntersectionObserver API 而不是 window.onscroll 事件的方式,前者具有更好的性能笤虫,由瀏覽器統(tǒng)一監(jiān)聽所有元素位置信息更改并分發(fā)滾動(dòng)事件結(jié)果:


(function() {
  /* avoid garbage collection */
  window.hexoLoadingImages = window.hexoLoadingImages || {};

  function query(selector) {
    return document.querySelectorAll(selector);
  }
  
  /* registry listener */
  if (window.IntersectionObserver) {
  
    var observer = new IntersectionObserver(function (entries) {

      entries.forEach(function (entry) {
        // in view port
        if (entry.isIntersecting) {
          observer.unobserve(entry.target);

          // proxy image
          var img = new Image();
          var imgId = "_img_" + Math.random();
          window.hexoLoadingImages[imgId] = img;

          img.onload = function() {
            entry.target.src = entry.target.getAttribute('data-src');
            window.hexoLoadingImages[imgId] = null;
          };
          img.onerror = function() {
            window.hexoLoadingImages[imgId] = null;
          }

          entry.target.src = entry.target.getAttribute('data-loading');
          img.src = entry.target.getAttribute('data-src');

        }
      });
    });
    
    query('img[lazyload]').forEach(function (item) {
      observer.observe(item);
    });
  
  } else {
  /* fallback */
    query('img[lazyload]').forEach(function (img) {
      img.src = img.getAttribute('data-src');
    });
  
  }
}).bind(window)();

? 使用 IntersectionObserver API 懶加載其它資源

IntersectionObserver API由于性能更好已經(jīng)在我的博客中作為一種主要的資源懶加載方式,我還使用它來(lái)優(yōu)化博客評(píng)論組件 Valine 的加載罚拟。一般評(píng)論組件都位于博客文章的最下方,因此剛載入文章頁(yè)面時(shí)完全沒有必要進(jìn)行評(píng)論組件的資源加載,可以考慮使用 IntersectionObserver 監(jiān)聽評(píng)論組件是否進(jìn)入視口叔扼,進(jìn)入后再使用 async 異步腳本下載并回調(diào)執(zhí)行評(píng)論系統(tǒng)初始化。

另一方面每篇文章底部的音樂播放器 Aplayer 也使用了類似的加載策略,可以說優(yōu)化效果屢試不爽价捧!

? 使用 CDN 加載外部依賴腳本

CDN 即內(nèi)容分發(fā)網(wǎng)絡(luò)。CDN 服務(wù)商將靜態(tài)資源緩存到遍布全國(guó)的高性能加速節(jié)點(diǎn)上推正,當(dāng)用戶訪問相應(yīng)的業(yè)務(wù)資源時(shí)掌测,CDN系統(tǒng)能夠?qū)崟r(shí)地根據(jù)網(wǎng)絡(luò)流量和各節(jié)點(diǎn)的連接負(fù)載狀況、到用戶的距離和響應(yīng)時(shí)間 等綜合信息將用戶的請(qǐng)求重新導(dǎo)向離用戶最近的服務(wù)節(jié)點(diǎn)上竞端,使內(nèi)容能夠傳輸?shù)母欤臃€(wěn)定乘陪⊥程ǎ可以提升首次請(qǐng)求的響應(yīng)能力。

博客中一些公用外部庫(kù)比如 BootstrapjQuery 都是使用的外部 CDN 資源地址啡邑,一方面可以減小當(dāng)前主站的網(wǎng)頁(yè)帶寬消耗贱勃,另一方面 CDN 也能提供一些資源下載加速。

? 使用 Aplayer 替代 iframe 加載網(wǎng)易云音樂

博客之前的版本會(huì)在文章界面的底部嵌入一個(gè)網(wǎng)易云音樂自己的播放器,這個(gè)播放器其實(shí)是一個(gè) iframe 像這樣:

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="http://music.163.com/outchain/player?type=2&id=781246&auto=1&height=66"></iframe>

iframe 加載的時(shí)候會(huì)加載一堆東西舞丛,雖然可以通過 lazy 屬性來(lái)進(jìn)行懶加載怀骤,不過iframe 也有很多缺點(diǎn):

  • iframe會(huì)阻塞主頁(yè)面的onload事件
  • iframe和主頁(yè)面共享HTTP連接池末患,而瀏覽器對(duì)相同域的連接有限制哗讥,所以會(huì)影響頁(yè)面的并行加載
  • iframe不利于網(wǎng)頁(yè)布局
  • iframe對(duì)移動(dòng)端不友好
  • iframe的反復(fù)重新加載可能導(dǎo)致一些瀏覽器的內(nèi)存泄露
  • iframe中的數(shù)據(jù)傳輸復(fù)雜
  • iframe不利于SEO

新版本將 iframe 播放器換成了 Aplayer 并且把自己喜歡的一個(gè)歌曲列表上傳到了另一個(gè) gitee pages 倉(cāng)庫(kù) 進(jìn)行靜態(tài)托管瑞驱,可以通過以下方式在博客底部加載一個(gè)自定義歌曲列表:

var ap = new APlayer({
  container: document.getElementById('aplayer'),
  theme: '#e9e9e9',
  audio: [{
    name: '存在信號(hào)',
    artist: 'AcuticNotes',
    url: 'https://nojsja.gitee.io/static-resources/audio/life-signal.mp3',
    cover: 'https://nojsja.gitee.io/static-resources/audio/life-signal.jpg'
    
  },
  {
    name: '遺サレタ場(chǎng)所/斜光',
    artist: '岡部啓一',
    url: 'https://nojsja.gitee.io/static-resources/audio/%E6%96%9C%E5%85%89.mp3',
    cover: 'https://nojsja.gitee.io/static-resources/audio/%E6%96%9C%E5%85%89.jpg'
  }]
});

預(yù)覽圖:

aplayer

2. 優(yōu)化界面運(yùn)行性能

? 優(yōu)化頁(yè)面的回流和重繪情況

1)概念

回流(重排闻书,reflow)和重繪(repaint)是瀏覽器渲染網(wǎng)頁(yè)必不可少的一個(gè)過程,回流主要HTML渲染過程中元素空間位置和大小改變引起的 DOM 樹重新渲染池摧,而重繪是由于節(jié)點(diǎn)的樣式屬性發(fā)生改變,并不會(huì)影響空間布局棘幸。從性能而言回流的性能消耗大,而且容易產(chǎn)生級(jí)聯(lián)效應(yīng)菇晃,即一個(gè)正常 DOM 樹的流布局中荒辕,一個(gè)元素發(fā)生回流后卓囚,該元素位置之后的元素全部都會(huì)發(fā)生回流并重新渲染飞袋,重繪相對(duì)性能消耗更小沸版。

2)怎樣有效判斷界面的回流和重繪情況卧晓?

其實(shí)一般基于 chromium 架構(gòu)瀏覽器都附帶一個(gè)網(wǎng)頁(yè)開發(fā)工具 Devtools抖锥,但可以說絕大多數(shù)前端開發(fā)者都沒有認(rèn)真了解過這個(gè)工具的具體用途屎飘,只是把它用作簡(jiǎn)單的 log調(diào)試掰烟、網(wǎng)頁(yè)請(qǐng)求追蹤和樣式調(diào)試這些基礎(chǔ)功能∫诒猓回流和重繪也是可以通過它來(lái)進(jìn)行可視化度量的:F12打開 Devtools,找到右上角三個(gè)點(diǎn)的折疊按鈕依次打開 -> More Tools(更多工具) -> Rendering (渲染) - 勾選前兩項(xiàng) Paint Flashing (高亮重繪區(qū)域) 和 Layout Shift Regions (高亮回流區(qū)域),現(xiàn)在重新回到你打開的頁(yè)面進(jìn)行操作霹琼,操作過程中發(fā)生了回流的區(qū)域會(huì)變成藍(lán)色胃夏,發(fā)生了重繪的區(qū)域會(huì)變成綠色蚕愤,持續(xù)時(shí)間不長(zhǎng)答恶,注意觀察。

Devtools

Repaint:

Repaint

Reflow:

Reflow

除了用于可視化界面回流/重繪的情況萍诱,Devtools 還有其他一些很實(shí)用的功能悬嗓,比如:Coverage Tools 可以用于分析界面上引入的外部 css/js 腳本內(nèi)容的使用覆蓋率,就是說我們能通過這個(gè)工具衡量引入的外部文件的使用情況裕坊,使用頻次較低的外部資源可以考慮內(nèi)聯(lián)或是直接手寫實(shí)現(xiàn)包竹,提升引入外部資源的性價(jià)比

? 使用節(jié)流和去抖思想優(yōu)化滾動(dòng)事件監(jiān)聽

在面對(duì)一些需要進(jìn)行調(diào)用控制的函數(shù)高頻觸發(fā)場(chǎng)景時(shí)籍凝,可能有人會(huì)對(duì)何時(shí)使用節(jié)流何時(shí)使用去抖產(chǎn)生疑問周瞎。這里通過一個(gè)特性進(jìn)行簡(jiǎn)單區(qū)分:如果你需要保留短時(shí)間內(nèi)高頻觸發(fā)的最后一次結(jié)果時(shí),那么使用去抖函數(shù)饵蒂,如果你需要對(duì)函數(shù)的調(diào)用次數(shù)進(jìn)行限制声诸,以最佳的調(diào)用間隔時(shí)間保持函數(shù)的持續(xù)調(diào)用而不關(guān)心是否是最后一次調(diào)用結(jié)果時(shí),請(qǐng)使用節(jié)流函數(shù)退盯。

比如 echarts 圖常常需要在窗口 resize 之后重新使用數(shù)據(jù)渲染彼乌,但是直接監(jiān)聽 resize 事件可能導(dǎo)致短時(shí)間內(nèi)渲染函數(shù)被觸發(fā)多次泻肯。我們可以使用函數(shù)去抖的思想,監(jiān)聽 resize 事件后在監(jiān)聽器函數(shù)里獲取參數(shù)再使用參數(shù)調(diào)用事先初始化好了的 throttle 函數(shù)慰照,保證 resize 過程結(jié)束后能觸發(fā)一次實(shí)際的 echarts 重渲染即可软免。

這里給出節(jié)流函數(shù)和去抖函數(shù)的簡(jiǎn)單實(shí)現(xiàn):

  /**
    * fnDebounce [去抖函數(shù)]
    * @author nojsja
    * @param  {Function} fn [需要被包裹的原始函數(shù)邏輯]
    * @param  {Numberl} timeout [延遲時(shí)間]
    * @return {Function} [高階函數(shù)]
    */
  var fnDebounce = function(fn, timeout) {
    var time = null;

    return function() {
      if (!time) return time = Date.now();
      if (Date.now() - time >= timeout) {
        time = null;
        return fn.apply(this, [].slice.call(arguments));
      } else {
        time = Date.now();
      }
    };
  };

  /**
    * fnThrottle [節(jié)流函數(shù)]
    * @author nojsja
    * @param  {Function} fn [需要被包裹的原始函數(shù)邏輯]
    * @param  {Numberl} timeout [延遲時(shí)間]
    * @return {Function} [高階函數(shù)]
    */
  var fnThrottle = function(fn, timeout) {
    var time = null;

    return function() {
      if (!time) return time = Date.now();
      if ((Date.now() - time) >= timeout) {
        time = null;
        return fn.apply(this, [].slice.call(arguments));
      }
    };
  };

博客中文章右側(cè)的內(nèi)容導(dǎo)航欄會(huì)根據(jù)滾動(dòng)條位置自動(dòng)切換 fixed 布局和一般流布局,這么做是為了讓導(dǎo)航欄在閱讀文章過程中也能正常呈現(xiàn)焚挠,不會(huì)被隱藏到頂部:

  /* 限150ms才能觸發(fā)一次滾動(dòng)檢測(cè) */
  (window).on('scroll', fnThrottle(function() {
      var rectbase = $$tocBar.getBoundingClientRect();
      if (rectbase.top <= 0) {
        $toc.css('left', left);
        (!$toc.hasClass('toc-fixed')) && $toc.addClass('toc-fixed');
        $toc.hasClass('toc-normal') && $toc.removeClass('toc-normal');
      } else {
        $toc.css('left', '');
        $toc.hasClass('toc-fixed') && $toc.removeClass('toc-fixed');
        (!$toc.hasClass('toc-normal')) && $toc.addClass('toc-normal');
        ($$toc.scrollTop > 0) && ($$toc.scrollTop = 0);
      }
  }, 150));

? IntersectionObserver API 的 polyfill 兼容策略

文章中提到 IntersectionObserver API 已經(jīng)用于博客中各個(gè)界面組件的懶加載功能膏萧,它的性能更好、功能也更加全面蝌衔。但是網(wǎng)頁(yè)開發(fā)中我們通常會(huì)考慮各個(gè) API 的兼容性榛泛,這里可以通過 Can I Use 這個(gè)兼容性報(bào)告網(wǎng)站進(jìn)行查看,從下圖可知這個(gè) API 的兼容情況還是可以的噩斟,桌面端很多較高版本瀏覽器均已支持:

caniuse

因此為了解決個(gè)別低版本瀏覽器的兼容性問題曹锨,這里采用了一種比較極端的處理方式。常規(guī)情況下我們需要引入外部 [xxx].polyfill.js (xxx為相應(yīng)的 API) 來(lái)為低版本瀏覽器添加相應(yīng)功能剃允,但是對(duì)于高版本瀏覽器自身已經(jīng)支持了這個(gè) API,卻要重復(fù)下載 polyfill 庫(kù)斥废,造成網(wǎng)頁(yè)請(qǐng)求數(shù)和帶寬資源的浪費(fèi)牡肉。因此我這里不采用這種方式捧灰,因?yàn)檫@個(gè) API 大部分瀏覽器已經(jīng)支持,我們默認(rèn)不使用 <script> 標(biāo)簽引入 polyfill.js 而是通過腳本判斷當(dāng)前瀏覽器是否支持此 API统锤,如果不支持的話再使用同步XHR請(qǐng)求遠(yuǎn)程 下載polyfill 文件毛俏,下載后使用 eval(...) 的方式執(zhí)行整個(gè)腳本。使用同步方式會(huì)阻塞當(dāng)前 js 執(zhí)行線程饲窿,請(qǐng)謹(jǐn)慎使用煌寇,此處是為了保證 IntersectionObserver 以高優(yōu)先級(jí)方式被注入到網(wǎng)頁(yè)中,不然可能引發(fā)一些使用了此 API 腳本錯(cuò)誤逾雄。

<!-- 此腳本被放置在靠近頁(yè)面首部某個(gè)位置 -->
<script>
  if ('IntersectionObserver' in window &&
    'IntersectionObserverEntry' in window &&
    'intersectionRatio' in window.IntersectionObserverEntry.prototype) {

    if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
      Object.defineProperty(window.IntersectionObserverEntry.prototype,
        'isIntersecting', {
        get: function () {
          return this.intersectionRatio > 0;
        }
      });
    }
  } else {
    /* load polyfill sync */
    sendXMLHttpRequest({
      url: '/blogs/js/intersection-observer.js',
      async: false,
      method: 'get',
      callback: function(txt) {
        eval(txt);
      }
    });
  }
</script>

? 使用 IntersectionObserver 替代原生 onscroll 事件監(jiān)聽

IntersectionObserver 通常用于界面中的一些相交檢測(cè)場(chǎng)景:

  • 圖片懶加載 – 當(dāng)圖片滾動(dòng)到可見時(shí)才進(jìn)行加載
  • 內(nèi)容無(wú)限滾動(dòng) – 用戶滾動(dòng)到接近滾動(dòng)容器底部時(shí)直接加載更多數(shù)據(jù)阀溶,而無(wú)需用戶操作翻頁(yè),給用戶一種網(wǎng)頁(yè)可以無(wú)限滾動(dòng)的錯(cuò)覺
  • 檢測(cè)廣告的曝光情況——為了計(jì)算廣告收益嘲驾,需要知道廣告元素的曝光情況
  • 在用戶看見某個(gè)區(qū)域時(shí)執(zhí)行任務(wù)淌哟、播放視頻

以內(nèi)容無(wú)限滾動(dòng)為例,古老的相交檢測(cè)方案就是使用 scroll 事件監(jiān)聽滾動(dòng)容器辽故,在監(jiān)聽器函數(shù)中獲取滾動(dòng)元素的幾何屬性判斷元素是否已經(jīng)滾動(dòng)到底部徒仓。我們知道scrollTop等屬性的獲取和設(shè)置都會(huì)導(dǎo)致頁(yè)面回流,并且如果界面需要綁定多個(gè)監(jiān)聽函數(shù)到scroll事件進(jìn)行類似操作的時(shí)候誊垢,頁(yè)面性能會(huì)大打折扣:

  /* 滾動(dòng)監(jiān)聽 */
  onScroll = () => {
    const { 
      scrollTop, scrollHeight, clientHeight
    } = document.querySelector('#target');
    
    /* 已經(jīng)滾動(dòng)到底部 */
    // scrollTop(向上滾動(dòng)的高度)掉弛;clientHeight(容器可視總高度)症见;scrollHeight(容器的總內(nèi)容長(zhǎng)度)
    if (scrollTop + clientHeight === scrollHeight) { /* do something ... */ }
  }

這里以一個(gè)簡(jiǎn)單實(shí)現(xiàn)的圖片懶加載功能來(lái)介紹下它的使用方式,詳細(xì)使用可以查看博客:《前端性能優(yōu)化技巧詳解(1)》殃饿。

(function lazyload() {

  var imagesToLoad = document.querySelectorAll('image[data-src]');

  function loadImage(image) {
    image.src = image.getAttribute('data-src');
    image.addEventListener('load', function() {
      image.removeAttribute('data-src');
    });
  }

  var intersectionObserver = new IntersectionObserver(function(items, observer) {
    items.forEach(function(item) {
      /* 所有屬性:
        item.boundingClientRect - 目標(biāo)元素的幾何邊界信息
        item.intersectionRatio - 相交比 intersectionRect/boundingClientRect
        item.intersectionRect -  描述根和目標(biāo)元素的相交區(qū)域
        item.isIntersecting - true(相交開始)谋作,false(相交結(jié)束)
        item.rootBounds - 描述根元素
        item.target - 目標(biāo)元素
        item.time - 時(shí)間原點(diǎn)(網(wǎng)頁(yè)在窗口加載完成時(shí)的時(shí)間點(diǎn))到交叉被觸發(fā)的時(shí)間的時(shí)間戳
      */
      if (item.isIntersecting) {
        loadImage(item.target);
        observer.unobserve(item.target);
      }
    });
  });

  imagesToLoad.forEach(function(image) {
    intersectionObserver.observe(image);
  });
  
})();

3. 網(wǎng)站最佳實(shí)踐

? 使用 hexo-abbrlink 插件生成文章鏈接

hexo 框架生成的博客地址默認(rèn)是 :year/:month/:day/:title 這種格式的,也就是 /年/月/日/標(biāo)題乎芳。當(dāng)博客標(biāo)題為中文時(shí)遵蚜,生成的url鏈接中也出現(xiàn)中文,中文路徑對(duì)于搜索引擎優(yōu)化不友好奈惑。復(fù)制后的鏈接會(huì)被編碼吭净,非常不利于閱讀,也不簡(jiǎn)潔肴甸。

使用 hexo-abbrlink 可以解決這個(gè)問題寂殉,安裝插件:npm install hexo-abbrlink --save,在 _config.yml 中添加配置:

permalink: :year/:month/:day/:abbrlink.html/
permalink_defaults:
abbrlink:
  alg: crc32  # 算法:crc16(default) and crc32
  rep: hex    # 進(jìn)制:dec(default) and hex

之后生成的博客文章就會(huì)變成這種:https://post.zz173.com/posts/8ddf18fb.html/原在,即使更新了文章標(biāo)題友扰,文章的鏈接也不會(huì)改變。

? 使用 hexo-filter-nofollow 規(guī)避安全風(fēng)險(xiǎn)

hexo-filter-nofollow 插件會(huì)為所有 <a> 鏈接添加屬性 rel="noopener external nofollow noreferrer"庶柿。

網(wǎng)站內(nèi)部有大量的外鏈會(huì)影響網(wǎng)站的權(quán)重,不利于SEO村怪。

  • nofollow:是 Google、Yahoo 和微軟公司前幾年一起提出的一個(gè)屬性澳泵,鏈接加上這個(gè)屬性后就不會(huì)被計(jì)算權(quán)值实愚。nofollow 告訴爬蟲無(wú)需追蹤目標(biāo)頁(yè),為了對(duì)抗 blogspam(博客垃圾留言信息)兔辅,Google推薦使用nofollow,告訴搜索引擎爬蟲無(wú)需抓取目標(biāo)頁(yè)击喂,同時(shí)告訴搜索引擎無(wú)需將的當(dāng)前頁(yè)的Pagerank傳遞到目標(biāo)頁(yè)维苔。但是如果你是通過 sitemap 直接提交該頁(yè)面,爬蟲還是會(huì)爬取懂昂,這里的nofollow只是當(dāng)前頁(yè)對(duì)目標(biāo)頁(yè)的一種態(tài)度介时,并不代表其他頁(yè)對(duì)目標(biāo)頁(yè)的態(tài)度。
  • noreferrernoopener:當(dāng) <a> 標(biāo)簽使用 target="_blank" 屬性鏈接到另一個(gè)頁(yè)面時(shí)凌彬,新頁(yè)面將與您的頁(yè)面在同一個(gè)進(jìn)程上運(yùn)行沸柔。如果新頁(yè)面正在執(zhí)行開銷極大的 JavaScript,舊頁(yè)面性能可能會(huì)受影響铲敛。并且新頁(yè)面可以通過 window.opener 拿到舊頁(yè)面窗口對(duì)象執(zhí)行任意操作褐澎,具有極大的安全隱患。使用 noopener (兼容屬性 noreferrer) 之后伐蒋,新打開的頁(yè)面就不能拿到舊頁(yè)面窗口對(duì)象了工三。
  • external:告訴搜素引擎迁酸,這是非本站的鏈接,這個(gè)作用相當(dāng)于 target=“_blank”俭正,減少外部鏈接的 SEO 權(quán)重影響奸鬓。

? 使用 hexo-deployer-git 和 github workflow 進(jìn)行自動(dòng)化部署

靜態(tài)資源打包生成完成后,我需要將其提交到對(duì)應(yīng)的 github pagesgitee pages 倉(cāng)庫(kù)中掸读,當(dāng)需要部署多個(gè)倉(cāng)庫(kù)時(shí)串远,手動(dòng)操作效率非常低。因此這里采用 hexo-deployer-git 插件進(jìn)行自動(dòng)化部署儿惫,可以向下面一樣聲明需要部署的倉(cāng)庫(kù)信息抑淫,如果有多個(gè)倉(cāng)庫(kù)直接聲明多個(gè) deploy 字段即可:

# Deployment
deploy:
  type: git
  repository: https://github.com/nojsja/blogs
  branch: master
  ignore_hidden:
    public: false
  message: update

值得說明的是,非付費(fèi)用戶 gitee pages 倉(cāng)庫(kù)不支持提交后自動(dòng)部署姥闪。因此我采用的方案是只聲明一個(gè) deploy 部署倉(cāng)庫(kù)指向 github pages 倉(cāng)庫(kù)始苇,然后再通過 github 倉(cāng)庫(kù)自帶的 github workflow 服務(wù)配合 gitee-pages-action 這個(gè)腳本實(shí)現(xiàn)的 gitee 自動(dòng)部署。它的原理就是使用 github 自動(dòng)化工作流將 github 倉(cāng)庫(kù)的代碼同步到 gitee 倉(cāng)庫(kù)中去筐喳,然后通過讀取我們配置的 gitee 賬戶信息自動(dòng)執(zhí)行登錄 gitee 賬戶并調(diào)用網(wǎng)頁(yè)的手動(dòng)部署接口催式,實(shí)現(xiàn)整個(gè)自動(dòng)化部署流程。

github-workflow

4. 網(wǎng)站SEO優(yōu)化

? 使用 hexo-generator-sitemap 插件自動(dòng)生成網(wǎng)站地圖

站點(diǎn)地圖是什么:

  • 站點(diǎn)地圖描述了一個(gè)網(wǎng)站的結(jié)構(gòu)避归。它可以是一個(gè)任意形式的文檔荣月,用作網(wǎng)頁(yè)設(shè)計(jì)的設(shè)計(jì)工具,也可以是列出網(wǎng)站中所有頁(yè)面的一個(gè)網(wǎng)頁(yè)梳毙,通常采用分級(jí)形式哺窄。這有助于訪問者以及搜索引擎的機(jī)器人找到網(wǎng)站中的頁(yè)面。
  • 一些開發(fā)者認(rèn)為網(wǎng)站索引是組織網(wǎng)頁(yè)的一種更合適的方式账锹,但是網(wǎng)站索引通常是A-Z索引萌业,只提供訪問特定內(nèi)容的入口,而一個(gè)網(wǎng)站地圖為整個(gè)站點(diǎn)提供了一般的自頂向下的視圖奸柬。
  • 網(wǎng)站地圖讓所有頁(yè)面可被找到來(lái)增強(qiáng)搜索引擎優(yōu)化的效果生年。

安裝:

$: npm install hexo-generator-sitemap --save

配置文件_config.yml中添加相關(guān)字段:

# sitemap
sitemap:
  path: sitemap.xml
# page url
url: https://nojsja.github.io/blogs 

之后運(yùn)行 hexo generate 之后就可以自動(dòng)生成網(wǎng)站地圖 sitemap.xml 了,接下來(lái)需要到 Google Search Console 記錄自己的站點(diǎn)并提交相應(yīng)的站點(diǎn)文件廓奕。

? 向 Google Search Console 提交個(gè)人網(wǎng)站地圖

  • 登錄 Google Search Console
  • 添加自己的網(wǎng)站信息


    Search Console
  • 可以通過幾種方法驗(yàn)證所有權(quán)


    Search Console
  • 提交插件生成的 sitemap.xml
    sitemap.xml

Google Search Console 還能看到自己網(wǎng)站的點(diǎn)擊情況抱婉、關(guān)鍵詞索引情況等,非常方便桌粉。

參考


  1. Lighthouse與Google的移動(dòng)端最佳實(shí)踐
  2. Google web.dev

結(jié)語(yǔ)


學(xué)習(xí)前端性能優(yōu)化的方方面面蒸绩,一方面是對(duì)我們核心基礎(chǔ)知識(shí)的考察,另一方面也能為我們遇到的一些實(shí)際問題提供處理思路铃肯,是每個(gè)前端人進(jìn)階的的必經(jīng)之路患亿。

以上就是本篇文章的所有內(nèi)容,后續(xù)有需要還會(huì)繼續(xù)更新…

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缘薛,一起剝皮案震驚了整個(gè)濱河市窍育,隨后出現(xiàn)的幾起案子卡睦,更是在濱河造成了極大的恐慌,老刑警劉巖漱抓,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件表锻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡乞娄,警方通過查閱死者的電腦和手機(jī)瞬逊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)仪或,“玉大人确镊,你說我怎么就攤上這事》渡荆” “怎么了蕾域?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)到旦。 經(jīng)常有香客問我旨巷,道長(zhǎng),這世上最難降的妖魔是什么添忘? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任采呐,我火速辦了婚禮,結(jié)果婚禮上搁骑,老公的妹妹穿的比我還像新娘斧吐。我一直安慰自己,他們只是感情好仲器,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布煤率。 她就那樣靜靜地躺著,像睡著了一般娄周。 火紅的嫁衣襯著肌膚如雪涕侈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天煤辨,我揣著相機(jī)與錄音,去河邊找鬼木张。 笑死众辨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舷礼。 我是一名探鬼主播鹃彻,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼妻献!你這毒婦竟也來(lái)了蛛株?” 一聲冷哼從身側(cè)響起团赁,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谨履,沒想到半個(gè)月后欢摄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笋粟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年怀挠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片害捕。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绿淋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尝盼,到底是詐尸還是另有隱情吞滞,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布盾沫,位于F島的核電站裁赠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疮跑。R本人自食惡果不足惜组贺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祖娘。 院中可真熱鬧失尖,春花似錦、人聲如沸渐苏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琼富。三九已至仪吧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鞠眉,已是汗流浹背薯鼠。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留械蹋,地道東北人出皇。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哗戈,于是被迫代替她去往敵國(guó)和親郊艘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容