摘要:關(guān)于動(dòng)靜分離的描述,這里推薦一篇不錯(cuò)的博文網(wǎng)站靜態(tài)化處理動(dòng)靜分離策略。這里的解決辦法則是采用的屬性,將其應(yīng)用于數(shù)據(jù)請(qǐng)求相關(guān)的上,就可以達(dá)到腳本與數(shù)據(jù)并發(fā)加載的效果。
作者:莫冠釗
轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接和作者信息
前言當(dāng)今許多大型網(wǎng)頁(yè)應(yīng)用尤其是SPA均采用了動(dòng)靜分離的策略。關(guān)于動(dòng)靜分離的描述,這里推薦一篇不錯(cuò)的博文 網(wǎng)站靜態(tài)化處理—?jiǎng)屿o分離策略。
本人是做前端的,之前有幸與一位對(duì)性能追求極致的后端同學(xué)一起開(kāi)發(fā)這種動(dòng)靜分離的web項(xiàng)目,以下將從傳統(tǒng)順序模式、單路數(shù)據(jù)并發(fā)模式(以下簡(jiǎn)稱單并發(fā)模式)、多路數(shù)據(jù)并發(fā)模式(以下簡(jiǎn)稱多路并發(fā)模式)來(lái)談?wù)勛约簩?duì)這類應(yīng)用關(guān)于前端加載方面的心得。本文中的例子均來(lái)自該項(xiàng)目中。
1. 傳統(tǒng)順序模式一般情況下,瀏覽器首先會(huì)接收到一張靜態(tài)的頁(yè)面,這張頁(yè)面會(huì)包含樣式文件和腳本文件引用的標(biāo)簽(圖片什么的不在這里討論)。至于數(shù)據(jù)哪里來(lái),下面介紹兩種方式:
腳本請(qǐng)求獲取
通常,在腳本加載完畢后,腳本會(huì)執(zhí)行一段向服務(wù)端發(fā)送請(qǐng)求數(shù)據(jù)的代碼,然后通過(guò)回調(diào)函數(shù)取出數(shù)據(jù)并做初始化工作。這一個(gè)過(guò)程為:請(qǐng)求頁(yè)面 => 渲染頁(yè)面 => 加載腳本 => 請(qǐng)求數(shù)據(jù) => 數(shù)據(jù)與腳本一起初始化 => 初始化完畢,也就是從加載應(yīng)用到啟動(dòng)應(yīng)用是以順序任務(wù)的形式執(zhí)行。
直接填充于隱藏標(biāo)簽中
服務(wù)端也可以直接將數(shù)據(jù)填充到網(wǎng)頁(yè)中的一個(gè)隱藏標(biāo)簽中再傳回給客戶端,也就是上面順序中把獲取數(shù)據(jù)放在頁(yè)面請(qǐng)求之前。之后在腳本中直接去獲取相應(yīng)的DOM中的內(nèi)容也就是數(shù)據(jù),來(lái)進(jìn)行初始化工作。
這兩種方法各有優(yōu)劣,因?yàn)椴皇潜疚闹攸c(diǎn),在此就直接帶過(guò)。不過(guò)筆者更傾向于前者。
1.1 工作流圖如果用工作流的思想去理解,大概可以為下圖(第一種方式):
1.2 結(jié)果分析在這里我們只研究數(shù)據(jù)以及main.js的加載情況。
base64.css是用來(lái)存儲(chǔ)一些小圖片的base64字符串并且是允許延后加載,可以將其歸為圖片資源一類。
總體情況還是可以接受的,畢竟后端同學(xué)對(duì)緩存這一塊下了很大的功夫,用戶會(huì)在500ms左右看到頁(yè)面的內(nèi)容,到了600ms之后程序就可以正式啟動(dòng)。
這種模式的優(yōu)點(diǎn)是顯而易見(jiàn)的,這種順序加載啟動(dòng)模式易用性、可維護(hù)性都比較好,也能很好地發(fā)揮動(dòng)靜分離的特長(zhǎng)。
然而,我們認(rèn)為,如果將上圖中數(shù)據(jù)的請(qǐng)求放在前面和腳本一起并發(fā)請(qǐng)求,也許會(huì)減少整個(gè)頁(yè)面的加載和啟動(dòng)所需時(shí)間,而且后端同學(xué)還覺(jué)得這樣的加載效果會(huì)更加直觀、整齊……
于是便有了下面的研究。
2. 單并發(fā)模式要實(shí)現(xiàn)數(shù)據(jù)與腳本并發(fā)加載,最核心的就是要讓數(shù)據(jù)不依賴于腳本進(jìn)行加載,筆者所能想到的有兩種:
在頭部添加一個(gè)script,插入一段發(fā)送ajax請(qǐng)求的代碼,向服務(wù)端發(fā)送數(shù)據(jù)請(qǐng)求。
同樣是添加一個(gè)script,將其src設(shè)為數(shù)據(jù)請(qǐng)求的url來(lái)引用外部數(shù)據(jù)資源。
單從執(zhí)行效率來(lái)說(shuō),1比2還多了一步,故本文中選擇2進(jìn)行討論。
2.1難點(diǎn)與解決方案 如何保證script標(biāo)簽進(jìn)行外部下載時(shí)不阻塞其他資源的下載?把script在head標(biāo)簽內(nèi)。在下載script引入的外部腳本時(shí),瀏覽器處于阻塞狀態(tài),網(wǎng)絡(luò)不好或者script文件過(guò)大時(shí),頁(yè)面處于空白停頓狀態(tài),這樣的體驗(yàn)是很不好的。
我們一般會(huì)將腳本文件放在頁(yè)面底部來(lái)降低腳本下載與運(yùn)行所帶來(lái)的阻塞影響,而且這樣可以保證腳本中所引用的頁(yè)面元素已經(jīng)渲染完畢。
而數(shù)據(jù)請(qǐng)求是與頁(yè)面元素?zé)o關(guān),在這里我們希望它能放在頭部確??梢员M早地開(kāi)始加載來(lái)達(dá)到與其它資源一起請(qǐng)求,但又不阻塞其他資源的下載。
瀏覽器對(duì)標(biāo)記有async屬性的scripts會(huì)立即加載并解析,該script相對(duì)于頁(yè)面的其余部分異步地執(zhí)行(當(dāng)頁(yè)面繼續(xù)進(jìn)行解析時(shí),腳本將被執(zhí)行)。
這里的解決辦法則是采用HTML5的async屬性,將其應(yīng)用于數(shù)據(jù)請(qǐng)求相關(guān)的script上,就可以達(dá)到腳本與數(shù)據(jù)并發(fā)加載的效果。如下代碼:
script(src="/Table/Data" type="text/javascript" async="async")在數(shù)據(jù)與腳本加載的順序未知的情況下,如何保證正確的頁(yè)面啟動(dòng)?
javascript是一門解析性語(yǔ)言,當(dāng)它加載完畢之后就會(huì)執(zhí)行。
此時(shí)的數(shù)據(jù)請(qǐng)求變成了一個(gè)script標(biāo)簽,也就是說(shuō),它可以變成一段與賦值相關(guān)的javascript代碼,直接把得到的結(jié)果放在公共環(huán)境中。如果不把它變成賦值代碼,基于上面的引言,可能得到的數(shù)據(jù)就會(huì)變成環(huán)境中的一個(gè)匿名對(duì)象而在之后無(wú)法再次被訪問(wèn)。這樣一來(lái),在腳本記載完畢就可以直接去引用這個(gè)結(jié)果進(jìn)行啟動(dòng)頁(yè)面。那么問(wèn)題來(lái)了……
基于上面async中闡述的方案,在實(shí)際中更多時(shí)候我們可能無(wú)法100%保證數(shù)據(jù)與腳本加載的先后順序。資源大小的確一定程度決定了加載時(shí)間,但是網(wǎng)絡(luò)傳輸也有著許多不穩(wěn)定的因素。
我們也不可能直接在任何一個(gè)script中直接引用對(duì)方的資源(如果未加載完畢,會(huì)返回undefined的錯(cuò)誤)。
不到萬(wàn)不得已,不應(yīng)該使用輪詢檢查的方法去解決并發(fā)問(wèn)題,這樣的應(yīng)用性能太低,和我們的初衷相違背。
既然它們是相互依賴的關(guān)系,而且我們只需要其中一方引用另一方的資源即可完成我們所需要的啟動(dòng)。在這里,我們只需要讓先加載完成前的把資源暴露到公共環(huán)境window中,讓后加載的那一方察覺(jué)到之后直接引用進(jìn)行啟動(dòng)即可。
對(duì)于數(shù)據(jù)與腳本,我們把它們的資源分別定為:
名稱 | 資源 | 描述 |
---|---|---|
數(shù)據(jù) | allData(Object) | 存儲(chǔ)所有的動(dòng)態(tài)數(shù)據(jù) |
腳本 | mainInitByData(Function) | 主引導(dǎo)函數(shù) |
在數(shù)據(jù)請(qǐng)求里,代碼為:
var allData = window.allData = "{"name":"data"}"; //檢查腳本的資源是否存在 if (typeof window.mainInitByData !== "undefined") { mainInitByData(JSON.parse(allData)); };
腳本里相關(guān)的片段則為:
var mainInitByData = window.mainInitByData = function(data) { //TODO... } if (typeof window.allData !== "undefined") { mainInitByData(JSON.parse(allData)); }2.2 工作流圖 2.3 結(jié)果分析
不難發(fā)現(xiàn),經(jīng)過(guò)并行化處理之后,加載頁(yè)面的效率相比于之前的順序模式大大增加了。且頁(yè)面程序也能順利啟動(dòng)(這里大家可以自行嘗試)。
不料后端同學(xué)在一兩個(gè)月后,又提出了希望作多路數(shù)據(jù)并發(fā)請(qǐng)求,因?yàn)閯?dòng)態(tài)數(shù)據(jù)中也有部分?jǐn)?shù)據(jù)相對(duì)一段時(shí)間內(nèi)為靜態(tài)的,這部分?jǐn)?shù)據(jù)可以用緩存處理,其他數(shù)據(jù)則直接從其它服務(wù)器中獲取,可以進(jìn)一步提高并發(fā)效率。事情變得越來(lái)越有趣,也有了下面的研究。
3. 多路并發(fā)模式 3.1 “繼承”單并發(fā)此時(shí),假設(shè)我們所需請(qǐng)求的數(shù)據(jù)共有三條A、B、C,其中A為相對(duì)靜態(tài)數(shù)據(jù),可以做出以下定義:
名稱 | 資源 | 描述 |
---|---|---|
子數(shù)據(jù)A | AData(Object) | 存儲(chǔ)A的相對(duì)靜態(tài)數(shù)據(jù) |
子數(shù)據(jù)B | BData(Object) | 存儲(chǔ)B的動(dòng)態(tài)數(shù)據(jù) |
子數(shù)據(jù)C | CData(Object) | 存儲(chǔ)C的動(dòng)態(tài)數(shù)據(jù) |
腳本 | mainInitByData(Function) | 主引導(dǎo)函數(shù) |
如果繼續(xù)沿用單并發(fā)中的策略,腳本的相關(guān)片段代碼則為:
var mainInitByData = window.mainInitByData = function(dataA, dataB, dataC) { //TODO... } if (typeof window.dataA !== "undefined" && window.dataB !== "undefined" && window.dataC !== "undefined") { var dataA = JSON.parse(dataA), dataB = JSON.parse(dataB), dataC = JSON.parse(dataC); mainInitByData(dataA, dataB, dataC); }
以上數(shù)據(jù)只是一個(gè)例子,并不代表這樣就可以解決這類的問(wèn)題。假如有一天后端突然要求一次并發(fā)加載10條數(shù)據(jù),代碼就會(huì)變得十分冗余。
既然要處理并發(fā),那么單并發(fā)的思想是可以沿用的,只是這里的方向不對(duì)。
不妨我們換個(gè)角度思考,腳本仍然和數(shù)據(jù)進(jìn)行互相檢查,但是這個(gè)數(shù)據(jù)包含了所有子數(shù)據(jù),在這里我直接將其稱為父數(shù)據(jù)。那子數(shù)據(jù)之間怎么辦?
3.2 以信號(hào)量的思想處理數(shù)據(jù)整合之所以說(shuō)是信號(hào)量的思想而不是信號(hào)量,因?yàn)樾盘?hào)量本身是多線程多任務(wù)同步,而對(duì)于帶有async標(biāo)簽里的javascript是單線程異步,但不代表javascript不能利用信號(hào)量的思想,信號(hào)量的思想就是在解決處理并發(fā)問(wèn)題。具體的信號(hào)量定義,請(qǐng)讀者自行查閱。
為了更好的描述這個(gè)借用思想的過(guò)程,先做以下定義:
父數(shù)據(jù)與子數(shù)據(jù)之間共用一種信號(hào)量,子數(shù)據(jù)運(yùn)用這種信號(hào)量進(jìn)行數(shù)據(jù)的整合,而父數(shù)據(jù)應(yīng)用這種信號(hào)量進(jìn)行與腳本初始化啟動(dòng)。
每次子數(shù)據(jù)加載完畢后,釋放信號(hào)量,并把自己的數(shù)據(jù)整合到父數(shù)據(jù)中。
假設(shè)子數(shù)據(jù)之間申請(qǐng)信號(hào)量的順序未知,但必定在父數(shù)據(jù)之前。
整合的數(shù)據(jù)以及信號(hào)量都放在一個(gè)js對(duì)象integrateData中,分別命名為data、sem(其值為1-子數(shù)據(jù)數(shù)量),即integrateData = {data: {}, sem: -2}
這里可能需要對(duì)子數(shù)據(jù)的格式做一定的調(diào)整。變成以下類型,方便做整合
{"message":"success", "data": {....}}
那么對(duì)于所有子數(shù)據(jù)的處理代碼為:
var result = "JSON"; var integrateData = window.integrateData || (window.integrateData = { data: {}, sem: 1 - 3 }); var onDataCallback = window.onDataCallback || (window.onDataCallback = function(result_, integrateData) { function dataIsReady(integrateData) { return integrateData.sem > 0; } function dataReadyCallback(integrateData) { integrateData.sem--; //父數(shù)據(jù)與腳本啟動(dòng) var mainInitBydata = window.mainInitBydata; if (typeof mainInitBydata === "function") { mainInitBydata(integrateData); } integrateData.sem++; } if (dataIsReady(integrateData)) { alert("非法請(qǐng)求"); return; } var result = result_; if (typeof result_ === "string") { result = JSON.parse(result_); } //數(shù)據(jù)整合 if (result.message === "success") { var data = result.data; for (var key in data) { integrateData.data[key] = data[key]; } } //釋放信號(hào)量 integrateData.sem++; //檢查信號(hào)量 if (dataIsReady(integrateData)) { dataReadyCallback(integrateData); } }); onDataCallback(result, integrateData);
此時(shí),腳本里的相關(guān)代碼則為:
var mainInitByData = window.mainInitByData = function(integrateData) { //TODO... } var integrateData = window.integrateData; //這里無(wú)需擔(dān)心沖突問(wèn)題,因?yàn)閖s是單線程執(zhí)行,子數(shù)據(jù)整合完畢后會(huì)直接執(zhí)行父數(shù)據(jù)檢查腳本資源的行為,所以sem>0時(shí),父數(shù)據(jù)處于就緒狀態(tài)。 if (integrateData && integrateData.sem > 0) { mainInitBydata(integrateData) }3.3 工作流圖 3.4 結(jié)果分析
其實(shí)效率相比單并發(fā)提高不多,主要是涉及的動(dòng)態(tài)數(shù)據(jù)規(guī)模不大,而且每次發(fā)送的請(qǐng)求報(bào)文和響應(yīng)報(bào)文都會(huì)有一定大小的報(bào)頭,造成不必要的開(kāi)銷。但假如動(dòng)態(tài)數(shù)據(jù)足夠大的話,這種策略是可以起到很大的作用。同時(shí),單并發(fā)模式中的雙向檢查也可以用信號(hào)量的思想實(shí)現(xiàn)。
總結(jié)總結(jié)以上的模式,我們可以得出以下的結(jié)論:
模式 | 效率 | 易用性 | 性能主要影響因素 | 適用場(chǎng)景 |
---|---|---|---|---|
順序 | 普通 | 容易 | 數(shù)據(jù)與程序的大小總和 | 一般的小項(xiàng)目 |
單并發(fā) | 比順序模式高 | 普通 | 數(shù)據(jù)與程序大小比例 | 大多數(shù)動(dòng)靜分離的網(wǎng)站應(yīng)用 |
多路并發(fā) | 一般比單并發(fā)高,當(dāng)數(shù)據(jù)太小時(shí)效率會(huì)比單并發(fā)低 | 復(fù)雜 | 劃分?jǐn)?shù)據(jù)的比例 | 數(shù)據(jù)比較龐大的網(wǎng)站應(yīng)用,尤其是數(shù)據(jù)之間按相對(duì)均勻的比例歸類 |
除此以外,上述中,單并發(fā)與多路并發(fā)的一大缺陷就是代碼的耦合性會(huì)相對(duì)地提高,對(duì)于多路并發(fā)而言,如果子數(shù)據(jù)請(qǐng)求之間有依賴關(guān)系,可能還要定義多種不同的信號(hào)量,不利于管理。
利用現(xiàn)有的工具比如EventProxy,可以很好管理這些并發(fā)請(qǐng)求,包括任務(wù)之間的依賴關(guān)系。通過(guò)事件訂閱與觸發(fā)的形式可以讓程序更好地知道當(dāng)前所完成的任務(wù)以觸發(fā)相應(yīng)的回調(diào)函數(shù)進(jìn)行處理。
希望本文可以給讀者帶來(lái)一定的幫助。
最后打個(gè)小廣告,歡迎follow我的github:https://github.com/zero-mo
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/87802.html
摘要:關(guān)于動(dòng)靜分離的描述,這里推薦一篇不錯(cuò)的博文網(wǎng)站靜態(tài)化處理動(dòng)靜分離策略。這里的解決辦法則是采用的屬性,將其應(yīng)用于數(shù)據(jù)請(qǐng)求相關(guān)的上,就可以達(dá)到腳本與數(shù)據(jù)并發(fā)加載的效果。 作者:莫冠釗 轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接和作者信息 前言 當(dāng)今許多大型網(wǎng)頁(yè)應(yīng)用尤其是SPA均采用了動(dòng)靜分離的策略。關(guān)于動(dòng)靜分離的描述,這里推薦一篇不錯(cuò)的博文 網(wǎng)站靜態(tài)化處理—?jiǎng)屿o分離策略。 本人是做前端的,之前有幸與一...
摘要:為了優(yōu)化動(dòng)靜混合站點(diǎn)和純動(dòng)態(tài)站點(diǎn)的加速效果,阿里云推出了全站加速方案,通過(guò)智能區(qū)分動(dòng)靜態(tài)請(qǐng)求,實(shí)現(xiàn)整站加速效果的全面提升。 摘要: 伴隨著近幾年O2O的爆發(fā),網(wǎng)絡(luò)已經(jīng)不僅是傳統(tǒng)的展示企業(yè)品牌的渠道,而逐漸演變成為嫁接企業(yè)和用戶之間服務(wù)和交流的橋梁,我們開(kāi)始賦予網(wǎng)絡(luò)更多的功能,比如購(gòu)物、出行、學(xué)習(xí)、娛樂(lè)等等。 同時(shí),網(wǎng)絡(luò)內(nèi)容形態(tài)的進(jìn)階發(fā)展,網(wǎng)頁(yè)內(nèi)容已經(jīng)從靜態(tài)的圖片、文字向短視頻、直播演變...
摘要:接入層作用一的聚合。接入層作用二服務(wù)發(fā)現(xiàn)與動(dòng)態(tài)負(fù)載均衡既然統(tǒng)一的入口變?yōu)榱私尤雽樱瑒t接入層就有責(zé)任自動(dòng)的發(fā)現(xiàn)后端拆分,聚合,擴(kuò)容,縮容的服務(wù)集群,當(dāng)后端服務(wù)有所變化的時(shí)候,能夠?qū)崿F(xiàn)健康檢查和動(dòng)態(tài)的負(fù)載均衡。 此文已由作者劉超授權(quán)網(wǎng)易云社區(qū)發(fā)布。 歡迎訪問(wèn)網(wǎng)易云社區(qū),了解更多網(wǎng)易技術(shù)產(chǎn)品運(yùn)營(yíng)經(jīng)驗(yàn)。 這個(gè)系列是微服務(wù)高并發(fā)設(shè)計(jì),所以我們先從最外層的接入層入手,看都有什么樣的策略保證高并發(fā)。...
摘要:反向代理要說(shuō)反向代理,我們就先要理解正向代理下面我們就談?wù)務(wù)虼砗头聪虼戆???蛻舳瞬拍苁褂谜虼怼7聪虼砜偨Y(jié)就一句話代理端代理的是服務(wù)端。因此,動(dòng)態(tài)資源轉(zhuǎn)發(fā)到服務(wù)器我們就使用到了前面講到的反向代理了。 反向代理 要說(shuō)反向代理,我們就先要理解正向代理 ,下面我們就談?wù)務(wù)虼砗头聪虼戆伞?正向代理 一個(gè)位于客戶端和原始服務(wù)器(origin server)之間的服務(wù)器,為了從原始...
閱讀 1844·2021-11-25 09:43
閱讀 2773·2019-08-30 15:53
閱讀 1901·2019-08-30 15:52
閱讀 2994·2019-08-29 13:56
閱讀 3408·2019-08-26 12:12
閱讀 647·2019-08-23 17:58
閱讀 2235·2019-08-23 16:59
閱讀 1084·2019-08-23 16:21