省流

defer属性规定当页面已完成加载后,才会执行脚本。

defer 属性仅适用于外部脚本(只有在使用 src 属性时)

有多种执行外部脚本的方法:

  • 如果 async=”async”:脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
  • 如果不使用 async 且 defer=”defer”:脚本将在页面完成解析时执行
  • 如果既不使用 async 也不使用defer:在浏览器继续解析页面之前,立即读取并执行脚本

bug产生

在使用 Notiflix js库时,遇到了一个奇怪的问题:在使用命令:

1
Notiflix.Notify.warning('正在请求最新数据...');

刷新后通知并没有出来,而且控制台一直报错:Uncaught ReferenceError: Notiflix is not defined,没有实例化?,但是我已经引入了呀,很奇怪,先是在用关键词找找错误:

1
Notiflix Uncaught ReferenceError: Notiflix is not defined

可能是用的人少,没什么记录,正巧看到MDN,有篇文章说了ReferenceError: “x” is not defined这种未实例化的通用原因,但还是没有头绪。

复现demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="https://jsd.onmicrosoft.cn/npm/notiflix@3.2.5/dist/notiflix-notify-aio-3.2.5.min.js"></script>

</head>
<body>
<script>
Notiflix.Notify.warning('正在请求最新数据...');
</script>
</body>
</html>

报错

解决过程

后来筛选了官方的 issues 也没找到什么原因,再后来找了官方的样例,一步一步对着看,终于,在引入notiflix-notify-aio-3.2.5.min.js的时候和官方有点不一样,出现问题的demo多了个defer,后来一查,噗!js加载顺序的字段。删去defer属性就运行成功了。

PS:裂了,基础不打好,什么bug都来找你,你却不知道它是谁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://jsd.onmicrosoft.cn/npm/notiflix@3.2.5/dist/notiflix-notify-aio-3.2.5.min.js"></script>

</head>
<body>
<script>
Notiflix.Notify.warning('正在请求最新数据...');
</script>
</body>
</html>

解决

总结

js加载顺序一般是:

1、<head>xxx</head>,网页主体(body)还未加载,这里适合放一些最先执行的函数,比如一些网页监控脚本,

2、<body>xxx</body>,这里适合放一些需要和网页元素互动的脚本,例如获取p标签的值,但是脚本必须放在标签的后面

3、</body>xxx<html>,网页主体已经渲染完成,这里适合需要立即执行的命令

一般加载方式是把脚本元素放在文档体的底端(</body> 标签之前,与之相邻),这样脚本就可以在 HTML 解析完毕后加载了。此方案的问题是:只有在所有 HTML DOM 加载完成后才开始脚本的加载/解析过程。对于有大量 JavaScript 代码的大型网站,可能会带来显著的性能损耗。这也是 async 属性诞生的初衷。

浏览器遇到 async 脚本时不会阻塞页面渲染,而是直接下载然后运行。这样脚本的运行次序就无法控制,只是脚本不会阻止剩余页面的显示。当页面的脚本之间彼此独立,且不依赖于本页面的其它任何脚本时,async 是最理想的选择。

defer 属性规定当页面已完成加载后,才会执行脚本。

例子:

1
2
3
4
5
6
7

<script async src="js/script2.js"></script>

<script src="js/vendor/jquery.js"></script>

<script defer src="js/script3.js"></script>

浏览器先按照从上往下的顺序加载脚本,先遇到async,不会阻塞页面渲染,直接让子线程下载并运行该脚本,再遇到第二个加载脚本命令,没有属性,则阻塞页面渲染,下载并运行该脚本,最后等待页面加载完成,浏览器才下载运行第三个脚本。

脚本调用策略小结:

  • 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 async
  • 如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。

引用文章

https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Errors/Not_defined

https://www.runoob.com/tags/att-script-defer.html