1. 程式人生 > >移動端H5活動頁優化方案

移動端H5活動頁優化方案

nginx服務 || 做的 教程 網上 存在 方法 tee cat

背景

項目:移動端H5電商項目
痛點:慢!!!
初始方案:最基本的圖片懶加載,靜態資源放到cdn,predns等等已經都做了。但是還是慢,慢在哪?
顯而易見的原因:由於前後端分離,所有的數據都由接口下發,之後根據模板渲染頁面。也就是說,我們需要先加載js,等到js加載完畢之後,請求接口,接口返回數據之後,渲染頁面,加載圖片等等。盡管使用了模塊化的加載方式,但是對於要求高的首頁和活動頁,給用戶的感知也不是很好。

初版解決方案

最初,由於時間緊迫,基本上都是從客戶端作優化處理,基本上可以總結為以下幾個方面。

一、本地緩存

我們做了本地緩存優化的策略,第一次請求之後就把接口數據緩存到localStorage裏面,並且存儲當時的時間,設定過期時間,一般設置為5分鐘,用戶在5分鐘內重復打開頁面,不會再次請求接口,從localstorage中拿取數據,直接渲染頁面。
後續幹脆把模板渲染好的html片段存儲了起來,直接拼接,省去了模板計算的時間。
基本實現方案如下:

var 
    cache = localStorage.getItem(‘cache‘)
    , expires = 5 * 60 * 1000
;

// 判斷是否過期
function isOverdue(pastTime, expires) {
    return Date.now() - pasttime >= expires;
}

if (cache && !isOverdue(cache.time, expires)) {
    // 說明緩存存在,並且沒有過期
    // 就正常取cache.data做相應的渲染
} else {
    // 說明緩存不存在或者已經過期了
// 重新請求接口 $.get(‘a.cn‘, funciton (res) { // do something // 把對應的渲染操作處理完成之後,將數據緩存,並記錄當前的時間 localStorage.setItem(‘cache‘, { data: res, time: Date.now() }) }) }

然而還是不夠,新用戶在首次打開時,還是不能秒開頁面,並且用戶在5分鐘之後重新加載之時,仍然會有一定的延遲(由於瀏覽器會緩存一部分靜態資源,此時再打開並不會像用戶初次打開一樣那麽慢)。

二、進一步緩存靜態資源

在日常開發中,有很多依賴庫,常用的fastclick,swipe等等,這些庫,沒有必要每次都去加載,雖然瀏覽器會對一些靜態資源做緩存,但是卻不能完全被我們控制,所以,可以將這些不常發生變化的靜態資源緩存起來,同樣的,存到localStorage裏面。

需要註意

這個方案有一個問題,如果是直接加載的script標簽,是無法直接拿到它的腳本內容的。

<script id="script1" src="js/jquery.js"></script>
console.log(document.querySelector(‘#script1‘).innerHTML);
// 此時輸出的是undefined,因為innerHTML是獲取標簽內容,此時script標簽裏並沒有內容。
<script id="script2">
console.log(‘2‘);
</script>
console.log(document.querySelector(‘#script2‘).innerHTML);
// 此時輸出的是console.log(‘2‘);
// 因為innerHTML是獲取標簽內容,此時script標簽裏並沒有內容。

這當然不是我們想要的,我們需要的是外鏈js的可執行代碼。

動態添加js的兩種方案

我在前一篇高性能JavaScript讀書筆記中提到了兩種方案。

1. 動態腳本元素

var script = document.createElement(‘script‘);
script.type = ‘text/javascript‘;
script.onload = function () {
    // do something
}
script.src = ‘jquery.js‘;
document.getElementByTagName(‘heda‘)[0].appendChild(script);

這種方法可以監控到腳本的完成事件,但是由於也是通過添加一個script標簽,並不能拿到我們想要的js代碼。

2. 通過XMLHttPRequest腳本註入

var xhr = new XMLHttpRequest();
xhr.open("get", "file1.js", true);
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
            localStorage.setItem(‘file1‘, xhr.responseText);
        }
    }
}

需要特別註意的是,這個方法有跨域的風險,所以,我們需要靜態資源服務器的allow-origin設置為*。或者直接加載本域名下的js。

有同學要問了,既然localStorage這麽強大,為什麽不把所有的東西都緩存起來呢?
當然是因為它的大小有限制,在FireFox和chrome中,一般來說,sessionStorage和localStorage大小為10MB,而safari只有5MB。詳見這篇文章。
這就限制了我們什麽都存的想法。如果圖片較小,可以緩存起來。

第二版解決方案

在前一版方案裏,我們解決了後續加載的速度緩慢問題,在後續的頁面打開速度上,基本上可以做到秒開。
但是這個方案還是有不足,對於新用戶的體驗不是很好,如果用戶在5min的這個時間點上打開,速度還是會有所下降。
最後還是只能做SSR。
知乎上有一個問題,為什麽現在又流行服務端渲染html?。
服務端渲染有很多好處,對我們此時而言,最大的好處就是頁面直出。省去了請求接口這一步操作。並且對於提高用戶體驗上來說,很有好處。
如果時間充裕,可以使用node做服務端,但是由於歷史原因,我們這個項目遷移起來也比較費時間,所以最後決定使用openresty來做。
openResty的教程網上很多了,我也不多說,除了官方git,推薦開濤博客學習。

不論我們是在客戶端取接口數據,還是服務端,活動頁的數據一般來說都有一定持續時長,也就是說,我們也可以在我們的nginx服務器上做一個緩存,假設有一個用戶訪問了一個活動,那麽在接下來的五分鐘之內,其他任何用戶(相同權限下)訪問到的就是第一個用戶訪問時緩存好的頁面。

local args = ngx.req.get_uri_args()
local acId = args[‘acId‘]
local key = ‘ac‘ .. acId
local expire = 5 * 60 --緩存時間5分鐘

local value = dict.getData(key, expire)

if not value then
    local res = ngx.location.capture(‘/a.json‘)

    if res.status == 200 then
        -- dict是一個用來處理存儲和讀取邏輯的腳本
        dict.setData(key, res.body)
    else
        ngx.say(dict.getData(key))
    end
else
    --  ngx.log(4, type(value))
    ngx.say(value)
end

在dict中,我們可以把接口數據轉換成通過模板轉換為html片段存儲起來。這樣,用戶在第一次進入我們的頁面以後,也不會感覺慢了。

what‘s more

openResty能做的事不僅僅是這些,一些網關上權限的控制等等都可以用它來實現。
上面兩個方案還有一些不足之處,比如首屏可能內容比較多,一次都加載過來,不一定會快。直觀的首屏的由服務端渲染,對於不重要的內容,可以通過AJAX來異步加載。
如何計算活動頁這樣的頁面中,首屏有多大,如何組織代碼,哪些部分采用服務端渲染,哪些采用AJAX。這些問題在實際開發中也需要考慮。
限於時間,此次沒有能比較完善的解決這個問題,在日後開發中,還會繼續完善活動頁優化方案。

移動端H5活動頁優化方案