在页上有两个script标签,第一个script标签内动态创建了一个script,去加载一个config.js文件,第二个script标签内,需要用到动态加载的config。如下:
config.js
window.config={
appName:'name',
appId:'appId'
}
index.html
//第一个script
<script type="text/javascript">
(function () {
var script=document.createElement('script');
//设置为同步加载
script.async=false;
script.src='config.js';
document.head.appendChild(script);
//此时head内有了这个script标签,
//它会去加载config.js
//因为设置的是同步;async=false;
//此时程序应该停在此处,等待config.js的加载执行
})();
</script>
//第二个script
<script>
(function () {
var config=window.config;
//js的加载执行是同步的,
//按理说,程序执到这里时,动态加载的config.js文件已经加载执行完毕
//window.config应该是有数据的
//但实际上此时config为undefined
//也就是说动态加载的js文件,并没有阻塞其后的代码执行
//这是为什么??
console.log(config);//undefined
})();
</script>
为什么在第二个script标签内读取window.config时,却是undefined??
猜一下应该是这样的。
浏览器开始解释代码:
发现script1,执行script1(丢了个config.js到head里)
发现script2,执行script2
解释完毕
...config.js加载成功,执行config.js
因为前面加载到head时文件未下载执行,而进行了下一个代码片段,异步问题。
因为异步啊
执行第一段时主线程并不等待加载完成,而是执行完后立刻执行第二段
因为你创建的新script加载是异步的,这个新下载也并不会阻塞浏览器往下执行。
可以往全局挂一个config.js加载成功的标志,然后在第二个script中检测这个标志。
<script type="text/javascript">
(function(){
var script = document.createElement('script');
script.async = false;
script.src = "config.js";
document.head.insertBefore(script,document.head.firstChild);
script.onload = function(){
window.configReady = true;
console.log('config.js done');
}
})();
</script>
<script type="text/javascript">
(function(){
function checkConfig(callback){
var timer = setInterval(function(){
if(window.configReady){
clearInterval(timer);
var config = window.config;
if(callback && typeof callback === "function"){
callback(config);
}
}
},30);
}
checkConfig(function(config){
console.log(config);
})
})();
</script>
啊,那纯粹是因为下载的异步性
如果用 script.text 就没这问题了
至于为什么 document.write 可以,那是因为浏览器会选择下载完再继续解析 HTML
现代浏览器支持了对script的async``和
defer`
属性的支持,提高浏览器对HTML页面的解析速度和渲染呈现的速度。这2个属性只对设置了scr属性的外部脚本有效,对内联脚本(没有设置src属性的script)无效。
没有设置async
或defer
属性的脚本按其在html中出现的顺序阻塞式的执行-也就是浏览器会暂停对html页面元素的解析,等script脚本执行完毕后才能继续解析,在解析完成后触发DOMContentLoaded事件。
设置有async
/defer
属性的script不会阻塞对HTML标签的解析,HTML标签的解析继续进行;设置了async
的script将会并发异步的去下载对应的外部脚本文件(可能从本地缓存中获取),下载完成后,通过事件通知机制通知浏览器可以执行进程,事件调度器会调度执行脚本,按下载完成的时间的先后顺序,依次执行。defer
的script将会并发异步的去下载对应的外部脚本文件(可能从本地缓存中获取),下载完成后不会马上执行,要等到HTML解析完成后,按脚本出现的顺序依次执行,这个过程中多个脚本之间是顺序执行。async
和defer
同时指定的情况下,以defer
的方式处理script的执行
<script>脚本执行是在html解析过程中发生的,只有在在脚本执行了document.write操作时,页面解析器将会解析其中的html内容如果有脚本并马上执行脚本~~
//writDoc.js
document.open();
document.write("<script src='config.js'></script><h1>Out with the old - in with the new!</h1>");
document.close();
//html
<!DOCTYPE html>
<html>
<head>
<script src="writeDoc.js"></script>
<script>
console.log(window.config);
</script>
</head>
<body>
<p>Some original document content.</p>
</body>
</html>
而script脚本有2种类型:parser-inserted scripts
我们经常遇到的出现在HTML中以<script>方式出现,我们姑且称之为解析型脚本
<script src="config.js"></script>
<script>
console.log(window.config);
</script>
script-inserted scripts
通过JS代码动态添加的script脚本,动态型脚本,不涉及页面标签的解析
var script=document.createElement('script');
//设置为同步加载
script.async=false;
script.src='config.js';
document.head.appendChild(script);
这2种类型的script脚本,在async
方式的处理方式有些差异
动态型脚本在插入到DOM中后,即使马上从DOM中删除,也不影响脚本的存在:
A: 如果没有设置async属性并设置src属性,那么JS解析器就其当做async=true处理,此脚本将异步加载处理;
//config.js
window.config={
appName:'name',
appId:'appId'
}
//第一个script
<script type="text/javascript">
(function () {
var script=document.createElement('script');
script.src='config.js';
document.head.appendChild(script);
document.head.removeChild(script);//可以马上从DOM中删除刚插入的script元素,但是浏览器依然认为其存在,依然会去加载执行
})();
</script>
//第二个script
<script>
(function () {
var config=window.config;
console.log(config);//因为config.js下载执行事件不可预知,所以结果不可预知
})();
//获取设置一定的超时或许可以:)
setTimeout(function(){
var config=window.config;
console.log(config);
},100);
</script>
B: 如果没有设置src属性,那么无论是否设置async属性,通过为其text属性设置脚本代码的方法,那么动态添加的脚本被马上执行-可以认为是当前脚本一部分(实际不是,作用域不同);这个也是jquer的ajax加载执行外部JS脚本的方式。
//第一个script
<script type="text/javascript">
(function () {
var script=document.createElement('script');
//内联脚本
script.text="window.config={"+
"appName:'name',"+
"appId:'appId'"+
"};";
document.head.appendChild(script);
document.head.removeChild(script);//可以马上从DOM中删除刚插入的script元素,但是浏览器依然认为其存在,依然会去加载执行
})();
</script>
//第二个script
<script>
(function () {
var config=window.config;
console.log(config);//正确输出你期望的值
})();
</script>
c: 如果设置src属性并设置async=false,那么次动态脚本将被同步化处理,但是其执行时机不是暂停当前脚本的执行,而是等当前页面的解析工作完成后。多个async=false脚本按其插入的次序顺序执行。
//config2.js
var config=window.config;
console.log(config);//
//第一个script
<script type="text/javascript">
(function () {
var script=document.createElement('script');
script.async=false;
script.src='config.js';
document.head.appendChild(script);
document.head.removeChild(script);
var script2=document.createElement('script');
script2.async=false;
script2.src='config2.js';
document.head.appendChild(script2);
document.head.removeChild(script2);
//config1.js先于config2.js执行,并在end of boby后面,也就是在当前页面的所有的script的后面执行
})();
</script>
<body>
<script>
console.log('end of boby');
</script>
</body>