一.同步和异步的概念。

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

在下列代码中,按照同步概念,则是先打印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文件的脚本),其有两个属性可以改变同步状态——deferasync只要有这两个属性之一即为异步脚本

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. CCPC 网络赛

    array 做法 比赛中的表现..... 已经无法言语形容了. 题意是,查询前缀中大于某个数字的 mex,在线. 一下把问题转化为偏序问题.... 带修主席树?????这下好,直接一箭穿心,武将被移除 ...

  2. Codeforces Round #379 (Div. 2) E. Anton and Tree 缩点 树的直径

    传送门 题意: 这道题说的是在一颗有两种颜色的树上,每操作一个节点,可以改变这个节点颜色和相邻同色节点的颜色.问最少操作次数,使得树上颜色相同. 思路: 先缩点,把相同的颜色的相邻节点缩在一起.再求出 ...

  3. Codeforces 939 D Love Rescue

    Love Rescue 题意:Valya 和 Tolya 是一对情侣, 他们的T恤和头巾上都有小写字母,但是女朋友嫌弃男朋友上T恤上的字不和她的头巾上的字一样,就很生气, 然后来了一个魔法师, 它可以 ...

  4. Springboot2.x 自动创建表并且执行初始化数据

    1.使用springboot jdbc初始化数据库 项目结构 schema.sql drop table if exists user; create table user (id bigint(20 ...

  5. 007 Python程序语法元素分析

    目录 一.概述 二.程序的格式框架 2.1 代码高亮 2.2 缩进 2.3 注释 2.4 缩进.注释 三.命名与保留字 3.1 变量 3.2 命名 3.3 保留字 3.4 变量.命名.保留字 四.数据 ...

  6. 011 实例2-Python蟒蛇绘制

    目录 一."Python蟒蛇绘制"问题分析 1.1 Python蟒蛇绘制 二."Python蟒蛇绘制"实例编写 三.运行效果 3.1 程序关键 四." ...

  7. [大数据学习研究] 3. hadoop分布式环境搭建

    1. Java安装与环境配置 Hadoop是基于Java的,所以首先需要安装配置好java环境.从官网下载JDK,我用的是1.8版本. 在Mac下可以在终端下使用scp命令远程拷贝到虚拟机linux中 ...

  8. JAVA父类的静态方法能否被子类重写?

    静态: 在编译时所分配的内存会一直存在(不会被回收),直到程序退出内存才会释放这个空间,在实例化之前这个方法就已经存在于内存,跟类的对象没什么关系.子类中如果定义了相同名称的静态方法,并不会重写,而应 ...

  9. rpyc + plumbum 实现远程调用执行shell脚本

    rpyc可以很方便实现远程方法调用, 而plumbum则可以实现在python中类似shell的方式编码: 具体实现代码如下: Server.py import rpyc from rpyc.util ...

  10. 通过js获取tinymce4.x的值

    问题的引出: 在使用过程中,用传统的js的方法判断tinymce所选textarea(下面直接称textarea)的值是会出现这样的问题的: 在已有输入内容时,首次提交的时候,依然会弹出js写的警告提 ...