turbolinks踩坑(轉(zhuǎn))

由于公司內(nèi)部OA 項目將turbolinks升級到了5,但是有許多的js還是采用著之前的寫法肢扯,結(jié)果一大堆的莫名其妙的頁面渲染問題暴露出來妒茬,總結(jié)一下遇到的坑担锤,以及整理網(wǎng)上各路大神給出的解決方案,備忘蔚晨。

要不要用 Turbolinks5

必須用起來, 既然是 Rails5應(yīng)用, 強烈推薦使用 Turbolinks5來構(gòu)建你的應(yīng)用. 在用好它的前提下, 它的用戶體驗速度趕超用 AngularJS, ReactJS, VueJS構(gòu)建的單頁應(yīng)用( 在詳細頁里面有解釋 ). 而學(xué)習(xí)成本實際上很低, 一個團隊只要一個人掌握即可. 而且我希望通過我的文章你就能完全掌握好它.

實際上, Turbolinks 用的好, 能夠讓 Rails 應(yīng)用比 AngularJS 或 ReactJS 構(gòu)建的單頁應(yīng)用還要快. 而學(xué)習(xí)成本又極低. 說不定你看完此文就明白了. 本文嘗試解答以下問題:

  • Turbolinks5 的出現(xiàn)背景
  • Turbolinks5 是否應(yīng)該使用, 在什么樣的情況下使用?
  • Turbolinks5 與 Turbolinks-classic 有哪些區(qū)別?
  • Turbolinks5 典型問題與解決方法
  • Turbolinks5 技術(shù)內(nèi)冪

Turbolinks 背景

首先, 應(yīng)該先解讀一下 Turbolinks5 出現(xiàn)的背景與意義, 以此可以基本推出它解決問題的思路與采用的技術(shù).

Turbolinks5 是一個 "簡單" 的單頁應(yīng)用 JS 客戶端框架, 能夠讓你輕易地實現(xiàn) "單頁面" 應(yīng)用, 換言之, 它某種程度與 AngularJS, ReactJS 一樣, 都是為了提升客戶端體驗更快. 不同的地方是, 它是足夠 "簡單" 的方案, 幾乎只需要提供一行代碼即可:

//=require 'turbolinks'

它解決問題的思路與 Rails的理念是一致的:

  • 通過簡單直白的思路解決 80% 的問題, 剩下的交給你. 我們來看看它如何解決問題.

一個直白的公式: 網(wǎng)頁加載速度 = 下載資源速度 + 解析資源速度

"單頁面應(yīng)用" 快的秘訣就在于它同時減少了下載資源的大小( 除卻第一次加載模板后, 后續(xù)全部使用 JSON API), 以及極大提高了解析資源的速度( 通過 JSON 數(shù)據(jù)就能更新頁面).

Turbolinks 無法解決下載資源的大小的問題, 卻可以通過幾乎不影響原有網(wǎng)頁架構(gòu)的情況下極大提高解析資源的速度.

為了理解 Turbolinks 的工作原理, 我們先來看一下在 chrome 瀏覽器中, 網(wǎng)頁是如何被加載的.

  1. 下載 index.html
  2. 解析 head 標簽中的 link 與 script 標簽, 如果是帶有 src屬性, 阻塞其他邏輯執(zhí)行, 繼續(xù)去下載對應(yīng)的資源并執(zhí)行. 如果沒帶, 則直接執(zhí)行其中的代碼邏輯.
  3. 渲染 body 標簽的內(nèi)容, 并解析執(zhí)行 body 中的 script 標簽.
  4. 全部執(zhí)行完畢, 執(zhí)行 DOMContentLoaded 事件綁定的邏輯.

