loadscript 是使用js去請求一個(gè) script 到文檔里猪狈,這樣不會在首次加載時(shí)去下載和執(zhí)行script,就為瀏覽器減少了首次渲染的工作量
打包后代碼很可能是長這樣,里面有好多 module茧痒,好多變量,好多 function融蹂,最后有一個(gè) render 函數(shù)旺订,調(diào)用 vue/react 的渲染函數(shù)去渲染 element
(function () {
/*
... const module= ...
... const data=...
... function action(){
...
}
一大串代碼。有多長呢超燃?
*/
function render() {
// render
ReactDOM.render(a, document.getElementById('root'))
}
render()
})()
瀏覽器對于html文檔解析的大概順序
看看知乎大佬回答
取幾個(gè)關(guān)鍵點(diǎn)來看
瀏覽器是從上到下從左到右解析 html 文檔的
script 中可能會有 dom 操作区拳,瀏覽器在解析script標(biāo)簽后就阻塞 dom 樹的構(gòu)建
思考一下我把支持我渲染的代碼放到 RenderScript 里,整個(gè)頁面就是個(gè) div 容器和 render script意乓,這樣就可以做到最快的渲染出頁面樱调。反過來說就是把代碼中和渲染無關(guān),甚至和首屏無關(guān)的代碼届良,都可以拋棄笆凌,這樣首屏渲染就不會被無關(guān)的js阻塞
使用js創(chuàng)建標(biāo)簽請求script
const script = document.createElement('script')
script.src = src
document.head.appendChild(script)
Promise
所謂 Promise,簡單說就是一個(gè)容器伙窃,里面保存著某個(gè)未來才會結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果菩颖。
插入 script 的操作可以看作執(zhí)行了一個(gè)動(dòng)作(扔給瀏覽器一個(gè) script 標(biāo)簽讓他加載),但是并不是立馬生效为障,瀏覽器要去下載晦闰,下載也可能因?yàn)榫W(wǎng)絡(luò)失敗放祟,那他就是一個(gè)標(biāo)準(zhǔn)的未來才會結(jié)束的事件,并且有2個(gè)狀態(tài):成功:失敗
狀態(tài)理清了呻右,在不同狀態(tài)下執(zhí)行不同的代碼跪妥,這就是 promise 的好處。比如我請求 echarts 的 js声滥,我請求成功后眉撵,我就可以調(diào)用echarts.init
開始畫圖了;如果失敗了落塑,我應(yīng)該顯示 error 畫面纽疟,提醒用戶,加載失敗了憾赁,或者重試
來試試把 loadscript 封裝成 promise
function LoadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
單例模式
相同的 script 不用重復(fù)請求污朽,而是取上一次的結(jié)果就好了。理清了 promise 龙考,那把這個(gè)請求 script 的 promise 操作按照src存起來就好了蟆肆,我下一次按照 src 想調(diào)用loadScript
函數(shù),我就可以拿到上次的請求結(jié)果啦
不過注意的是晦款,上次 loadScript 如果失敗了炎功,那這個(gè) promise 就永遠(yuǎn)是 rejected 狀態(tài)了,要想重試的話缓溅,只能刪除這個(gè)值蛇损,重新創(chuàng)建 script,所以我們要寫一個(gè)delete
函數(shù)去清除存儲的值
const LoadScript = (function () {
let instances = {}
return function (src) {
if (!instances[src]) {
instances[src] = new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
// 提供一個(gè)刪除instace的接口
instances[src].deleteInstance = function () {
delete instances[src]
}
}
return instances[src]
}
})()
這樣就改造好啦肛宋,不論多少次調(diào)用loadscript州藕,實(shí)際上這個(gè)動(dòng)作只執(zhí)行1次束世,就實(shí)現(xiàn)多次loadscript
取上一次的結(jié)果了