從A標(biāo)簽跳轉(zhuǎn)后JS失效的坑和填坑方法
遇到的問(wèn)題
最近在折騰我的個(gè)人博客時(shí),碰到了一個(gè)挺奇怪的問(wèn)題:從其他頁(yè)面點(diǎn)擊文章鏈接跳轉(zhuǎn)過(guò)去后,頁(yè)面上的某些JavaScript功能就“罷工”了。比如圖片燈箱點(diǎn)不開(kāi)、代碼復(fù)制按鈕沒(méi)反應(yīng)之類的。
但更奇怪的是,如果我直接在地址欄輸入文章網(wǎng)址訪問(wèn),或者刷新頁(yè)面,一切又都正常了。
讓我掉坑的代碼
這是我的博客文章列表頁(yè),看起來(lái)一切正常:
<!-- 文章列表頁(yè) --> <a href="/article/spring-boot-guide" rel="external nofollow" rel="external nofollow" >Spring Boot入門指南</a> <a href="/article/vue3-composition-api" rel="external nofollow" >Vue3 Composition API詳解</a>
點(diǎn)擊這些鏈接能正常跳轉(zhuǎn)到文章詳情頁(yè),但詳情頁(yè)里的JS功能就是不起作用。
偶然發(fā)現(xiàn)的解決方法
在排查過(guò)程中,我無(wú)意間給鏈接加了個(gè)target="_self":
<a href="/article/spring-boot-guide" rel="external nofollow" rel="external nofollow" target="_self">Spring Boot入門指南</a>
誒?就這么簡(jiǎn)單加個(gè)屬性,JS功能居然都恢復(fù)正常了!
探究原因:瀏覽器在搞什么鬼?
不加 target=“_self” 時(shí)
瀏覽器可能會(huì)嘗試“智能”地優(yōu)化頁(yè)面加載,有時(shí)會(huì)復(fù)用之前的JS上下文,或者采用特殊的緩存策略。這就導(dǎo)致新頁(yè)面雖然加載了,但JS的執(zhí)行環(huán)境沒(méi)完全重置。
加了 target=“_self” 后
相當(dāng)于明確告訴瀏覽器:“就在當(dāng)前標(biāo)簽頁(yè)里老老實(shí)實(shí)加載,別整那些花里胡哨的優(yōu)化”。瀏覽器就會(huì)走完整的頁(yè)面加載流程,JS自然就能正常執(zhí)行了。
我試過(guò)的各種解決方案
方案一:簡(jiǎn)單粗暴版(不推薦但有效)
給所有跳轉(zhuǎn)鏈接都加上target="_self":
<!-- 簡(jiǎn)單,但有點(diǎn)丑,而且總覺(jué)得治標(biāo)不治本 --> <a href="/article/123" rel="external nofollow" target="_self">查看文章</a>
這個(gè)方法雖然能解決問(wèn)題,但作為有追求的程序員,我不能接受這種“打補(bǔ)丁”式的解決方案。
方案二:優(yōu)化JS加載時(shí)機(jī)(推薦)
經(jīng)過(guò)一番折騰,我總結(jié)出了一個(gè)比較靠譜的解決方案:讓JS初始化代碼更加“耐心”一點(diǎn)。
原來(lái)的代碼可能是這樣的:
// 以前的寫法,問(wèn)題多多
document.addEventListener('DOMContentLoaded', function() {
initLightbox(); // 有時(shí)候DOM還沒(méi)完全準(zhǔn)備好
initCopyButton(); // 特別是Thymeleaf動(dòng)態(tài)渲染的內(nèi)容
});
改進(jìn)后的版本:
// 現(xiàn)在的寫法,更穩(wěn)健
function initPage() {
// 先檢查關(guān)鍵元素是否存在
const articleContent = document.querySelector('.article-content');
if (!articleContent) {
// 如果元素還不存在,等100毫秒再試
setTimeout(initPage, 100);
return;
}
// 元素存在了,開(kāi)始初始化
initLightbox();
initCopyButton();
initCommentSection();
}
// 多重保險(xiǎn):多個(gè)事件都觸發(fā)初始化
window.addEventListener('DOMContentLoaded', initPage);
window.addEventListener('load', initPage);
// 處理瀏覽器前進(jìn)/后退的情況
window.addEventListener('pageshow', function(event) {
if (event.persisted) { // 頁(yè)面是從緩存加載的
initPage();
}
});
方案三:終極穩(wěn)健版
考慮到各種邊緣情況,我最終寫了一個(gè)更加健壯的初始化方案:
// 頁(yè)面初始化管理器
const PageManager = {
initialized: false,
// 需要初始化的功能列表
features: [
{ name: 'lightbox', init: initLightbox, selector: '.article-image' },
{ name: 'copyBtn', init: initCopyButtons, selector: 'pre code' },
{ name: 'comments', init: initComments, selector: '#comment-section' }
],
// 初始化所有功能
async initialize() {
if (this.initialized) return;
console.log('開(kāi)始初始化頁(yè)面功能...');
for (const feature of this.features) {
// 等待所需元素出現(xiàn)
await this.waitForElement(feature.selector);
// 執(zhí)行初始化
feature.init();
console.log(`? ${feature.name} 初始化完成`);
}
this.initialized = true;
console.log('?? 所有頁(yè)面功能初始化完成!');
},
// 等待元素出現(xiàn)
waitForElement(selector, timeout = 3000) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
// 元素已經(jīng)存在
if (document.querySelector(selector)) {
resolve();
return;
}
// 超時(shí)檢查
if (Date.now() - startTime > timeout) {
console.warn(`?? 等待 ${selector} 超時(shí)`);
reject(new Error('等待元素超時(shí)'));
return;
}
// 繼續(xù)等待
setTimeout(check, 100);
};
check();
});
},
// 重置狀態(tài)(用于頁(yè)面重新加載時(shí))
reset() {
this.initialized = false;
}
};
// 頁(yè)面顯示時(shí)初始化
window.addEventListener('pageshow', () => {
PageManager.reset();
PageManager.initialize();
});
// 也監(jiān)聽(tīng)傳統(tǒng)的加載事件
window.addEventListener('load', () => {
PageManager.initialize();
});
開(kāi)發(fā)經(jīng)驗(yàn)總結(jié)
通過(guò)這次踩坑,我學(xué)到了幾個(gè)重要的教訓(xùn):
不要相信“DOMContentLoaded”總是靠譜的,特別是在使用模板引擎或動(dòng)態(tài)內(nèi)容時(shí)。
瀏覽器的頁(yè)面緩存機(jī)制比想象中復(fù)雜,前進(jìn)/后退、普通跳轉(zhuǎn)、刷新,每種情況都可能不同。
JS初始化代碼要有“防御性”,先檢查再執(zhí)行,避免因?yàn)樵剡€沒(méi)渲染好就報(bào)錯(cuò)。
可以考慮使用MutationObserver監(jiān)聽(tīng)DOM變化,這樣能更精確地知道什么時(shí)候該執(zhí)行初始化。
給同樣遇到這個(gè)問(wèn)題的你
如果你也遇到了類似的問(wèn)題,我建議按這個(gè)順序排查:
- 先試試加target="_self",如果能解決,說(shuō)明確實(shí)是頁(yè)面加載機(jī)制的問(wèn)題
- 檢查JS初始化時(shí)機(jī),確保代碼在DOM完全準(zhǔn)備好后才執(zhí)行
- 添加一些調(diào)試日志,看看不同跳轉(zhuǎn)方式下,JS的執(zhí)行情況有什么不同
- 考慮使用我上面的PageManager模式,能比較好地處理各種邊緣情況
前端開(kāi)發(fā)就是這樣,有時(shí)候一個(gè)看似簡(jiǎn)單的問(wèn)題,背后可能涉及瀏覽器的各種優(yōu)化策略和渲染機(jī)制。不過(guò)每一次踩坑都是學(xué)習(xí)的機(jī)會(huì),至少現(xiàn)在我更加理解頁(yè)面生命周期了。
到此這篇關(guān)于從A標(biāo)簽跳轉(zhuǎn)后JS失效的坑和填坑方法的文章就介紹到這了,更多相關(guān)A標(biāo)簽跳轉(zhuǎn)后JS失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Javascript中判斷對(duì)象是否具有屬性的5種方法分享
這篇文章主要介紹了Javascript中判斷對(duì)象是否具有屬性的5種方法分享,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08
javascript實(shí)現(xiàn)點(diǎn)擊圖片切換
這篇文章主要介紹了javascript實(shí)現(xiàn)點(diǎn)擊圖片切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
JS實(shí)現(xiàn)跟隨鼠標(biāo)立體翻轉(zhuǎn)圖片的方法
這篇文章主要介紹了JS實(shí)現(xiàn)跟隨鼠標(biāo)立體翻轉(zhuǎn)圖片的方法,涉及javascript操作圖片翻轉(zhuǎn)的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-05-05
JavaScript 進(jìn)度條實(shí)現(xiàn)代碼(Firefox等相似瀏覽器下不支持)
JavaScript實(shí)現(xiàn)的進(jìn)度條,可惜在Firefox等相似瀏覽器下不支持(遠(yuǎn)程)2009-07-07
微信小程序調(diào)用天氣接口并且渲染在頁(yè)面過(guò)程詳解
這篇文章主要介紹了微信小程序調(diào)用天氣接口并且渲染在頁(yè)面過(guò)程詳解,今天寫一個(gè)具體的例子,調(diào)用一個(gè)免費(fèi)的天氣接口的api,并且把所獲取的內(nèi)容展示在前端的界面,前端界面與 iView Weapp結(jié)合,需要的朋友可以參考下2019-06-06
js如何監(jiān)聽(tīng)input輸入事件及使用防抖封裝函數(shù)處理方法
這篇文章主要給大家介紹了關(guān)于js如何監(jiān)聽(tīng)input輸入事件及使用防抖封裝函數(shù)處理方法的相關(guān)資料,最近有一個(gè)需求,需要我們實(shí)時(shí)監(jiān)聽(tīng)input輸入框中的內(nèi)容,從而帶來(lái)更好的用戶體驗(yàn),需要的朋友可以參考下2023-07-07
js實(shí)現(xiàn)跟隨鼠標(biāo)移動(dòng)且?guī)шP(guān)閉功能的圖片廣告實(shí)例
這篇文章主要介紹了js實(shí)現(xiàn)跟隨鼠標(biāo)移動(dòng)且?guī)шP(guān)閉功能的圖片廣告,實(shí)例分析了javascript操作鼠標(biāo)事件及html元素的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02
利用JavaScript實(shí)現(xiàn)一個(gè)日期范圍選擇器
日期范圍選擇器是一個(gè)常見(jiàn)的Web應(yīng)用功能,它允許用戶選擇一個(gè)日期范圍,本文我們將使用JavaScript來(lái)實(shí)現(xiàn)這個(gè)功能,感興趣的小伙伴可以了解下2024-01-01

