你曾在编写原生 JS 或 JQuery 代码的时候,是不是会经常遇到 DOM 元素获取不到的问题?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="your-script.js"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>
// your-script.js
document.getElementById("app").innerHTML = "I am Paul.";
Uncaught TypeError: Cannot set property 'innerHTML' of null
    at your-script.js:1

为什么

这是因为你的代码在页面加载完成之前就已经被执行了,默认只能获取到 script 标签声明之前的内容。所以可以将 script 标签放在最后面,是平常最简单直接的办法,防止拿不到对应的 DOM 元素。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>

    <script src="your-script.js"></script>
</body>
</html>

但在某些情况下,script 标签不一定能被放在最后面,于是需要考虑到如何在页面内容加载完成后再执行代码。写过 JQuery 的同学,一定都知道 $(document).ready() 这个 API。

// JQuery
$(document).ready(function() {
  $("#app").html("I am Paul").
})

或者是原生版本:

// Vanilla
document.addEventListener("DOMContentLoaded", () => {
  document.getElementById("app").innerHTML = "I am Paul.";
})

// ! 不推荐,因为会因阻塞资源(图片等)导致长时间不会执行,就是页面所有资源加载完成之后才会执行
window.onload = function () {
  document.getElementById("app").innerHTML = "I am Paul.";
}

这里面基本上都是一个「匿名函数」封装了一波,以此实现让浏览器加载完页面内容之后才执行 script 的内容。

defer,新的帮手

然而,有一个你也许并不知道的属性,非常完美的解决这个问题。

<script src="your-script.js" defer></script>

没错,只需要在 script 标签上加一个 defer 属性,就可以让浏览器知道这个脚本需要在页面内容加载完成(不包括 onload 提到的资源阻塞)之后才执行了。完全不再需要把脚本放在最底部,或者是设置以上两个事件,就可以使用。

your-script.js 现在只需要一行代码直接执行,就可以拿到 DOM 上的内容了。

document.getElementById("app").innerHTML = "I am Paul.";

当然,相信大家并不会这样做,毕竟这样写会把变量完全公开出来,很容易被控制台获取,编写一个脚本重写代码更是「轻而易举」。实际情况去编写一段简单的代码,依然需要匿名函数的封装。

(function () {
  document.getElementById("app").innerHTML = "I am Paul.";
})();

再举个例子,Svelte 框架生成的原生代码是这样的:

var app = function () {
  ...
}();

兼容性

这个你就不用担心了,连 IE10 都能用,其他浏览器基本上都是 OK 的。

CanIUse

执行顺序

全部带上 deferscript 标签,将和一般引入的情况执行顺序相同。

<script src="kico.js" defer></script>
<script src="your-script.js" defer></script>

如果一个带上,一个不带上,执行顺序就是先执行不带 defer 的,再执行带 defer 的。

<!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>
    <!-- (2) 再执行它 -->
    <script src="your-script.js" defer></script>    
</head>
<body>
    <div id="app"></div>
    <!-- (1) 先执行 -->
    <script src="your-script-2.js"></script>
</body>
</html>