在純靜態(tài)網(wǎng)站里,有時(shí)候會(huì)動(dòng)態(tài)更新某個(gè)區(qū)域往會(huì)選擇 Pjax(swup、barba.js)去處理,他們都是使用 ajax 和 pushState 通過(guò)真正的永久鏈接,頁(yè)面標(biāo)題和后退按鈕提供快速瀏覽體驗(yàn) 。
但是實(shí)際使用中可能會(huì)遇到不同頁(yè)面可能會(huì)需要加載不同插件處理,有些人可能會(huì)全量選擇加載,這樣會(huì)導(dǎo)致加載很多無(wú)用的腳本,有可能在用戶關(guān)閉頁(yè)面時(shí)都不一定會(huì)訪問(wèn)到,會(huì)很浪費(fèi)資源 。
解決思路首先想到的肯定是在請(qǐng)求到新的頁(yè)面后,我們手動(dòng)去比較當(dāng)前 DOM 和 新 DOM 之間 script 標(biāo)簽的差異,手動(dòng)給他插入到 body 里 。
處理 Script一般來(lái)說(shuō) JavaScript 腳本都是放在 body 后,避免阻塞頁(yè)面渲染,假設(shè)我們頁(yè)面腳本也都是在 body 后,并在 script 添加 [data-reload-script] 表明哪些是需要?jiǎng)討B(tài)加載的 。
首先我們直接獲取到帶有 [data-reload-script] 屬性的 script 標(biāo)簽:
// NewHTML 為 新頁(yè)面 HTMLconst pageContent = NewHTML.replace('<body', '<div id="DynamicPluginBody"').replace('</body>', '</div>');let element = document.createElement('div');element.innerHTML = pageContent;const children = element.querySelector('#DynamicPluginBody').querySelectorAll('script[data-reload-script]');然后通過(guò)創(chuàng)建 script 標(biāo)簽插入到 body:
children.forEach(item => {const element = document.createElement('script');for (const { name, value } of arrayify(item.attributes)) {element.setAttribute(name, value);}element.textContent = item.textContent;element.setAttribute('async', 'false');document.body.insertBefore(element)})如果你的插件都是通過(guò) script 引入,且不需要執(zhí)行額外的 JavaScript 代碼,只需要在 Pjax 鉤子函數(shù)這樣處理就可以了 。
執(zhí)行代碼塊實(shí)際很多插件不僅僅需要你引入,還需要你手動(dòng)去初始化做一些操作的 。我們可以通過(guò) src 去判斷是引入的腳本,還是代碼塊 。
let scripts = Array.from(document.scripts)let scriptCDN = []let scriptBlock = []children.forEach(item => {if (item.src)scripts.findIndex(s => s.src =https://www.huyubaike.com/biancheng/== item.src) < 0 && scriptCDN.push(item);elsescriptBlock.push(item.innerText)})scriptCDN 繼續(xù)通過(guò)上面方式插入到 body 里,然后通過(guò) eval 或者 new Function 去執(zhí)行 scriptBlock。因?yàn)?scriptBlock 里的代碼可能是會(huì)依賴 scriptCDN 里的插件的,所以需要在 scriptCDN 加載完成后在執(zhí)行 scriptBlock。
const loadScript = (item) => {return new Promise((resolve, reject) => {const element = document.createElement('script');for (const { name, value } of arrayify(item.attributes)) {element.setAttribute(name, value);}element.textContent = item.textContent;element.setAttribute('async', 'false');element.onload = resolveelement.onerror = rejectdocument.body.insertBefore(element)})}const runScriptBlock = (code) => {try {const func = new Function(code);func()} catch (error) {try {window.eval(code)} catch (error) {}}}Promise.all(scriptCDN.map(item => loadScript(item))).then(_ => {scriptBlock.forEach(code => {runScriptBlock(code)})})卸載插件按照上面思去處理之后,會(huì)存在一個(gè)問(wèn)題 。比如:我們添加了一個(gè) 全局的 'resize' 事件的監(jiān)聽(tīng),在跳轉(zhuǎn)其他頁(yè)面時(shí)候我們需要移除這個(gè)監(jiān)聽(tīng)事件 。
這個(gè)時(shí)候我們需要對(duì)代碼塊的格式進(jìn)行一個(gè)約束,比如像下面這樣,在初次加載時(shí)執(zhí)行 mount 里代碼,頁(yè)面卸載時(shí)執(zhí)行 unmount 里代碼 。
<script data-reload-script>DynamicPlugin.add({// 頁(yè)面加載時(shí)執(zhí)行mount() {this.timer = setInterval(() => {document.getElementById('time').innerText = new Date().toString()}, 1000)},// 頁(yè)面卸載時(shí)執(zhí)行unmount() {window.clearInterval(this.timer)this.timer = null}})</script>
經(jīng)驗(yàn)總結(jié)擴(kuò)展閱讀
- 羊肉在什么溫度下保鮮
- 弱弱的問(wèn)一下九制橄欖怎么吃啊
- 神武天降寶箱怎么做
- 兒童可以一個(gè)人乘坐高鐵嗎
- 春分下雨還會(huì)冷嗎
- 心中播下愛(ài)情種子,就會(huì)慢慢去經(jīng)營(yíng)的星座
- 感情中一味討好,完全放下自尊的星座女
- 2023年下半年開(kāi)張好日子
- 在愛(ài)的人面前才會(huì)撤下高冷面具的星座
- 海信電視怎么進(jìn)入刷機(jī)模式
