摘要:所以這里需要另外的操作來(lái)對(duì)文件加載進(jìn)行優(yōu)化加載這是中定義的一個(gè)屬性,它用來(lái)表示的是,當(dāng)渲染引擎遇到的時(shí)候,如果引用的是外部資源,則會(huì)暫時(shí)掛起,并進(jìn)行加載。
在js引擎部分,我們可以了解到,當(dāng)渲染引擎解析到script標(biāo)簽時(shí),會(huì)將控制權(quán)給JS引擎,如果script加載的是外部資源,則需要等待下載完后才能執(zhí)行。 所以,在這里,我們可以對(duì)其進(jìn)行很多優(yōu)化工作。
放置在body底部為了讓渲染引擎能夠及早的將DOM樹給渲染出來(lái),我們需要將script放在body的底部,讓頁(yè)面盡早脫離白屏的現(xiàn)象,即會(huì)提早觸發(fā)DOMContentLoaded事件. 但是由于在IOS Safari, Android browser以及IOS webview里面即使你把js腳本放到body尾部,結(jié)果還是一樣。 所以這里需要另外的操作來(lái)對(duì)js文件加載進(jìn)行優(yōu)化.
defer加載這是HTML4中定義的一個(gè)script屬性,它用來(lái)表示的是,當(dāng)渲染引擎遇到script的時(shí)候,如果script引用的是外部資源,則會(huì)暫時(shí)掛起,并進(jìn)行加載。 渲染引擎繼續(xù)解析下面的HTML文檔,解析完時(shí),則會(huì)執(zhí)行script里面的腳本。
他的支持度是<=IE9的.
并且,他的執(zhí)行順序,是嚴(yán)格依賴的,即:
當(dāng)頁(yè)面解析完后,他便會(huì)開始按照順序執(zhí)行 outside1 和 outside2文件。
如果你在IE9以下使用defer的話,可能會(huì)遇到 它們兩個(gè)不是順序執(zhí)行的,這里需要一個(gè)hack進(jìn)行處理,即在兩個(gè)中間加上一個(gè)空的script標(biāo)簽
//hack
但是,如果你將defer屬性用在inline的script腳本里面,在Chrome和FF下是沒有效果的。
即:
async是H5新定義的一個(gè)script 屬性。 他是另外一種js的加載模式。
渲染引擎解析文件,如果遇到script(with async)
繼續(xù)解析剩下的文件,同時(shí)并行加載script的外部資源
當(dāng)script加載完成之后,則瀏覽器暫停解析文檔,將權(quán)限交給JS引擎,指定加載的腳本。
執(zhí)行完后,則恢復(fù)瀏覽器解析腳本
可以看出async也可以解決 阻塞加載 這個(gè)問題。不過,async執(zhí)行的時(shí)候是異步執(zhí)行,造成的是,執(zhí)行文件的順序不一致。即:
這時(shí),誰(shuí)先加載完,就先執(zhí)行誰(shuí)。所以,一般依賴文件就不應(yīng)該使用async而應(yīng)該使用defer.
defer的兼容性比較差,為IE9+,不過一般是在移動(dòng)端使用,也就不存在這個(gè)problem了。
其實(shí),defer和async的原理圖,如圖一樣。(包括放在head中的script標(biāo)簽)
腳本異步是一些異步加載庫(kù)(比如require)使用的基本加載原理. 直接上代碼:
function asyncAdd(src){ var script = document.createElement("script"); script.src = src; document.head.appendChild(script); } //加載js文件 asyncAdd("test.js");
這時(shí)候,可以異步加載文件,不會(huì)造成阻塞的效果.
但是,這樣加載的js文件是無(wú)序的,無(wú)法正常加載依賴文件。
如果你想要js文件按照你自定義的順序執(zhí)行,則要將async設(shè)置為false. 但是會(huì)阻塞其它文件的加載
var asyncAdd = (function(){ var head = document.head, script; return function(src){ script = document.createElement("script"); script.src= src; script.async=false; document.head.appendChild(script); } })(); //加載文件 asyncAdd("first.js"); asyncAdd("second.js"); //或者簡(jiǎn)便一點(diǎn) ["first.js","second.js"].forEach((src)=>{async(src);});
但是,使用腳本異步加載的話,需要等待css文件加載完后,才開始進(jìn)行加載,不能充分利用瀏覽器的并發(fā)加載優(yōu)勢(shì)。而使用靜態(tài)文本加載async或者defer則不會(huì)出現(xiàn)這個(gè)問題。
使用腳本異步加載時(shí),只能等待css加載完后才會(huì)加載
使用靜態(tài)的async加載時(shí),css和js會(huì)并發(fā)一起加載
(from 妙凈)
關(guān)于這三種如何取舍,那就主要看leader給我們目標(biāo)是什么,是兼容IE8,9還是手機(jī)端,還是桌面瀏覽器,或者兩兩組合。
但是對(duì)于多帶帶使用某一個(gè)技能的場(chǎng)景,使用時(shí)需要注意一些tips。
js文件放置位置應(yīng)該放置到body末尾
如果使用async的話,最后加上defer以求向下兼容
//如果兩者都支持,async會(huì)默認(rèn)覆蓋掉defer //如果只支持一個(gè),則執(zhí)行對(duì)應(yīng)的即可
通常,我們使用的加載都是defer加載(因?yàn)楹軓?qiáng)的依賴關(guān)系).
但,上面的簡(jiǎn)單js文件依賴加載只針對(duì)于,依賴關(guān)系不強(qiáng),或者說,相互關(guān)聯(lián)性不強(qiáng)的js文件。先在js模塊化思想 已經(jīng)成為主流, 如果這樣手動(dòng)添加defer或者async是沒有太大的實(shí)際意義的。
原因就在于, 好復(fù)雜~
所以,才有了webpack,requireJS等模塊打包工具。這也是給我們?cè)谛阅芎徒Y(jié)構(gòu)上尋找一個(gè)平衡點(diǎn)的嘗試。
這里也給大家安利一些建議:
業(yè)務(wù)邏輯代碼使用模塊化書寫, 測(cè)試代碼或者監(jiān)聽代碼使用async,或者defer填充。 這也是比較好的實(shí)踐。
最簡(jiǎn)單的腳本異步就是在head里添加一個(gè)script標(biāo)簽.
var asyncAdd = (function(){ var head = document.head, script; return function(src){ script = document.createElement("script"); script.async=false; document.head.appendChild(script); } })(); asyncAdd("test.js"); //異步加載文檔
這樣寫,其實(shí)還不如,直接加async. 這樣簡(jiǎn)單的異步加載,是不能滿足我們模塊化書寫的龐大業(yè)務(wù)邏輯的。 這里,我們將一步一步的優(yōu)化我們的代碼,實(shí)現(xiàn),異步j(luò)s文件加載的模塊化.
串行加載js文件對(duì)上述簡(jiǎn)單js異步腳本的升級(jí)版就是使用串行方式,加載js腳本。首先,我們需要了解一下,DOMreadyState和onload事件,這里先安利一下Nicholas大神 推薦的一份檢測(cè)onload的腳本:
function loadScript(url, callback){ var script = document.createElement("script") script.type = "text/javascript"; if (script.readyState){ //IE script.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; //解除引用 callback(); } }; } else { //Others script.onload = function(){ callback(); }; } script.src = url; document.body.appendChild(script); }
但從IE11開始,已經(jīng)支持onload事件, 不過,現(xiàn)在這份代碼的價(jià)值還是非常大的, 目前主流兼容IE8+。
當(dāng)然,我們可以使用loadScript中進(jìn)行回調(diào)加載.
loadScript("test1.js",loadScript("test2.js",loadScript("test3.js")));
不過,這簡(jiǎn)直就是沒人性的寫法。 所以,這里我們可以進(jìn)行優(yōu)化一下。我們可以使用以前的模式,進(jìn)行重構(gòu),這里我選擇命令模式和鏈?zhǔn)秸{(diào)用。
直接貼代碼吧:
var loadJs = (function() { var script = document.createElement("script"); if (script.readyState) { return function(url, cb) { script = document.createElement("script"); script.src = url; document.body.appendChild(script); script.onreadystatechange = function() { if (script.readyState == "loaded" || script.readyState == "complete") { script.onreadystatechange = null; //解除引用 cb(); } }; } } else { return function(url, cb) { script = document.createElement("script"); script.src = url; document.body.appendChild(script); script.onload = function() { cb(); }; } } })(); //測(cè)試用例: commandJs.add("test.js",[test.js,test1.js]).exe(); //或者 commandJs.add("test.js").add("test1.js").add([test1.js,test2,js]).exe(); var commandJs = (function() { var group = [], len = 0; //類型檢測(cè) //數(shù)組 var isArray = function(para) { return (para instanceof Array); } //String類型 var isString = function(para) { return Object.prototype.toString.call(para) === "[object String]"; } //集合檢測(cè) var correctType = function(para) { return isString(para) || isArray(para); } //添加src內(nèi)容 var add = function() { for (var i = 0, js; js = arguments[i++];) { if (!correctType(js)) { throw new Error(`the ${i}th js file"s type is not correct`); } group.push(js); } return this; } var isFinish = function() { len--; if (len === 0) { exe(); //開始加載下一組js文件 } } //并行加載js文件 var loadArray = function(urls) { urls.forEach((url) => { loadJs(url, (function() { isFinish(); //判斷是否執(zhí)行完全 }).bind(this)); }); } var exe = function() { if (group.length === 0) return; //遍歷完所有的urls時(shí),退出執(zhí)行 var js = group.shift(); if (isArray(js)) { len = js.length; loadArray(js); } else { len = 1; loadArray([js]); } return this; } return { exe, add } })();
OK, 我們來(lái)驗(yàn)證一樣,串行執(zhí)行的測(cè)試結(jié)果:
commandJs.add("./js/loader01.js").add("./js/loader02.js").exe(); //或者 commandJs.add("./js/loader01.js","./js/loader02.js").exe(); //這兩種寫法都是可以的
最后的結(jié)果是:
ok~ 可以通過,這樣可以自定義加載很多依賴文件。 但是,造成結(jié)果是,時(shí)間成本耗費(fèi)太大。 有時(shí)候, 一個(gè)主文件的main 有很多依賴js模塊, 那么我們考慮一下,能否把這些js模塊并行加載進(jìn)來(lái)呢?
其實(shí),上面的那一串代碼,已經(jīng)將串行和并行給結(jié)合起來(lái)了。那并行是怎么做的呢? 其實(shí)就是,同時(shí)向頁(yè)面中添加script tag然后監(jiān)聽,是否所有的tag都已經(jīng)加載完整。如果是,開始加載下一組js文件。
其實(shí),最主要的代碼塊是這里:
var isFinish = function() { len--; if (len === 0) { exe(); //開始加載下一組js文件 } } //并行加載js文件 var loadArray = function(urls) { urls.forEach((url) => { loadJs(url, (function() { isFinish(); //判斷是否執(zhí)行完全 }).bind(this)); }); } //執(zhí)行順序就是,然后中間加了一些trick進(jìn)行,類型的判斷. //exe=> loadArray => isFinish ~>exe并行加載js
OK, 上面我們已經(jīng)測(cè)試了js的異步加載,這里我們測(cè)試一下js并行加載的效果:
commandJs.add(["./js/loader01.js","./js/loader02.js"]).exe();
上圖時(shí)間:
我們對(duì)比一下異步加載的:
從上面很容易知道,異步和同步加載的區(qū)別,因?yàn)檫@個(gè)文件較小體現(xiàn)的價(jià)值不是很大,我們換一個(gè)比較大的文件進(jìn)行加載:
//并行: commandJs.add(["http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js","https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom-server.js"]).exe(); //串行 commandJs.add("http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js","https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom-server.js").exe();
看一下圖:
//并行
//串行
大家可以鉤鉤手指,算一下兩者的時(shí)間差, 一個(gè)是取max, 一個(gè)是取add. 結(jié)果是顯而易見的。 當(dāng)然,模塊加載插件比如requireJS,labJS,他們所要做的功能比這里的要豐滿的多, 當(dāng)你 多個(gè)文件引入同一個(gè)依賴的時(shí)候,只需要加載一次(判斷唯一性), 以及引用模塊的ID 的 標(biāo)識(shí)等。
js 腳本異步加載還有很多方法,比如xhr, iframe ,以及使用img 的 src進(jìn)行加載,這些都是可行的, 但是他們的局限性也很大, xhr,iframe的同域要求,使用img還不如直接使用script。 我這里列一下他們的大概情況表吧
加載方式 | 實(shí)現(xiàn)效果 |
---|---|
xhr | 腳本并行下載,要求同域,不會(huì)阻塞其他資源 |
iframe | 要求同域,腳本并行下載,不阻塞其他資源,但損耗較大,目前業(yè)界推崇淘汰 |
img | 慘無(wú)人道,大家知道有就行了 |
其實(shí),大家看到這里也就可以了。下文,主要是我對(duì)上面代碼的一個(gè)優(yōu)化,或者說是Promise實(shí)踐. 由于懶得開篇幅了,所以就直接接著寫。
使用Promise異步加載前面說了,如果使用像loadScript這種,直接進(jìn)行回調(diào)串行的話,造成的結(jié)果是,callback hell;
即
loadScript("test1.js",loadScript("test2.js",loadScript("test3.js")));
如果了解Promise的童鞋,應(yīng)該知道,使用Promise就可以完全解決這個(gè)問題。 這里,我們使用Promise對(duì)上面進(jìn)行代碼進(jìn)行重構(gòu)
var loadJs = (function() { var script = document.createElement("script"); if (script.readyState) { return function(url) { return new Promise(function(res, rej) { script = document.createElement("script"); script.src = url; document.body.appendChild(script); script.onreadystatechange = function() { if (script.readyState == "loaded" || script.readyState == "complete") { script.onreadystatechange = null; //解除引用 res(); } }; }) } } else { return function(url) { return new Promise(function(res, rej) { script = document.createElement("script"); script.src = url; document.body.appendChild(script); script.onload = function() { res(); }; }) } } })();
接著,我們來(lái)調(diào)用代碼看看:
loadJs("http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js") .then(function(){ return loadJs("./js/loader01.js"); }).then(function(){ console.log("finish loading"); })
結(jié)果是:
那如果我們想并行加載的話,怎么辦呢? 很簡(jiǎn)單使用Promise提供的all函數(shù)就可以了.
show u the code:
Promise.all([loadJs("http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"),loadJs("./js/loader01.js")])
結(jié)果為:
OK~ 平時(shí),我們加載模塊的時(shí)候,就可以使用Promise來(lái)進(jìn)行練習(xí),這樣可以減少很多不必要的邏輯代碼。簡(jiǎn)直,贊~(≧▽≦)/~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78680.html
摘要:介紹一款模塊加載工具的入門,并且重點(diǎn)介紹其優(yōu)化工具。發(fā)布目錄項(xiàng)目源代碼工具目錄,例如構(gòu)建工具等。另外,前端代碼發(fā)布前都會(huì)進(jìn)行壓縮,使文件足夠小。原來(lái)是因?yàn)槔锪?,所以?yōu)化工具把也合并進(jìn)來(lái)了。而優(yōu)化工具要用好,要多嘗試他們的配置選項(xiàng)。 前端變化太快,如今RequireJS已經(jīng)無(wú)法吸引眼球了。介紹一款模塊加載工具:RequireJS的入門,并且重點(diǎn)介紹其優(yōu)化工具。 一、RequireJS簡(jiǎn)介...
摘要:本文主要介紹關(guān)鍵渲染路徑與網(wǎng)絡(luò)兩個(gè)方面的性能優(yōu)化并提供,篇幅較長(zhǎng)建議電腦觀看。百度統(tǒng)計(jì)代碼注意,的腳本不會(huì)被阻塞,完成后立即執(zhí)行,但是有可能會(huì)阻塞關(guān)鍵渲染路徑。 本文主要介紹關(guān)鍵渲染路徑與網(wǎng)絡(luò)兩個(gè)方面的性能優(yōu)化并提供demo,篇幅較長(zhǎng)建議電腦觀看。 前端優(yōu)化的方面太多,本文介紹的僅僅是其中的一部分,力求涵蓋關(guān)鍵渲染路徑的方方面面,及一些不常被提到的網(wǎng)絡(luò)優(yōu)化部分。 測(cè)試環(huán)境如無(wú)特殊說明均...
摘要:本文主要介紹關(guān)鍵渲染路徑與網(wǎng)絡(luò)兩個(gè)方面的性能優(yōu)化并提供,篇幅較長(zhǎng)建議電腦觀看。百度統(tǒng)計(jì)代碼注意,的腳本不會(huì)被阻塞,完成后立即執(zhí)行,但是有可能會(huì)阻塞關(guān)鍵渲染路徑。 本文主要介紹關(guān)鍵渲染路徑與網(wǎng)絡(luò)兩個(gè)方面的性能優(yōu)化并提供demo,篇幅較長(zhǎng)建議電腦觀看。 前端優(yōu)化的方面太多,本文介紹的僅僅是其中的一部分,力求涵蓋關(guān)鍵渲染路徑的方方面面,及一些不常被提到的網(wǎng)絡(luò)優(yōu)化部分。 測(cè)試環(huán)境如無(wú)特殊說明均...
摘要:本文主要介紹關(guān)鍵渲染路徑與網(wǎng)絡(luò)兩個(gè)方面的性能優(yōu)化并提供,篇幅較長(zhǎng)建議電腦觀看。百度統(tǒng)計(jì)代碼注意,的腳本不會(huì)被阻塞,完成后立即執(zhí)行,但是有可能會(huì)阻塞關(guān)鍵渲染路徑。 本文主要介紹關(guān)鍵渲染路徑與網(wǎng)絡(luò)兩個(gè)方面的性能優(yōu)化并提供demo,篇幅較長(zhǎng)建議電腦觀看。 前端優(yōu)化的方面太多,本文介紹的僅僅是其中的一部分,力求涵蓋關(guān)鍵渲染路徑的方方面面,及一些不常被提到的網(wǎng)絡(luò)優(yōu)化部分。 測(cè)試環(huán)境如無(wú)特殊說明均...
摘要:中在性能優(yōu)化所做的努力,也大抵圍繞著這兩個(gè)大方向展開。因此,將依賴模塊從業(yè)務(wù)代碼中分離是性能優(yōu)化重要的一環(huán)。大型庫(kù)是否可以通過定制功能的方式減少體積。這又違背了性能優(yōu)化的基礎(chǔ)。接下來(lái)可以抓住一些細(xì)節(jié)做更細(xì)的優(yōu)化。中,為默認(rèn)啟動(dòng)這一優(yōu)化。 前言:在現(xiàn)實(shí)項(xiàng)目中,我們可能很少需要從頭開始去配置一個(gè)webpack 項(xiàng)目,特別是webpack4.0發(fā)布以后,零配置啟動(dòng)一個(gè)項(xiàng)目成為一種標(biāo)配。正因?yàn)?..
閱讀 2651·2021-11-22 15:24
閱讀 1383·2021-11-17 09:38
閱讀 2757·2021-10-09 09:57
閱讀 1209·2019-08-30 15:44
閱讀 2449·2019-08-30 14:00
閱讀 3550·2019-08-30 11:26
閱讀 2942·2019-08-29 16:28
閱讀 757·2019-08-29 13:56