如何将时间戳添加到<script>标签中的JavaScript文件URL以避免缓存。

40

我想在JavaScript文件的源路径末尾附加一个随机数或时间戳,以便每次页面重新加载时都会下载最新版本。

应该像这样:

<script type="text/javascript" src="/js/1.js?v=1234455"/>

我该如何生成并添加这个数字?这是一个简单的HTML页面,因此不能使用任何PHP或JSP相关的代码。

8个回答

58

方法一

可以通过这种方式添加许多扩展,包括异步包含和脚本延迟加载。许多广告网络和高流量网站都使用这种方法。

<script type="text/javascript">
(function(){ 
     var randomh=Math.random();
     var e = document.getElementsByTagName("script")[0];
     var d = document.createElement("script");
     d.src = "//site.com/js.js?x="+randomh+"";
     d.type = "text/javascript"; 
     d.async = true;
     d.defer = true;
     e.parentNode.insertBefore(d,e);
 })();
</script>

方法二(AJZane的评论)

一个小巧而强大的包含方式,你可以精确地看到JavaScript在哪里被触发,并且它比方法1更加简洁(到点)。

    <script>document.write("<script type='text/javascript' src='//site.com
    /js.js?v=" + Date.now() + "'><\/script>");</script>

1
甜美的代码。不需要异步或延迟加载,但我意识到你加了这些作为额外的附加功能。 - AlphaApp
1
谢谢。是的,这就是为什么我把它留下来的原因,如果您想要,可以将Math.Random替换为Date New,以免有任何句号。 - TheBlackBenzKid
27
这不是过于复杂了吗?为什么不选择一个简单的方式呢? <script>document.write("<script type='text/javascript' src='//site.com/js.js?v=" + Date.now() + "'><\/script>");</script> - AJ Zane
1
我更喜欢AJ Zane提出的方法,因为现在你可以确切地知道JavaScript文件被包含在哪个位置:就是文档写入所在的位置。 - Serge van den Oever
1
关于document.write()的注意事项:https://developers.google.com/web/updates/2016/08/removing-document-write - eflat
显示剩余3条评论

14
如果你选择使用日期或随机数附加到URI中,你将为终端用户提供了获取相同缓存文件的机会,并可能暴露意外的安全风险。显式版本控制系统则不会这样做。这是原因:
为什么“随机”和随机数都是不好的
对于随机数,你无法保证之前没有生成并为该用户提供相同的随机数。生成相同字符串的概率在较小的“随机”数字集合或提供更频繁结果的低效算法中更大。通常情况下,如果你依赖于JavaScript随机方法,请记住它是伪随机的,如果你试图依赖唯一性来修补脚本中的XSS漏洞或类似问题,则可能具有安全问题。我们不希望约翰尼在向不再受信任的第三方脚本发出AJAX调用的那一天访问黑客先生时,被服务旧的缓存和未修补的JS文件。
为什么日期或时间戳也不好,但不像随机数那么糟糕
关于将日期作为“唯一”标识符,JavaScript 将从客户端生成日期对象。根据日期格式,您意外缓存的机会可能会有所不同。仅使用日期(20160101)就允许了一整天的潜在缓存问题,因为早上的访问结果是 foo.js?date=20160101,晚上的访问也是如此。相反,如果您指定到秒(20160101000000),则最终用户调用相同 GET 参数的几率会降低,但仍然存在。
一些罕见但可能的例外情况:
  • 大多数时区每年都会重置(倒退)时钟一次

  • 由于某种原因,在重新启动时计算机会重置本地时间

  • 自动网络时间同步导致您的时钟在本地时间与服务器时间不同时向后调整几秒/分钟

  • 旅行时调整时区设置(IIS 上的宇航员每隔几分钟穿越一个时区...让我们不要降低他们的浏览体验:P)

  • 用户喜欢重置系统时钟来捣乱

为什么增量或唯一版本控制是好的 :)

对于仅限于前端的解决方案,我的建议是设置一个明确的版本,这可以由您或您的团队成员每次更改文件时简单地硬编码。手动执行与您在问题代码中所做的完全相同的操作是一个好习惯。

您或您的团队应该是唯一编辑您的JS文件的人,因此关键点不是您的文件需要每次都新鲜呈现,而是当它 更改 时需要新鲜呈现。在您的情况下,浏览器缓存并不是坏事,但您需要告诉最终用户 何时 更新。实质上,当您的文件更新时,您希望确保客户端获取更新的副本。通过这种方式,您还可以享有能够回滚到以前版本的代码而不必担心客户端缓存问题的额外优势。唯一的缺点是您需要尽职尽责确保在更新JS文件时实际更新版本号。请记住,仅因为某些事情不是自动化的,这并不意味着它一定是不良实践或不规范的。让您的解决方案适合您的情况和可用资源。

