使用 AJAX 加载的 JS 代码的执行顺序问题
为了提升网站性能,或者为了更好的模块化开发,我们可能用 AJAX 技术,载入部分页面内容(HTML),里面也可能包含新的 JS 逻辑代码或者外部 JS 文件。
为了讨论方便,我在这里把一个普通的网页称为 “主页面” 或者 “父页面”,用以区分 AJAX 加载的部分,那部分我称为 “子页面”。
Vue 对象不存在的问题
我们遇到了这样一个问题,父页面里,有一个弹窗,是通过 AJAX 加载的,弹窗里用了 Vue 的对象,所以弹窗里也引用了 Vue.js 的库。
此前,这个功能一直运转正常,但是,当我们引入 CDN 给全局的静态文件加速后,突然开始报错,Vue 对象不存在。就是在 AJAX 引入的子页面里,当初始化 Vue 的时候,报错 Vue 对象不存在。显示是 Vue.js
文件还没有下载并初始化完毕。
那么,为什么没使用 CDN 的时候,反倒是每次都可以等到 Vue.js 下载完毕再调用呢?使用了 CDN 反倒不可以?
问题分析
先来看一个 “子页面” 里的代码片段:1
2
3$(document).ready(function() {
const app = Vue.createApp(Main);
})
这个代码片段,在 “子页面” 里使用了 jQuery 的 document 对象的 ready 事件,那么 jQuery 是哪里来的呢?是 “父页面” 里本来就有的,所以在子页面里可以直接引用。但是这个 ready 事件为什么在 Vue 对象加载完毕之前就触发了呢?
可以认为,ready 事件在 AJAX 加载的内容和在父页面里,定义是不一样的。网上我也搜到了一些记录,就是讲,AJAX 加载的内容里,ready 会在加载的瞬间就会触发,所以,我怀疑,这个 ready 是父页面的 DOM 加载完毕后,就始终处于 ready 状态了。
所以,这么做大概率是不对的。但是这里有个问题,为什么使用 CDN 之前,这么做没问题呢?
碰巧对了。
我猜测,现在都是使用 h2 协议来连接服务器了,所以在 AJAX 下载子页面的时候,里面的脚本也在同一台服务器,也都共享了同一个连接下载,所以,本质上,造成了一种同步下载的效果。
但是换了 CDN 后,共享同一个下载连接的情况就不存在了,浏览器的行为,回到了一种通用的情况。这种情况就是,对 <script>
引入的 JS 文件,浏览器采用异步的方式下载,并且,刚才也说了 document.ready 事件是无效的。
那么最早下载完毕的子页面内嵌代码,最先执行,后果就是 Vue 对象不存在。
解决办法
知道了原因,解决办法就很显然。
第一个方法,将 Vue 的引用安排到父页面。这样,子页面可以直接引用,届时,Vue 对象肯定是存在的。
第二个方法,显示动态加载 Vue.js 库,并在回调中调用逻辑代码,也是可以的,并且就不用假设浏览器的行为等问题。1
2
3
4
5
6
7
8
9
10['a.js', 'b.js'].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
document.head.appendChild(script);
script.onload = () => {
if (src == 'a.js') {
const app = Vue.createApp(Main);
}
}
});
上面的代码片段,展示了动态载入 JS 的方式,
总结
使用 AJAX 载入 HTML 的时候,外部 JS 文件和内嵌代码的执行顺序和时机,都是要小心处理,不然容易产生 Bug。
不过,以上都是我的推测,不能当真,只是基于经验的一种推测,还需要严谨地证明。不过我时间比较紧,记录在这里,后面有时间,我要写代码来验证一下,届时再和大家分享。