【易错求解】 script 标签的 async 和 defer 到底有什么区别?

问题描述

网上的知识千篇一律,比如说在多脚本执行中是否符合预期呢?我们应该选用什么呢?兼容性呢?

问题出现的环境背景及自己尝试过哪些方法

在网上查过一些资料,但是没有比较明显的例子。

本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。
阅读 1.7k
1 个回答

我们通过搜索引擎一般来说可以得到如下知识

async 和 defer 都是用于异步加载 JavaScript 脚本的属性,它们的主要区别在于脚本的执行时间。

async:表示异步加载,即脚本的加载和 HTML 文档的解析是并行进行的,当脚本加载完成后,会立即执行脚本,无论 HTML 文档是否已经解析完成。如果有多个 async 脚本,它们的执行顺序是不确定的,因为它们之间是并行加载和执行的。

defer:也表示异步加载,但是脚本的加载和 HTML 文档的解析是分开进行的。当 HTML 文档解析到 defer 脚本时,它会先将脚本下载到本地并且等待文档解析完成后再执行脚本。如果有多个 defer 脚本,它们的执行顺序是按照它们在文档中出现的顺序执行的。

因此,当需要异步加载 JavaScript 脚本时,如果脚本不依赖于 HTML 文档的解析结果,可以使用 async 属性,以尽快加载和执行脚本,减少页面的加载时间。如果脚本依赖于 HTML 文档的解析结果,应该使用 defer 属性,以保证脚本的执行顺序和文档解析的顺序一致。

然后还会得到一张图

image.png
图片来自:https://blog.csdn.net/qq_27674439/article/details/101316754

但是他们没有提供测试页面,所以我们需要一个测试 DEMO。

下面的代码不一定对,可以当成伪代码看,主要是为了好懂,真实代码可以看 stackblitz

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Async vs Defer</title>
</head>
<body>
  <h1>Async vs Defer</h1>

  <script src="async.js" async></script>
  <script src="defer.js" defer></script>
</body>
</html>
// async.js
console.log('Async script loaded at', new Date().toLocaleTimeString());

window.addEventListener('load', function() {
  console.log('Async script executed at', new Date().toLocaleTimeString());
});
// defer.js
console.log('Defer script loaded at', new Date().toLocaleTimeString());

window.addEventListener('load', function() {
  console.log('Defer script executed at', new Date().toLocaleTimeString());
});

通过这种方式可以测试 async 和 defer 属性对脚本加载和执行时间的影响,并对比它们之间的差异。

但是需要注意的是,由于测试结果可能受到多种因素的影响,如网络速度、硬件性能等,导致无法得出更准确的结果。

所以我们还需要一些其他东西的帮助,比如说 charles 或者直接用 Node 服务来模拟


我们可以使用 node 来做一个 sleep

const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    // 返回 HTML 页面
    const html = fs.readFileSync('index.html', 'utf-8');
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(html);
  } else if (req.url.startsWith('/script')) {
    // 模拟脚本执行的延迟时间
    const delay = parseInt(req.url.split('?')[1] || '0', 10);
    setTimeout(() => {
      // 返回脚本内容
      const script = fs.readFileSync(req.url.slice(1), 'utf-8');
      res.writeHead(200, {'Content-Type': 'text/javascript'});
      res.end(script);
    }, delay);
  } else {
    // 返回 404 Not Found
    res.writeHead(404);
    res.end();
  }
});

server.listen(3000, () => {
  console.log('Server listening on http://localhost:3000');
});

当然也可以 express

const express = require('express');
const fs = require('fs');

const app = express();

app.get('/', (req, res) => {
  // 返回 HTML 页面
  const html = fs.readFileSync('index.html', 'utf-8');
  res.send(html);
});

app.get('/script/:name', (req, res) => {
  // 模拟脚本执行的延迟时间
  const delay = parseInt(req.query.delay || '0', 10);
  setTimeout(() => {
    // 返回脚本内容
    const script = fs.readFileSync(req.params.name, 'utf-8');
    res.set('Content-Type', 'text/javascript');
    res.send(script);
  }, delay);
});

app.listen(3000, () => {
  console.log('Server listening on http://localhost:3000');
});

当然上面的写法是写死的,其实我们可以把所有输出都放在前端页面,

app.get('/script/:name', (req, res) => {
  // 从 URL 查询参数中获取代码、数据、响应头、状态码和延迟等参数
  const code = parseInt(req.query.code) || 200;
  const data = req.query.data || '';
  const headers = req.query.headers || '';
  const delay = parseInt(req.query.delay) || 0;

  // 设置响应头部
  for (const key in headers) {
    if (headers.hasOwnProperty(key)) {
      res.set(key, headers[key]);
    }
  }

  // 延迟执行一段时间后,发送响应主体
  setTimeout(() => {
    res.status(code).send(data);
  }, delay);
});
    <script src="/script/defer-1.js?delay=500&data=console.log('Defer Script 1: delay=500, data=console.log(\\'Defer Script 1\\')')" defer></script>
    <script src="/script/defer-2.js?delay=1000&data=console.log('Defer Script 2: delay=1000, data=console.log(\\'Defer Script 2\\')')" defer></script>
    <script src="/script/defer-3.js?delay=2000&data=console.log('Defer Script 3: delay=2000, data=console.log(\\'Defer Script 3\\')')" defer></script>
    <script src="/script/defer-4.js?delay=3000&data=console.log('Defer Script 4: delay=3000, data=console.log(\\'Defer Script 4\\')')" defer></script>

stackblitz 的 demo 地址

  1. 同步阻塞测试 sync.html 页面 。现在基本都是 SPA 的页面。如果说在new Vue 之前存在一些三方资源,是不是就会导致我们的资源开始执行时间异常? image.png
  2. async 不阻塞 乱序 async.html 页面。image.png
  3. defer 不阻塞 有序 defer.html 页面。可以看到 End 先执行了image.png
本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题