我建议使用类似语义化版本控制的表单规则,通过查看文件名(假设开发过程中没有人搞砸他们的版本编号),轻松识别向后或破坏性兼容性。除非您有奇怪的用例,否则没有理由每次都强制向客户端传递新副本。 使用本地存储在客户端自动增加版本号 如果您想要一种前端方式来自动为您生成唯一的版本号,以便您不必显式设置它,则必须实现某种本地存储方法来跟踪和自动递增文件版本。下面所示的解决方案将失去语义化版本控制的能力,并且如果用户知道如何清除本地存储,则有可能被重置。但是,考虑到您的选项仅限于客户端解决方案,这可能是您最好的选择:
<script type="text/javascript">
(function(){
    /**
     * Increment and return the local storage version for a given JavaScript file by name
     * @param  {string} scriptName  Name of JavaScript file to be versioned (including .js file extension)
     * @return {integer}            New incremented version number of file to pass to .js GET parameter
     */
    var incrementScriptVer = function(scriptName){

        var version = parseInt(localStorage.getItem(scriptName));

        // Simple validation that our item is an integer
        if(version > 0){
            version += 1;
        } else {
            // Default to 1
            version = 1;
        }

        localStorage.setItem(scriptName, version);

        return version;
    };

    // Set your scripts that you want to be versioned here
    var scripts = ['foo.js', 'bar.js', 'baz.js'];

    // Loop through each script name and append our new version number
    scripts.map(function(script){
        var currentScriptVer = incrementScriptVer(script);
        document.write("<script language='text/javascript' type='text/javascript' src='http://yoursite.com/path/to/js/" + script + "?version=" + currentScriptVer + " '><\/script>");
    });
})();

</script>

为了完整起见,如果您正在从生成“随机”编号或日期的旧系统转换到递增版本化系统,请确保您不会在新的版本控制系统中跨越任何可能随机生成的文件名。如果有疑问,在更改方法时添加前缀到GET变量上,或者干脆添加一个新的GET变量。例如:“foo.js?version=my_prefix_121216”或“foo.js?version=121216&version_system=incremental”。

通过AJAX调用和其他方法进行自动化版本控制(如果后端开发是可能的)

个人而言,我喜欢避免使用本地存储选项。如果该选项可用,则是“最佳”解决方案。尝试让后端开发人员创建一个端点来跟踪JS文件的版本号,您始终可以使用该端点的响应来确定您的版本号。如果您已经使用像Git这样的版本控制,还可以选择其中一个Dev Ops团队将您的版本控制绑定到提交版本以获得非常出色的集成。

对于RESTful GET端点的jQuery解决方案可能如下:

var script = "foo.js";

// Pretend this endpoint returns a valid JSON object with something like { "version": "1.12.20" }
$.ajax({
  url: "//yoursite.com/path/to/your/file/version/endpoint/" + script
}).done(function(data) {
    var currentScriptVer = data.version;
    document.write("<script language='text/javascript' type='text/javascript' src='http://yoursite.com/path/to/js/" + script + "?version=" + currentScriptVer + " '><\/script>");
});

4
我认为这是过度设计。 - TheBlackBenzKid

11

通过 document.createElement('script') 动态插入脚本,然后在设置URL时,可以使用 new Date().getTime() 添加额外的参数。

如果你担心你的JavaScript在脚本加载之前执行,你可以使用脚本元素的 onload 回调函数(请注意,IE需要跳过一些步骤)。


抱歉,使用 math random 会向源代码添加一个点,并且未转义的情况下会导致错误。此外,它返回一个浮点数并具有一定的重复概率(这意味着用户重新加载旧脚本是<u>可能的</u>)。 - Beat Richartz
它可以被乘以一个大数字,但是日期选项更好。无论是否附加在“?”之后,只返回翻译的文本。 - epoch

6
如果您无法使用服务器端代码,则可以使用getScript方法来完成相同的操作。
$(document).ready(function(){
  var randomNum = Math.ceil(Math.random() * 999999);
  var jsfile = 'scripts/yourfile.js?v=' + randomNum;
  $.getScript(jsfile, function(data, textStatus, jqxhr) { });
});

参考链接: http://api.jquery.com/jQuery.getScript/

(请勿忘记标记为答案。)

该网站介绍了 jQuery 中的 getScript() 方法。此方法可异步加载并执行 JavaScript 文件。


@TheBlackBenzKid:我的代码有什么问题吗?为什么你标记为无用? - Jitendra Pancholi

4

4
您可以通过纯Javascript来替换脚本的源。
// get first script
var script = document.getElementsByTagName('script')[0]
var new = document.createElement("script");
// add the source with a timestamp
new.src = 'yoursource.js?' + new Date.getTime().toString();
new.type = 'text/javascript'
script.parentNode.insertBefore(new,script);

1
我怀疑这里没有问题;因为当你执行这个操作时,比如在document.ready中,页面已经加载了script标签中指定的javascript文件。所以在文件下载后更改源是没有用的...我是对的吗? - Seeker
对不起,我有点误解问题了,我的代码也没有起作用。我已经进行了修正,像奥威尔一样。现在我要去睡觉了 :) - Beat Richartz
1
在新的 Date.getTime().toString() 上仍然缺少括号 ()。 - cthiebaud

1

如果不是字母数字,则替换正则表达式 Date.prototype.toISOString()

var today = new Date();
"MyFile_" + today.toISOString().replace(/[^\w]/g, "") + ".pdf"

MyFile_20191021T173426146Z.pdf


-2

虽然这是一篇旧文章,但这里有一个简短的代码:

<script src="../Scripts/source/yourjsname.js?ver<%=DateTime.Now.Ticks.ToString()%>" type="text/javascript"></script>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接