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