第一次加載時網(wǎng)頁執(zhí)行跟上述是一致, 之后 Turbolinks 會綁定 Body 下所有的 a 元素的 click事件, 切換頁面時, Turbolinks 將會接管瀏覽器的頁面加載過程, 采用以下方式:

  1. 異步加載新頁面的 index.html
  2. 解析 head 標簽中的 link 與 script 標簽, 識別其中帶有 data-turbolinks-track
    的屬性, 如果 src有變化( 可能性很小 ), 則重載所有頁面. 如果沒有變化, 則不進行任何操作.
  3. 解析 head 標簽中新的 link 與 script 標簽, 加載并執(zhí)行.
  4. 用新頁面的 body 替換老的 body 中的內(nèi)容, 并執(zhí)行其中的 script 腳本.

這樣一來, Turbolinks 能夠絕大時間里避免每次重復(fù)的 head 其中的 css 與 javascript 標簽的解析與加載時間( 這個時間往往很耗時 ).

Turblinks5 與 Turbolinks-classic 的異同

Turblinks5 與 Turbolinks-classic 核心理念沒有變化, 最大的區(qū)別在于更清晰明確了流程, 以及更清晰的事件觸發(fā), 以及重構(gòu)了代碼.

對使用者影響最大的可能就是事件了.

  1. page:change -> turbolinks:load
  2. 不需要繼續(xù)綁定 ready事件了
  3. script 的追蹤標簽 由 data-turbolinks-track -> data-turbolinks-track="reload"

現(xiàn)在請直接使用 Turbolinks5, 更簡單明了. 以下均在 Turbolinks5 的基礎(chǔ)上進行講解與分析.

Turbolinks5 不是免費的

Turbolinks5 能夠讓你在極小的改動下提升網(wǎng)頁的加載速度, 但也帶來了新的問題: 頁面執(zhí)行邏輯的變化要求你的 javascript 邏輯盡可能的冪等( 即重復(fù)執(zhí)行也不影響最終的結(jié)果 ). 否則的話, 需要你理解 Turbolinks5 的工作原理并針對進行調(diào)整. 這便是本文最大的價值所在.

最佳實踐:

將所有 javascript 腳本打包為一個 application.js, 放在 head 中, 并用 'data-turbolinks-track="reload"' 追蹤.

但有的時候, 我們必須打破這個最佳, 比如第三方組件, 這時候我們該如何處理?

典型問題一: 為什么刷新就好了, 從其他頁面點擊過來就有問題?

注意到啟用了 Turbolinks5 的頁面, 瀏覽器的加載流程與 Turbolinks5 去加載的流程有大的差異. 如果沒有注意到一些冪等或依賴的 JS 問題, 就會出現(xiàn)這種問題.

遇到這種情況, 要具體問題具體分析.

典型問題二: head 中添加了新的 script, body 中有 script 對其有依賴

在典型的第三方插件中, 會要求你在 head 中添加它們的源, 在 body 中添加插件的初始化操作. 注意, 這種情況在 Turblinks5 中會有依賴加載的問題.

例:

// page1
<head>
  <script src='application.js' data-turbolinks-track='reload'></script>
  <script src='plugin.js'></script>
</head>
<body>
  <script> window.plugin.init(); </script>
