记一次 js 引入属性 defer 的坑
省流
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 |
|
解决过程
后来筛选了官方的 issues 也没找到什么原因,再后来找了官方的notiflix-notify-aio-3.2.5.min.js
的时候和官方有点不一样,出现问题的 demo 多了个 defer
,后来一查,噗!js 加载顺序的字段。删去 defer
属性就运行成功了。
PS:裂了,基础不打好,什么 bug 都来找你,你却不知道它是谁。
1 |
|
总结
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 |
|
浏览器先按照从上往下的顺序加载脚本,先遇到 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