京東首頁前端技術剖析與對比

發表於2015-09-11

逛京東的時候開著 chrome 控制檯,無意間看到了下面這串似曾相識的程式碼,

再看了下 localstorage,

看到這些內容,其實京東首頁的前端架構雛形就出來了。

JD 使用 seajs 作為模組載入器,使用 jd-jquery 為基本庫,看到它的 jq 版本是 1.6.4,

對比了下”正版” jquery-1.6.4 的原始碼,很顯然,JD 使用的是自己造的輪子,這說明京東的前端生態應該是十分完善的,有輪子就會有很多元件、外掛,這對一個公司批量造網頁很有裨益。

電商的主頁都是以呈現為主,展示各個橫向市場和縱向市場的入口,也可能會有一些個性化(所謂個性化,就是給不同的使用者推薦不同的內容)的推薦。它不像交易、詳情等頁面,頁面邏輯後端重而前端輕,後端需要做各種內容推薦、資料校驗、型別判斷等工作,而前端更多的是將後端的資訊有序地呈現出來。

首頁則不同,首頁承載了大量的二級頁面入口和一些頻道的推薦內容,而這些資料更多的是由運營去維護,可以認為資料是死的,就算存在一些個性化的資料,也會使用 jsonp 的形式去載入,前端需要快速高效地處理這些資料,可見前端任務相當艱鉅。加上作為一個網站的門面,它的安全穩定性也是極為重要的。

如果沒有猜錯,京東後端也有一箇中間層,中間層負責組裝資料,以模組為單位,根據前端的請求響應對應模組的內容,而資料是在另外一個運營平臺上維護,運營填好的資料會即時的推送到 CDN 或者應用,中間層拼合資料。看不到的東西就不猜測了,我們來看看京東首頁的整體結構。

如果希望一個網站跑起來飛快,你覺得怎麼做最靠譜?

我們都玩過微博,都上過手機淘寶,進入這些 app 應用,會發現很多頁面幾乎看不到載入的痕跡,因為他們是本地應用,很多圖片、指令碼、樣式都已經打包在本地了,所以載入起來速度是很快的。如果希望一個網頁也能飛奔起來,同樣的道理,讓請求的個數少一點,讓請求的內容少一點。還有一個至關重要的,讓那些次要的內容慢一點載入(我們稱之為懶載入)。

京東在按需載入和資料快取上的工作做的十分到位。

每個具有 lazyload 非同步標識的模組,都包含兩個屬性,一個是渲染該模組需要的內容(資料+JS),一個是這個內容過期的時間,只要內容不變就不會過期,所以這裡使用的是檔案 hash 來標註。

把需要請求的路徑寫在 dom 上,使用者滾動時,一旦該模組進入了視窗,則請求 dom 上對應的  data-path  地址,拿到渲染這個模組所需要的指令碼和資料,不過這中間還有一層本地快取 localstorage,如果在本地快取中匹配到了對應的 hash string 內容,則直接渲染,否則請求到資料之後更新本地快取。dom 上的  data-time  會在頁面載入時候,後端計算檔案 hash,hash 不變則輸出內容也不變。

這裡其實存在兩個請求,一個請求是載入資料和指令碼,而這裡的內容是:

為啥不在返回的內容中直接把指令碼也輸出出來?為了讓資料充分快取下了不少功夫。資料的變化頻率比較高,如果資料和初始化指令碼包裝在一起,雖然節約了一個請求,但一旦資料變化,整個指令碼都得重新載入,而將資料和指令碼分離,指令碼可以長期快取在本地,單獨請求資料,這個量會小很多。直接改變上面的 version 版本號便可以讓瀏覽器重新請求最新指令碼。

從上面可以看出,任何一個模組的改動,在前端只會引起一個較小的載入變化,加上 http 的快取策略,伺服器的壓力也是很小的。

比較常見的工程結構,如下:

所有 mods 中的  tpl 檔案通過一些標籤,引入到  src/index.tpl  中,需要同步渲染的模組資訊直接引入,而非同步渲染的模組內容,比如  moduleA/index.tpl ,其內容就十分簡單:

只引入一個模組鉤子(hook),然後按需載入/懶載入這個模組鉤子內容。相比 JD 採用的也是類似的模型。

看看上面列出的目錄結構,一般情況下,為了減少網頁的請求數,我們會把所有 mods 和 wedgets 中的 js 和 css 分別打包成一個檔案,然後前端 combo 請求,提前載入但是懶執行,這是 CMD 的思維方式。而京東使用了更懶的方式:懶載入並且懶執行。

這種方式帶來的好處就是,單個模組的更改,前端只更新一小部分快取;而提前載入所有模組的方式,任何一個模組有改動,整體都得重新下載。指令碼懶載入的缺點是,需要發起請求,如果需要載入多個模組,則需要發起多個指令碼請求,可以看到,快速拖動 JD 首頁,模組的載入速度不容樂觀。當然,指令碼是可以被瀏覽器快取的,這個問題也就是首次訪問或者清空了快取才會出現。

對請求控制如此嚴格,怎麼就沒考慮下優化原始碼當中的兩大段 css 和 js 程式碼呢?是不是也可以把 css 和 js 放到 localstorage 中,減少請求數。

原始碼中通過函式去載入資源:

如果 localstorage 中不存在,也不需要重新發請求,後端指令碼通過 cookie 判斷是否需要同步輸出程式碼:

如果發現 cookie 中的版本號與設定的版本號不一樣,或者沒有 cssV cookie,則同步內聯輸出 css 和 js。

本文只是對京東首頁用到的部分技術做一個簡要的分析,頁面載入速度確實十分可觀,贊!

隨著需求的多元化和終端裝置的多元化,前端技術在 web 舞臺上一直展現著優美的身姿,她在進化、在演變,幾乎每隔一兩個月就能聽到新的前端技術出來,所以學是學不過來的,前端的學習就兩個字:”理解為什麼”。

相關文章