</body>
// plugin.js
window.plugin = { init: function(){ // init code here }}

這種寫法在標準的瀏覽器加載時是非常正常的, 但是在 Turbolinks5 流程里就有問題了:

plugin.js在 Turbolinks5 中執(zhí)行是用的類似于

script = document.createElement('script')script.src = 'plugin.js'

這個加載過程是異步的, 所以往往在 body 中的 script 標簽執(zhí)行時, plugin.js還沒有下載完, 所以執(zhí)行就會出現(xiàn)變量未定義錯誤.在這種情況下, 建議將其改為

// page1
<head> 
  <script src='application.js' data-turbolinks-track='reload'></script>
</head>
<body> 
  <script> 
    $.getScript('plugin.js', function(){ 
      window.plugin.init(); 
    }); 
  </script>
</body>

典型問題三: body 中更新某個 DOM 節(jié)點會導(dǎo)致頁面緩存重復(fù)的問題

這也是一個非常容易犯的錯誤, 舉個例子

// page1
<head>
  <script src='application.js' data-turbolinks-track='reload'></script>
</head>
<body> 
  <p id='el'>hello world</p> 
  <a href='page2'>page2</a> 
  <script>
    $('#el').append('+123'); 
  </script>
</body>

這個問題非常難于發(fā)現(xiàn), 但很容易犯錯. 采用這個頁面的寫法之后, 就能觸發(fā)一個 bug:

  1. 打開 page1
  2. 點擊進入 page2
  3. 點擊瀏覽器的 "后退" 按鈕
  4. 點擊瀏覽器的 "前進" 按鈕
  5. 再次 "后退" 按鈕

bug 出現(xiàn), 頁面上 #el元素變?yōu)?'hello world+123+123' 了. 如果你繼續(xù) "后退", "前進" 將出現(xiàn)重復(fù)的 +123 這樣的錯誤.想像一下, 如果將上述簡單代碼換成一個圖表插件, 插件將會導(dǎo)致重復(fù)的圖表渲染. 這個 bug 還是非常嚴重的.

如何修復(fù)?

有兩種方法:

  1. 關(guān)閉緩存, 在這個特定的頁面的 head 放置 <meta name="turbolinks-cache-control" content="no-cache">的 meta 標記以關(guān)閉當前頁面的緩存, 這樣將強制每一次后退必須從服務(wù)端拉取最新的頁面.
  2. 利用 turbolinks:before-cache事件, 在緩存前重置這個元素, 例如這里可以用
$(document).on('turbolinks:before-cache', function(){
     $('#el').text('hello world');
});

來解決這個問題.

這種情況與我們接下來要講的緩存機制有關(guān). 接下來我們要深入理解 Turbolinks5 的機制, 達到掌控它的能力.

Turbolinks5 的緩存機制

Turbolinks5 在某種程度上比使用 "AngularJS", "VueJS", "ReactJS" 構(gòu)建的單頁應(yīng)用更快. 這得益于它的緩存機制設(shè)計.

Turbolinks5 在每一次訪問頁面后, 都會緩存當前頁面, 默認最多緩存 20 個. 緩存頁面有兩個用途:

  1. 使用瀏覽器后退, 前進時, 直接從緩存中取出對應(yīng)的頁面并渲染.
  2. 通過 a 元素點擊時, Turbolinks5 會率先從緩存中取出頁面, 渲染出來, 然后再通過 XMLHttpRequest 取得服務(wù)器最新的頁面, 再替換掉緩存頁, 并渲染最新的頁面.

這個時候請注意第一種情況, Turbolinks5 使用的是 cloneNode(true)
來緩存頁面, 這樣將導(dǎo)致它替換頁面時丟失掉所有的事件綁定, 它必須重新解析執(zhí)行其中的 script
腳本才能讓緩存頁面正常工作. 這時候如果處理不當就會出現(xiàn)上一段落第 3 個典型問題.

充分利用 Turbolinks5 的緩存機制, 能夠讓我們的頁面訪問速度超出 "單頁應(yīng)用", 所以, 我不建議你輕易的關(guān)閉它.

深入 Turbolinks5 的處理流程

我們再來深入的分析 Turbolinks5 在不同的情況的加載過程.

瀏覽器第一次加載, 或點擊刷新: 這種情況保持與瀏覽器的加載順序一致.

點擊瀏覽器后退或前進: 直接調(diào)取緩存頁面并顯示, 不再拉取服務(wù)端數(shù)據(jù).

點擊頁面的 a 元素: 先嘗試拉取緩存, 如果有, 渲染緩存頁面, 然后同時拉取服務(wù)端新頁面并替換緩存; 如果沒有, 則異步拉取服務(wù)端新頁面, 緩存之并渲染新頁面.

為了更清晰展示這個流程, 我花了幾個小時整理了一個流程圖:


還要詳細說明一下如何渲染頁面( 無論是緩存頁面還是新頁面 ):

  1. 檢查 head 中是否有標記過 reload 的 script 的 src 有變化, 如果有, 則完全刷新頁面了.
  2. head 中如果有新的 script 標簽, 就異步加載它( 通過 Element.src = ''的方式 )
  3. 執(zhí)行 body 中的 script 標簽

通過這個分析, 我們可以推斷出, 如果同時有緩存頁和新頁面, 其中的 script 標簽就可能被執(zhí)行兩次.

除了以上描述的標準流程外, Turbolinks5 還有幾個選項可以定制它的流程:

  • 定義如何臨時關(guān)閉緩存
  • 定義如何緩存頁面
  • 定義如何管理歷史記錄
  • 定義如何臨時關(guān)閉 Turbolinks5

關(guān)于這些選項, 可以閱讀它的源碼和官方主頁了解更多: https://github.com/turbolinks/turbolinks
如果想了解源代碼:http://www.reibang.com/p/622571b33eca

原文資料鏈接 Turbolinks5 概述及實現(xiàn)原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肛循,隨后出現(xiàn)的幾起案子铭腕,更是在濱河造成了極大的恐慌,老刑警劉巖多糠,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累舷,死亡現(xiàn)場離奇詭異,居然都是意外死亡夹孔,警方通過查閱死者的電腦和手機被盈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門析孽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人只怎,你說我怎么就攤上這事袜瞬。” “怎么了身堡?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵邓尤,是天一觀的道長。 經(jīng)常有香客問我贴谎,道長汞扎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任擅这,我火速辦了婚禮澈魄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蕾哟。我一直安慰自己一忱,他們只是感情好,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布谭确。 她就那樣靜靜地躺著帘营,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逐哈。 梳的紋絲不亂的頭發(fā)上芬迄,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機與錄音昂秃,去河邊找鬼禀梳。 笑死,一個胖子當著我的面吹牛肠骆,可吹牛的內(nèi)容都是我干的算途。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚀腿,長吁一口氣:“原來是場噩夢啊……” “哼嘴瓤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起莉钙,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤廓脆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后磁玉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體停忿,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年蚊伞,在試婚紗的時候發(fā)現(xiàn)自己被綠了席赂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吮铭。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颅停,靈堂內(nèi)的尸體忽然破棺而出沐兵,到底是詐尸還是另有隱情,我是刑警寧澤便监,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布扎谎,位于F島的核電站,受9級特大地震影響烧董,放射性物質(zhì)發(fā)生泄漏毁靶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一逊移、第九天 我趴在偏房一處隱蔽的房頂上張望预吆。 院中可真熱鬧,春花似錦胳泉、人聲如沸拐叉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凤瘦。三九已至,卻和暖如春案铺,著一層夾襖步出監(jiān)牢的瞬間蔬芥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工控汉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笔诵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓姑子,卻偏偏與公主長得像乎婿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子街佑,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

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

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案谢翎? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽?zāi)J的外補...
    _Yfling閱讀 13,754評論 1 92
  • 1. 介紹 瀏覽器可能是最廣泛使用的軟件。本書將介紹瀏覽器的工作原理舆乔。我們將看到岳服,當你在地址欄中輸入google....
    康斌閱讀 2,021評論 7 18
  • 簡介瀏覽器可以被認為是使用最廣泛的軟件,本文將介紹瀏覽器的工 作原理纲辽,我們將看到颜武,從你在地址欄輸入google.c...
    聽風(fēng)閣閱讀 3,291評論 0 7
  • 一:在制作一個Web應(yīng)用或Web站點的過程中璃搜,你是如何考慮他的UI、安全性鳞上、高性能这吻、SEO、可維護性以及技術(shù)因素的...
    Arno_z閱讀 1,160評論 0 1
  • 轉(zhuǎn)載說明 一篙议、介紹 瀏覽器可以被認為是使用最廣泛的軟件唾糯,本文將介紹瀏覽器的工作原理,我們將看到鬼贱,從你在地址欄輸入g...
    17碎那年閱讀 2,450評論 0 22