码海无涯系列,写给自己的注释
如何不大改html页面的代码,而令用户及时获得最新的图片和JS文件呢?
可以用JS给这些文件的后缀加上时间戳
以下是代码:
document.addEventListener("DOMContentLoaded", function () {
const timestamp = Date.now(); // 取时间戳
const baseURL = window.location.origin;
// 取地址,location.origin输出为“协议+域名”,一个字符串
document.querySelectorAll('script[src]:not([src*="?v="])').forEach(oldScript => {
// 遍历所有的script的非带时间戳的地址
try {
const scriptUrl = new URL(oldScript.src, baseURL);
// 将所有(相对的)JS库地址,和窗口的绝对地址拼接为新地址
if (!scriptUrl.pathname.endsWith("no.js")) return;
// 过滤掉不是no.js的地址(因为其他库不需要经常更新,只有no.js需要更新)
const newScript = document.createElement("script");
for (const {
name,
value
}
of oldScript.attributes) {
newScript.setAttribute(name, value)
}
// for..of循环遍历旧的script属性和值,并将其赋值给newScript
scriptUrl.searchParams.set("v", timestamp);
// 设置scriptUrl的v查询参数的新值为timestamp
newScript.src = scriptUrl.href;
// 把scriptUrl的链接值赋值给newScript
newScript.onerror = () => {
console.error("脚本加载失败:", scriptUrl.href);
document.head.appendChild(oldScript.cloneNode(true))
// 出错时的处理:将 oldScript 深复制一遍到DOM树上(即回退)
};
oldScript.parentNode.replaceChild(newScript, oldScript)
// 使用replaceChild替换oldScript(注意:replaceChild
// 是要在被替换的元素的父节点使用的,所以用
// oldScript.parentNode找到oldScript的父节点)
} catch (e) {
console.warn("脚本替换失败:", e)
}
});
// 下面给Img元素加时间戳
const processElements = (elements, baseURL) => {
elements.forEach(el => {
try {
if (el.tagName.toLowerCase() === 'img' && el.src) {
const urlObj = new URL(el.src, baseURL);
urlObj.searchParams.set("v", timestamp);
el.src = urlObj.href
}
} catch (e) {
console.warn("URL 解析失败:", e)
}
})
};
// 调用processElements函数,并过滤data:image/开头的base64编码的图片
processElements([...document.querySelectorAll("img[src]")].filter(img => !img.src.startsWith("data:image/")), baseURL);
// 对引入的 iframe 进行处理
document.querySelectorAll("iframe").forEach(iframe => {
const processIframe = () => {
try {
const doc = iframe.contentDocument;
// 查看 iframe 里是否有 Document 对象,如果没有停止处理
if (!doc) return;
processElements([...doc.querySelectorAll("img[src]")].filter(img => !img.src.startsWith("data:image/")), iframe.contentWindow.location.href)
} catch (e) {
console.warn("跨域 iframe 访问失败:", iframe.src)
}
};
const waitForDocumentReady = (doc, callback) => {
let timeoutId;
const checkReady = () => {
if (doc.readyState === 'complete') {
clearTimeout(timeoutId);
callback()
} else {
requestAnimationFrame(checkReady)
}
};
timeoutId = setTimeout(() => {
console.warn("iframe 加载超时", doc);
callback()
}, 30_000); // 超时设定为30秒
checkReady()
};
// 设定 iframe 的 onLoad
const onLoad = () => {
if (iframe.contentDocument) {
waitForDocumentReady(iframe.contentDocument, processIframe)
}
};
iframe.addEventListener('load', onLoad);
if (iframe.contentDocument && ['interactive', 'complete'].includes(iframe.contentDocument.readyState)) {
processIframe()
}
})
});