【jQuery】 append script 時 cache 失效
問題:
會發現這個 jQuery 的行為起源於 CDN 上面某隻 JS 檔案的 cache hit ratio 異常的低,而原因就在於當我們使用 jQuery append script 時,jQuery 會自動在 url 後帶入亂數來讓瀏覽器以及 CDN 的 cache 都失效。
所以這篇文章就是要解釋此行為的亂數來自哪裡以及如果我們不希望自動加亂數的解決方案。
解答:
首先我並沒有在 jQuery api 文件的 append 頁面找到任何相關的資料,所以我只能藉由原始碼來找原因。我查的是 3.6.0 (commit 0cc1ad6) 版本的原始碼。我們可以在 src/manipulation.js 找到 append 的定義。
// ... 省略 ...
append: function() {
return domManip( this, arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
} );
},
// ... 省略 ...
|
可以看到 append 內呼叫了 domManip。
// ... 省略 ...
function domManip( collection, args, callback, ignored ) {
// ... 省略 ...
// Evaluate executable scripts on first document insertion
for ( i = 0; i < hasScripts; i++ ) {
node = scripts[ i ];
if ( rscriptType.test( node.type || "" ) &&
!dataPriv.access( node, "globalEval" ) &&
jQuery.contains( doc, node ) ) {
if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) {
// Optional AJAX dependency, but won't run scripts if not present
if ( jQuery._evalUrl && !node.noModule ) {
jQuery._evalUrl( node.src, {
nonce: node.nonce || node.getAttribute( "nonce" )
}, doc );
}
} else {
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
}
}
}
// ... 省略 ...
}
// ... 省略 ...
|
domManip 內則對 script 標籤使用 _evalUrl 來直接取得 scripts。
// ... 省略 ...
jQuery._evalUrl = function( url, options, doc ) {
return jQuery.ajax( {
url: url,
// Make this explicit, since user can override this through ajaxSetup (#11264)
type: "GET",
dataType: "script",
cache: true,
async: false,
global: false,
// Only evaluate the response if it is successful (gh-4126)
// dataFilter is not invoked for failure responses, so using it instead
// of the default converter is kludgy but it works.
converters: {
"text script": function() {}
},
dataFilter: function( response ) {
jQuery.globalEval( response, options, doc );
}
} );
};
// ... 省略 ...
|
_evalUrl 內則又呼叫 ajax。
這時候我們再來看看 jQuery 的 ajax 官方文件。其中有一個參數叫做 cache,他的描述如下:
cache (default: true, false for dataType 'script' and 'jsonp') Type: Boolean If set to false, it will force requested pages not to be cached by the browser. Note: Setting cache to false will only work correctly with HEAD and GET requests. It works by appending "_={timestamp}" to the GET parameters. The parameter is not needed for other types of requests, except in IE8 when a POST is made to a URL that has already been requested by a GET. |
所以事實上我們遇到的這個亂數就是描述中的,會在 GET 參數中加上的 "_={timestamp}"。看似找到原因了,但是等等,_evalUrl 內給的 cache 參數值是 true,怎麼會產生 timestamp 呢?
git blame 一下我們可以發現這個 cache 參數值有被修正過 (commit 15f4dec)。在這之前的版本,cache 用的是預設值,也就是 false for dataType 'script' and 'jsonp'。
不過要小心的是該 commit 雖然在 2.2 版本前就被 merge,但是因為會改變行為等歷史因素,在第三版才正式生效。
知道原因後想要解決就不難了。解決方法就是升級 jQuery 至 3.0.0 (含) 以上或是依照原始碼中的註解:
// Make this explicit, since user can override this through ajaxSetup (#11264)
|
用 ajaxSetup 設定複寫 _evalUrl 給 ajax 的參數值。
實際範例一:jQuery 2.2.4:
<!DOCTYPE html>
<html>
<head>
<title>jQuery append script</title>
<meta charset="UTF-8">
<script
src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
</head>
<body>
<script>
let scriptTag = document.createElement('script');
scriptTag.src = './index.js';
$('body').append(scriptTag);
</script>
</body>
</html>
|
ver 2.2.4 有亂碼。
<!DOCTYPE html>
<html>
<head>
<title>jQuery append script</title>
<meta charset="UTF-8">
<script
src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
</head>
<body>
<script>
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
let cacheJsUrl = `${window.location.origin}/index.js`
if (options.url === cacheJsUrl) {
options.cache = true;
}
});
let scriptTag = document.createElement('script');
scriptTag.src = './index.js';
$('body').append(scriptTag);
</script>
</body>
</html>
|
ver 2.2.4 使用 ajaxSetup 針對特定 url 開啟 cache 後無亂碼。
實際範例二:jQuery 3.6.0:
<!DOCTYPE html>
<html>
<head>
<title>jQuery append script</title>
<meta charset="UTF-8">
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
</head>
<body>
<script>
let scriptTag = document.createElement('script');
scriptTag.src = './index.js';
$('body').append(scriptTag);
</script>
</body>
</html>
|
ver 3.6.0 原生就開啟 cache,不會有亂碼。
參考資料:
[1] jQuery git repository
https://github.com/jquery/jquery
[2] jQuery 文件 - ajax
https://api.jquery.com/jquery.ajax/
[3] jQuery git repository - issues/1887 data-URI scripts insertion
https://github.com/jquery/jquery/issues/1887
[4] jQuery git repository - issues/2723 2.2 Release progress
https://github.com/jquery/jquery/issues/2723
留言列表