在一些场景我们会动态插入script标签加载js。
譬如某个js文件不是很重要,并不是整个页面需要的脚本,可能只是某个功能需要的,这个功能可能是用户点击了某个按钮才触发,入口比较深。且和你页面本身的结构不同类,譬如你是基于react的页面,这个功能的js是jquery插件。这种js文件我一般采用动态加载方式引入。
如果你用js动态插入script,那么它什么时候执行呢,如果插入多个script,且之间有依赖关系,是否先插入的script先执行呢?
答案是:不是
demo案例
js-exec.js:
动态插入2个script
到页面中,test1.js
中定义了一个全局变量obj
,test2.js
加载完成后的onload
事件中会去使用这个变量obj
。test1.js
和test2.js
都在打印了信息方便查看执行顺序
var getReadyForEditor = () => {
console.log(obj.foo)
}
var editorJs = document.createElement("script")
editorJs.src = "./test1.js"
document.body.appendChild(editorJs)
var editorJs2 = document.createElement("script")
editorJs2.src = "./test2.js"
editorJs2.onload = getReadyForEditor
document.body.appendChild(editorJs2)
test1.js
: 控制台打印1
,并且定义了obj
变量
console.log(1)
var obj = {
foo: 'foo'
}
test2.js
: 控制台打印2。
console.log(2)
执行
通过不断刷新,发现大概率是按照test1、test2的顺序执行,但是也有一部分是先执行test2再执行test1:
由截图可见,网络请求顺序是按照script插入的顺序,先插入到dom的先请求,但是请求时间不一样,test2比test1的请求时间短,内容先返回。看现象貌似结论是:资源加载完成时执行,因此资源加载先完成的先执行
猜测
我们都知道如果是非动态插入的script,是按照在html里出现的顺序执行的,但是现在动态插入的脚本,虽然先插入的script位于html的前面,后插入的在后面,但是执行顺序却没有按这个顺序来。
是不是因为浏览器不知道在一个script标签插入后还有没有下一个要插入,所以没法按顺序执行呢?那么我们一次性插入这2个标签会怎样?
var getReadyForEditor = () => {
console.log(obj.foo)
}
var editorJs = document.createElement("script")
editorJs.src = "./test1.js"
// document.body.appendChild(editorJs)
var editorJs2 = document.createElement("script")
editorJs2.src = "./test2.js"
editorJs2.onload = getReadyForEditor
// document.body.appendChild(editorJs2)
var docFrag = document.createDocumentFragment()
docFrag.appendChild(editorJs) // Note that this does NOT go to the DOM
docFrag.appendChild(editorJs2)
document.body.appendChild(docFrag)
通过createDocumentFragment
创造文档片段,然后一次插入,这样浏览器该知道顺序了吧。
但是结果依旧没变。猜想错误!
真相
MDN文档:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
原来是因为浏览器对动态插入的script
标签,默认设置的是async
。(各浏览器有区别)
我们知道async
作用的js
脚本时没有顺序的,异步加载,加载后执行。
因此特性,所以还有个defer
,defer
是异步加载,按script
在文档中的顺序执行。
那我们的测试demo试一下,打印出来的async
果真是true
如何让动态插入的script标签按插入顺序执行
既然问题出在async上,那么创建script标签时把他设置为false就好。
var editorJs = document.createElement("script")
editorJs.src = "./test1.js"
editorJs.async = false
document.body.appendChild(editorJs)
var editorJs2 = document.createElement("script")
editorJs2.src = "./test2.js"
editorJs2.onload = getReadyForEditor
editorJs2.async = false
document.body.appendChild(editorJs2)
再观察,即使test2比test1先加载完,也会等待test1执行完在执行了~
▼
原创系列推荐
▼
5. Webpack4 入门(上)|| Webpack4 入门(下)
6. MobX 入门(上) || MobX 入门(下)
7. 59篇原创系列汇总
回复“加群”与大佬们一起交流学习~
点这,与大家一起分享本文吧~