用JS对页面中的图片和JS文件实时更新


码海无涯系列,写给自己的注释

如何不大改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()
        }
    })
});

文章作者: Alan Work
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alan Work !
  目录