一.同步和异步的概念。

同步:即按代码的顺序执行任务。

在下列代码中,按照同步概念,则是先打印1后打印2。

 console.log(1);
console.log(2);

异步:即执行一个任务的同时执行另一个任务。如果按照此概念执行上面代码,则是同时打印出1和2。

二.客户端JavaScript中代码的执行顺序

首先,不管是核心JavaScript还是客户端JavaScript都不包含任何线程机制,只有一个单线程执行模型。单线程即指脚本和事件处理程序在同一时间只能执行一个,不能同时执行,没有并发性。(HTML5定义了一种为后台线程的“Web Worker”,本人不甚了解,不做赘述)。

单线程的好处在于编程更加简单,编写代码可以确保两个事件处理程序不会同时运行,操作DOM文档也不会担心有其他线程同时修改文档。但这也意味着JavaScript脚本和事件处理程序不能运行太久,否则会降低网页的可读性,甚至导致浏览器奔溃的假象。那么,一个JS程序是怎么具体执行的呢?

JS的执行任务分为同步任务和异步任务:

同步任务:指除了异步任务之外的其他程序。

异步任务:指各种事件(比如资源载入事件中的loaded、DOMContentLoaded中的回调函数,普通事件中的click,focus,mouseover中的回调函数,window对象的定时器setInterval、setTimeout中的回调函数等等)。

过程:一个js程序执行时,首先将同步任务放入一个执行栈中,先解析同步任务和异步任务并且按顺序执行所有同步任务。当异步任务被触发时(如用户点击鼠标或者按下键盘),异步进程处理则会检测到并将其对应的异步任务转移到任务队列中。同步任务全部执行完毕后,则查看任务队列中是否有未完成的回调函数,如果有则按顺序执行。在此后期间会不断查看任务队列并不断执行,形成事件循环。请看如下过程图

三、HTML文件中script标签的执行顺序和其属性defer、async产生的影响

1.在默认情况下,HTML解析器遇到script标签时,是先执行脚本,进入脚本并按上面所述的顺序执行完代码。然后再继续解析渲染HTML页面文档,这是对于内联脚本来说。但同样的,对于一个由src属性指定外部文件的脚本来说,也是先下载并执行该脚本。也就是说,在完成改脚本的下载和执行前,其后面的文档部分都不会显现出来(实际上DOM树已经被载入,但是没被解析为DOM树)。

以下一个1996最先进的JS代码可以证明该概念(当时没有那么多的异步事件API实现异步调用,所以用如下的同步程序来实现动态添加HTML元素)

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Table of Factorials</h1>
<script>
function factorial(n) { //用来实现阶乘的函数
if(n <= 1) return n;
else return n * factorial(n-1);
} document.write("<table>"); //开始创建表格
document.write("<tr><th>n</th><th>n!</th></tr>"); //创建表头
for(var i = 0;i <= 10;i++) {
document.write("<tr><td>" + i + "</td><td>" + factorial(i) + "</td></tr>"); //输出十行表格
}
document.write("</table>"); //表格结束
document.write("Generated at " + new Date()); //输出时间戳
</script>
    <h2>Table of Factorials</h2>
</body>
</html>

以下为结果图:

可以得知,脚本的执行在默认的情况下是同步和阻塞的,这是由其单线程模型决定的。但是,对于使用src引入外部文件的script标签来说,其属性defer和async可以改变这种情况,实现异步调用

2.对于外联脚本(即由src属性引入外部js文件的脚本),其有两个属性可以改变同步状态——defer和async(只要有这两个属性之一即为异步脚本)

defer(延迟):有了该属性的外联脚本会延迟解析执行,即等待文档的载入和解析完成并可以操作时(不包括img,即可以理解为DOMContentLoaded事件触发时)才解析执行。看以下代码:

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css" media="screen">
div {
width: 100px;
height:100px;
}
</style>
</head>
<body>
<script type="text/javascript" src="console.js" defer></script>
<script type="text/javascript">
console.log(+new Date());
</script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded",function(){
console.log(+new Date());
});
</script>
</body>
</html>

console.js的代码为:

 console.log(+new Date());

运行结果截图:

