script 标签主要有三个属性
-
defer --> 立即请求文件 但不阻塞渲染引擎 等HTML解析完成之后再执行文件内容
-
async --> 立即请求文件 但不阻塞渲染引擎 文件加载完毕后阻塞渲染引擎并立即执行文件内容
-
type --> 主要是 HTML5 标准的
type = 'module'
让浏览器按照 ES6 标准将文件当作模块解析 阻塞效果默认同 defer 也可以配合 async 使用
demo
我们准备两个js文件 分别为
/**
* @file a.js
*/
console.log('module A');
setTimeout(() => {
console.log('done');
}, 2000);
/**
* @file b.mjs
*/
console.log('module B');
const nickname = await Promise.resolve('chou');
console.log(`My name is ${nickname}`);
然后我们在html中引入这两个文件
<body>
<script src="./a.js"></script>
<script src="./b.mjs"></script>
<script>
console.log('module C');
const h1 = document.createElement('h1');
h1.innerText = 'Hello Script';
document.querySelector('#container').appendChild(h1);
</script>
<div id="container"></div>
</body>
我们按顺序引入 a b 两个文件 然后在script底部 创建dom元素
这个时候 控制台直接报错了 原因直接通过报错信息就一目了然了
首先是 b.js 中 用到了 top-level-await 的语法 只在 ES6 module 中支持 所以
<script src="./b.mjs"></script>
要改成 <script src="./b.mjs" type="module"></script>
然后是 inline-script 里的 document.querySelector('#container').appendChild(h1)
在执行的时候未能找到id为container的dom元素 因为我们的dom元素在script后创建
所以这个script标签也要加上 type = 'module'
来延迟它的执行
这里要注意的是 defer
和 async
对于 inline-script 都是不会生效的 只有对于有scr属性的script标签才会生效 所以上面的场景用defer是不会生效的
改为后的 html 如下
<body>
<script src="./a.js"></script>
<script src="./b.mjs" type="module"></script>
<script type="module">
console.log('module C');
const h1 = document.createElement('h1');
h1.innerText = 'Hello Script';
document.querySelector('#container').appendChild(h1);
</script>
<div id="container"></div>
</body>
页面信息如下
此时 如果我们将 b 模块文件 改为 async 的方式
- <script src="./b.mjs" type="module"></script>
+ <script src="./b.mjs" type="module" async></script>
此时 页面信息如下
用 mdn 上的解释
对于模块脚本,如果存在
async
属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行
总结
- 为了避免
script
标签 阻塞渲染引擎 应该把script
标签放在 body 元素的最底部