详解defer和async的原理及应用

deferasyncscript标签的两个属性,用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。 
在介绍他们之前,我们有必要先了解一下页面的加载和渲染过程: 
1. 浏览器通过HTTP协议请求服务器,获取HMTL文档并开始从上到下解析,构建DOM; 
2. 在构建DOM过程中,如果遇到外联的样式声明和脚本声明,则暂停文档解析,创建新的网络连接,并开始下载样式文件和脚本文件; 
3. 样式文件下载完成后,构建CSSDOM;脚本文件下载完成后,解释并执行,然后继续解析文档构建DOM 
4. 完成文档解析后,将DOM和CSSDOM进行关联和映射,最后将视图渲染到浏览器窗口 
在这个过程中,脚本文件的下载和执行是与文档解析同步进行,也就是说,它会阻塞文档的解析,如果控制得不好,在用户体验上就会造成一定程度的影响。 
所以我们需要清楚的了解和使用deferasync来控制外部脚本的执行。 
在开发中我们可以在script中声明两个不太常见的属性:deferasync,下面分别解释了他们的用法: 
defer:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。 
async:HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。 
为了演示脚本的执行情况,进而介绍这两个属性的作用,我们先来搭建一个简单的服务器: 
 
如图所示,我们创建一个app目录,用于放置一些简单的Web资源,同时创建了一个简易的Node服务器server.js,其代码如下:

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

var typeMapping = {
    'html': 'text/html',
    'js'  : 'text/javascript',
    'css' : 'text/css',
    'ico' : 'image/x-icon'
};

var getResourceExtension = function(req) {
    var url = req.url;
    var lastIndexOfDot = url.lastIndexOf('.');

    if (lastIndexOfDot === -1) return 'text/plain';
    return url.substring(lastIndexOfDot + 1);
};

var respondResourceToClient = function(req, res) {
    //read the reource and respond via 'pipe'
    fs.createReadStream(req.url.replace(/^\//, '')).pipe(res);
};

var server = http.createServer(function(req, res) {

    console.log('requesting url: ', req.url);

    var extension = getResourceExtension(req);

    res.writeHead(200, {'Content-Type': typeMapping[extension]});

    var delay = function(time) {
        setTimeout(function() {
            respondResourceToClient(req, res);
        }, time || 0);
    };

    if (extension === 'html' || extension === 'css') {
        delay(0);
    } else if (extension === 'js') {
        delay(1000);
    } else {
        res.end('');
    }
});

server.listen(3000);

console.log('listening at port 3000...');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

从上面的代码我们可以看出,当服务器接收到请求之后,会判断请求资源是否为JS,如果是则延迟1s后返回对应的资源。 
启动这个服务很简单,只需执行node server.js即可,然后就可以在浏览器中输入http://localhost:3000/app/index.html访问主页了,现在我们来看看index.html中的内容:


<html>
    <head>
        <title>defer & asynctitle>
        <link rel="stylesheet" type="text/css" href="css/main.css">
        <script type="text/javascript" src="js/1.js">script>
    head>
    <body>
        <div class="text">Hello Worlddiv>
        <script type="text/javascript" src="js/2.js">script>
    body>
html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这个HTML文档中,我们先在head中引用了一个外部的脚本文件js/1.js,然后在我们要显示的Hello World后面又引用了一个js/2.js,它们的内容都很简单,就是弹出对应的标示信息:

// js/1.js
alert(1);

// js/2.js
alert(2);
  • 1
  • 2
  • 3
  • 4
  • 5

下面我们就来访问主页,看看会发生些什么: 

从图中可以看到,渲染的过程的确是自上而下,同步进行的,也就是说遇到外部的脚本,就得暂停文档的解析,下载并且解释执行,这种方式是阻塞的,会造成网页空白的现象。 
现在稍微修改一下代码,将head中的script标签加上defer属性,然后也稍微改动一下两个JS文件:


<html>
    <head>
        <title>defer & asynctitle>
        <link rel="stylesheet" type="text/css" href="css/main.css">
        
        <script type="text/javascript" src="js/1.js" defer>script>
    head>
    <body>
        <div class="text">Hello Worlddiv>
        <script type="text/javascript" src="js/2.js">script>
    body>
html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
// js/1.js
console.log(1);

// js/2.js
console.log(2);
  • 1
  • 2
  • 3
  • 4
  • 5

再次访问index.html,我们会在控制台中看到下面的执行顺序: 
 
显而易见,1.js被延后致至文档解析完成后执行了,它的执行顺序比body中的

你可能感兴趣的:(HTML5)