跳到主要内容

DOM-加载 JavaScript

· 阅读需 6 分钟
不如怀念
Web 前端工程师 (Web Front-end Engineer)

最后更新于 2016-05-24 22:49:00

页面的动态交互离不开 Javascript,将 js 脚本引入页面时会阻塞页面加载,在某些时候我们则可以选择异步加载 js 脚本。

引入 js 代码

通常我们会将 JavaScript 代码写在一个单独的文件中,这样做的好处是页面整洁、结构更清晰,页面更小加载速度更快,同时也易维护。实际上在页面中引入 js 代码的方法有多种,我们按需选择即可。

外部脚本文件(允许跨域)

这种方式是最常用的,但要注意的是 <script> 标签内如果再写 js 代码会被直接忽略掉(不执行)。

<script src="./js/main.js"></script>

内联脚本

该方式会生成一个文本节点,如果我们通过节点的 innerHTMLtextContent 属性改变其文本内容(代码),并不会执行新的代码。此种方式也较常用。

<script>
alert(1);
</script>

事件属性

我们可以在事件属性的值中写入一个方法引用,在事件触发时则会执行该方法。这种方法不推荐使用。

<body>
<div onclick="show()"></div>
<script>
function show() { ... }
// 作用等价形式为下
var obj = document.querySelector('div');
obj.onclick = show;
</script>
</body>

JavaScript 协议

我们可以在元素的属性值中使用 JavaScript 协议来执行相应代码,但这种方式是不推荐使用的。

<a href="javascript: false">Link</a>

加载外部 js 文件

页面在加载过程中,默认是同步加载 js 的,如果遇到 <script> 标签则会停止页面的一切解析行为,开始(下载)执行相应的 JavaScript 代码,直至执行完毕。这会造成页面阻塞,页面的显示效果可能会因此受到影响。所以说,我们应该尽可能将不需要立即执行的 js 脚本放在 <body> 标签的尾部加载,这时候所有的页面元素已加载完毕,并不会对页面产生过多影响。

延迟加载

有时候我们的 js 文件可能很大,即便是放在 <body> 尾部也加载的很慢,或者要放的更靠前一点,这样页面的显示效果会被严重影响。此时,我们可以选择将一些不是很重要的 js 文件延迟到 <html> 标签关闭前再进行下载、执行。

  • defer

exp:

<!-- 一直延迟到 </html> 前才开始加载 -->
<script defer src="./js/main.js"></script>

我们可以延迟多个脚本的加载,但并不是所有的浏览器最后都按顺序并且在 DOMContentLoaded 事件前加载它们。

异步加载

也许我们并不想将 js 文件延迟到 </html> 标签关闭前才进行加载,此时我们可以通过异步加载来更提前一些。

  • async

exp:

<!-- 开始加载,但不阻塞页面 -->
<script async src="./js/main.js"></script>

异步加载会一开始就进行 js 文件的下载,但不会阻塞当前页面的解析,至于何时加载完我们也不知道,但是一加载完就会执行相应代码,此时也不会阻塞页面的解析。同样地,我们可以异步加载多个文件,但必定不会按顺序进行加载,这是我们要注意的。而且,IE10 才开始支持该属性。

<!-- 测试加载完毕顺序 -->
<script async onload="console.log('1 加载完毕')" src="./js/1.js"></script>
<script async onload="console.log('2 加载完毕')" src="./js/2.js"></script>
<script async onload="console.log('3 加载完毕')" src="./js/3.js"></script>

动态引入,异步加载

我们可以在不使用 async 属性的情况下,动态创建 <script> 标签并插入 DOM 树,此时引入的 js 文件也会进行异步加载。

<script>
// 动态引入,异步加载,可跨域
var nScript = document.createElement('script');
nScript.src = "http://.../js/1.js";
document.body.appendChild(nScript);
</script>

**该方法可以解决跨域访问资源的问题。**同样的,多个文件动态引入均会进行异步加载,但也不会按顺序加载。

获取所有 script 节点

我们可以通过预先设置的属性获取页面中所有 <script> 标签的节点集合,然后我们可以对其进行遍历打印一些信息。

  • document.scripts

exp:

<script>
// 遍历并打印出 src
Array.prototype.slice.call(document.scripts).forEach(function(e){
console.log(e, e.src);
})
</script>

我们获取的并不是一个数组,而是一个节点集合,要使用数组的 forEach() 方法我们就先将其转换成一个数组。

结语

由于 <script> 标签的同步加载特性,若我们的代码中有进行 DOM 操作,那么放在 <head> 中将会执行失败,因为此时 <body> 中要操作的元素还未解析出来。

参考

  • 《DOM 启蒙》,Cody Lindley,陈养剑 译