可见,带有defer属性的console.js代码是第一个内联js执行后最后一个内联js执行前才执行的,印证了以上说法

async(异步):HTML解析器在遇到带有该属性的脚本时,不会中止页面文档的解析,而是一边下载该脚本一边继续后面文档的解析,一旦脚本下载解析完成则尽快停止文档解析并回去解析执行该脚本,从而避免了下载脚本时阻塞文档解析,可以凭此提高文档解析加载速度。看以下代码:

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css" media="screen">
div {
width: 100px;
height:100px;
}
</style>
</head>
<body>
<script type="text/javascript" src="console.js" async></script>
<script type="text/javascript">
console.log(+new Date());
</script>
<div> </div>
</body>
</html>

结果截图:

可以看出,原本按照默认方式应当先打印的console.js文件反而在内联script标签之后执行,可以印证上面所述。

那么,如果两个属性都同时拥有呢?这样的标签会按照什么方式执行?答案是浏览器会遵从async属性并忽略defer属性。

注意点:

1.拥有这两个属性的script标签的js文件即为异步脚本,异步脚本不能使用document.write()(因为如果用该函数会覆盖掉其对应标签解析之前的文档内容);如下面代码:

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="console.js" defer></script>
<script type="text/javascript" src="console2.js" async></script> </head>
<body> </body>
</html>

其中console.js和console.log2代码都为:

 document.write(1);

结果截图:

2.defer和async都是布尔属性,没有值,只要出现即能激活该属性

3.defer和async都只适用于外联脚本,内联脚本使用这两个属性是无效的。

4.defer能访问完整的文档树,无论其脚本位置在何处;而async必定能看到其脚本所在位置之前的文档树,但是可能或不可能访问其后面的文档内容。

四、客户端JavaScript执行顺序的总结

JS程序的执行有两个阶段

第一阶段:解析载入HTML文档的内容,并执行<script>元素里的代码(包括内联脚本和外部脚本),通常按其出现顺序执行。除非出现defer、async属性使其成为异步脚本(详情见上面defer、async属性的说明)

第二阶段:这个阶段是异步的,而且是由事件驱动的(即有用户事件才会发生)。在这个阶段,一旦用户产生事件,浏览器就会调用之前脚本中的事件处理程序函数,来响应异步发生的事件(如鼠标单击,键盘输入。此时对应的事件处理回调函数被放在了任务队列中,详情见第二部分)

我们对这两个阶段再进行详细的划分,形成一条理想的时间线:

1.Web浏览器创建一个Document对象,并开始解析渲染HTML文档,生成Element对象和Text节点放入文档中。此时,document.readystate的值为“loading”。

2.当解析HTML文档过程中遇到没有async和defer属性的脚本时,解析器停止解析文档并开始按顺序对脚本进行解析执行,此时脚本内可以便利和操作脚本之前的文档树。解析完遇到的脚本后则继续文档的解析,以此类推

3.如果遇到带有async属性的脚本,浏览器会一边下载该脚本一边继续后面文档内容的解析,当脚本下载解析完毕后立即返回解析执行该脚本。

4.当文档完成解析时,此时document.readystate的值为interactive

5.然后按照其出现顺序继续解析执行带有defer属性的脚本

6.所有的文档和脚本加载执行渲染完成后(不包括外部加载的图片多媒体文件等)浏览器触发了Document对象的DOMContentLoaded事件,标志着程序执行从同步脚本执行阶段进入到了异步事件处理事件程序执行阶段。注意此时可能还有异步任务还没执行完成。

7.此时,文档已经完全解析完成,但是有一些内容还在加载,如图片。当这些内容完全加载并且异步脚本全部载入和执行后,document.readystate的值为“complete”,并且触发window.onload事件。

8.此刻起,调用异步事件,以异步响应用户输入事件。

注意:这是一条理想的时间线。DOMContentLoaded事件和document.readystate属性大部分浏览器都支持。defer属性也被大部分浏览器支持。而async在IE9及其之前的版本是不支持的。

