在项目中有这么一些页面,需要通过 AJAX 动态载入内容,而载入的内容是 HTML 内容,并且还包含有 <script> 标签。

比如,文件 a.html 中通过 AJAX 载入 b.html 的内容

a.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Javascript Testing</title>
</head>
<body>
    <div id="partial-container"></div>
    <script>
        $(function(){
            $('#partial-container').load('./b.html');
        });
    </script>
</body>
</html>

b.html

<div>Hi, I'm partial content</div>
<script src="./foo.js" type=="text/javascript"></script>
<script type="text/javascript">
	foo();
</script>

当在浏览器中访问 a.html 时不会有任何问题。但如果我们在 b.html 中通过 <script> 标签引入的 foo.js 使用的是外部 CDN 地址。比如 https://github.com/lkebin/ajaxLoadDemo/blob/master/foo.js

b.html

...
<script src="https://github.com/lkebin/ajaxLoadDemo/blob/master/foo.js" type=="text/javascript"></script>
...

当 b.html 中使用外部 URL 地址加载 javascript 文件时,访问页面将会出现错误( Console 窗口)

Uncaught ReferenceError: foo is not defined

导致出现这个问题的原因是 .load() 会自动调用 .html 方法,而 .html() 方法会处理并执行 HTML/Javascript 内容,在加载 script 文件时不能保证执行顺序,导致 foo() 函数调用在 foo.js 加载之前就执行了。

翻看 jQuery 源码

...
if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {

	// Optional AJAX dependency, but won't run scripts if not present
	if ( jQuery._evalUrl ) {
		jQuery._evalUrl( node.src );
	}
} else {
	DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node );
}
...

所有 <script> 标签都会顺序处理,如果 <script> 标签带有 src 属性,则使用 jQuery._evalUrl 进行处理

jQuery._evalUrl = function( url ) {
	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,
		"throws": true
	} );
};

查看 jQuery._evalUrl 源码我们发现,它使用 async:false同步)请求方式请求 script 文件。

但从 jQuery API文档 中发现跨域请求不支持同步

By default, all requests are sent asynchronously (i.e. this is set to true by default). If you need synchronous requests, set this option to false. Cross-domain requests and dataType: “jsonp” requests do not support synchronous operation. Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active. As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done().

解决这个问题有2个方案

  1. 想办法避免跨域加载 script 文件,保证被 AJAX 加载的 script 代码能顺序执行
  2. 不要在需要被 AJAX 加载的页面中执行 script 代码,改为 HTML 和 Javascript 代码分开单独加载,这样可能会需要重构旧项目,但会使得程序理解起来比较清楚。