浅谈个人对客户端JavaScript同步、异步、执行顺序等概念的理解的更多相关文章

  1. JavaScript程序的执行顺序

    JavaScript程序的执行顺序:同步==>异步==>回调 同步是阻塞模式,异步是非阻塞模式.     同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个 ...

  2. js 异步执行顺序

    参考文章: js 异步执行顺序   1.js的执行顺序,先同步后异步 2.异步中任务队列的执行顺序: 先微任务microtask队列,再宏任务macrotask队列 3.调用Promise 中的res ...

  3. [转载]Javascript 同步异步加载详解

    http://handyxuefeng.blog.163.com/blog/static/4545217220131125022640/ 本文总结一下浏览器在 javascript 的加载方式. 关键 ...

  4. js同步、异步、回调的执行顺序以及闭包的理解

    首先,记住同步第一.异步第二.回调最末的口诀 公式表达:同步=>异步=>回调 看一道经典的面试题: for (var i = 0; i < 5; i++) { setTimeout( ...

  5. 浅谈 .NET 中的对象引用、非托管指针和托管指针 理解C#中的闭包

    浅谈 .NET 中的对象引用.非托管指针和托管指针   目录 前言 一.对象引用 二.值传递和引用传递 三.初识托管指针和非托管指针 四.非托管指针 1.非托管指针不能指向对象引用 2.类成员指针 五 ...

  6. JavaScript Alert 函数执行顺序问题

    * { color: #3e3e3e } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans ...

  7. html 和 javascript 的相关执行顺序

    1.dom 树和 js 的加载顺序 http://blog.csdn.net/jdsxzhao/article/details/44646463 2. jquery中各个事件执行顺序如下: https ...

  8. 通过实验窥探javascript的解析执行顺序

    简介 javascript是一种解释型语言,它的执行是自上而下的.但是各浏览器对于[自上而下]的理解是有细微差别的,而代码的上下游也就是程序流对于程序正确运行又是至关重要的.所以我们有必要深入理解js ...

  9. 浅谈WebService开发二(同步与异步调用)转

    上文 <http://www.dotnetgeek.cn/xuexiwebservice1.html>已经跟大家说了,如果创建一个webservice和简单的调用,本文将注重webserv ...

随机推荐

  1. POJ 3067 Japan (树状数组求逆序对)

    POJ - 3067 题意:有(1-n)个城市自上到下在左边, 另有(1-m)个城市自上到下在右边,共有m条高速公路,现求这m条直线的交点个数,交点不包括在城市处相交. 题解:先将高速公路读入,然后按 ...

  2. Codeforces 734C. Anton and Making Potions(二分)

    Anton is playing a very interesting computer game, but now he is stuck at one of the levels. To pass ...

  3. Relatively Prime Graph CF1009D 暴力 思维

    Relatively Prime Graph time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  4. yzoj P1122 阶乘 题解

    T组数据,给出N,求出N!最右边非零的数. 对于30%的数据,N <= 30,T<=10. 对于全部的数据,N <= 10^2009,T<=30. 一道数学题 解析 N!/(1 ...

  5. SqlServer数据库技巧

    1.数值转换失败提供默认值 , )) end

  6. 用webpack构建一个常规项目,好处和坏处分析

    最近项目改版,用webpack重新架构. 些许心得我会写几篇记录一下. 好处如下: 1.ES6语法用起来,babel-loader转义,各种新语法用起来. 2.import 语法写起来,webpack ...

  7. ets查询接口match、select说明

    ets:match/2用法:match(Tab, Pattern) -> [Match]返回和模式Pattern匹配的对象.一个匹配模式可能包含:绑定部分.'_'匹配任何Erlang项和匹配变量 ...

  8. 【Offer】[4] 【二维数组中的查找】

    题目描述 思路分析 Java代码 代码链接 题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数 ...

  9. Netty源码分析 (六)----- 客户端连接接入accept过程

    通读本文,你会了解到1.netty如何接受新的请求2.netty如何给新请求分配reactor线程3.netty如何给每个新连接增加ChannelHandler netty中的reactor线程 ne ...

  10. HashMap和ConcurrentHashMap的区别,HashMap的底层源码

    HashMap本质是数组加链表,根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面. ConcurrentHashMap在HashMap的基础 ...