js同步和异步的区别是什么(js同步和异步的理解)

js同步和异步的区别是什么(js同步和异步的理解)

Javascript 语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。对于浏览器来说,也就是无法在渲染页面的同时执行代码。单线程机制的优点在于实现起来较为简单

运行环境相对简单。缺点在于,如果中间有任务需要响应时间过长,经常会导致页面加载错误或者浏览器无响应的状况。这就是所谓的“同步模式”,程序执行顺序与任务排列顺序一致。

对于浏览器来说,同步模式效率较低,耗时长的任务都应该使用异步模式;而在服务器端,异步模式则是唯一的模式,如果采用同步模式个人认为服务器很快就会出现 12306 在高峰期的表现

一、单线程

(1)单线程的概念

如果大家熟悉java,应该都知道,java是一门多线程语言,我们常常可以利用java的多线程处理各种各样的事,比如说文件上传,下载等,而JavaScript是否也可以支持多线程呢?

答案是否定的,JavaScript是一门单线程的语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,比如说下面这段代码

// 同步代码function fun1() { console.log(1);}function fun2() { console.log(2);}fun1();fun2();// 输出12

很容易可以看出,输出会依次输入1,2,因为代码是从上到下依次执行,执行完fun1(),才继续执行fun2(),但是如果fun1()中的代码执行的是读取文件或者ajax操作,文件的读取和数据的获取都需要一定时间,难道我们需要完全等到fun1()执行完才能继续执行fun2()么?为了解决这个问题,后面我们会介绍同步和异步的概念

(2)为什么是单线程

其实,JavaScript的单线程,与它的用途是有很大关系,我们都知道,JavaScript作为浏览器的脚本语言,主要用来实现与用户的交互,利用JavaScript,我们可以实现对DOM的各种各样的操作,如果JavaScript是多线程的话,一个线程在一个DOM节点中增加内容,另一个线程要删除这个DOM节点,那么这个DOM节点究竟是要增加内容还是删除呢?这会带来很复杂的同步问题,因此,JavaScript是单线程的

二、同步任务和异步任务

(1)为什么会有同步和异步

因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务,但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验

因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务

(2)同步任务

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务

(3)异步任务

异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

function fun1() { console.log(1);}function fun2() { console.log(2);}function fun3() { console.log(3);}fun1();setTimeout(function(){ fun2();},0);fun3(); // 输出132

有了异步,就算fun2()里面是文件的读取或ajax这种需要耗时的任务,也不怕fun3()要等到fun2()执行完才能执行啦

(4)异步机制

那么,JavaScript中的异步是怎么实现的呢?那要需要说下回调和事件循环这两个概念啦

首先要先说下任务队列,我们在前面也介绍了,异步任务是不会进入主线程,而是会先进入任务队列,任务队列其实是一个先进先出的数据结构,也是一个事件队列,比如说文件读取操作,因为这是一个异步任务,因此该任务会被添加到任务队列中,等到IO完成后,就会在任务队列中添加一个事件,表示异步任务完成啦,可以进入执行栈啦~但是这时候呀,主线程不一定有空,当主线程处理完其它任务有空时,就会读取任务队列,读取里面有哪些事件,排在前面的事件会被优先进行处理,如果该任务指定了回调函数,那么主线程在处理该事件时,就会执行回调函数中的代码,也就是执行异步任务啦

单线程从从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有任务,就会等到,直到有新的任务,这就叫做任务循环,因为每个任务都是由一个事件触发的,因此也叫作事件循环

总的来说,JavaScript的异步机制包括以下几个步骤

(1)所有同步任务都在主线程上执行,行成一个执行栈(2)主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件(3)一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行(4)主线程不断的重复上面的第三步

三、异步编程

那么,怎么才能实现异步编程,写出性能更好的代码呢,下面有几种方式

(1)回调函数

回调函数是实现异步编程最简单的方法啦,回调函数我们在使用ajax时应该用的很多啦,其实在使用ajax时,我们就用到了异步

1

2

3

4

var req = new XMLHttpRequest();req.open(“GET”,url);req.send(null);req.onreadystatechange=function(){}

req.send()方法是 AJAX 向服务器发生数据,它是一个异步任务,而 req.onreadystatechange()属于事件回调,借由浏览器的HTTP请求线程发起对服务器的请求,在请求得到响应之后触发请求完成事件,将回调函数推入事件队列等待执行

其实像setTimeout,还有我们平时为元素绑定监听事件,和上面说的道理也是一样的

回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数

(2)Promise

一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心,近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API

Promise包括以下几个规范

一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)

一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换

promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致

then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用,同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象

在使用Promise时,我们需要检测一些浏览器是否支持Promise

if(typeof(Promise)===”function”) { console.log(“支持”);}else { console.log(“不支持”);}

我们可以使用new Promise进行Promise的创建

function wait(time) { return new Promise(function(resolve,reject) { setTimeout(resolve,time); });}

这个时候我们就可以使用Promise处理异步任务啦

wait(1000).then(function(){ console.log(1);})

上面这个例子表示1秒后输出1,同样的道理,我们可以使用Promise进行更加复杂的操作,关于更多的操作,就不继续说啦,关于异步的实现,其实还有其它的一些方法,但是因为上面说的这两种方法用的比较多,所以就只说上面这两种了

异步模式的四种方式:

1.回调函数 callback

所谓回调函数,就是将函数作为参数传到需要回调的函数内部再执行。

典型的例子就是发送 ajax 请求。例如:

$.ajax({ async: false, cache: false, dataType: ‘json’, url: “url”, success: function(data) { console.log(‘success’); }, error: function(data) { console.log(‘error’); } })

当发送 ajax 请求后,等待回应的过程不会堵塞程序运行,耗时的操作相当于延后执行。回调函数的优点在于简单,容易理解,但是可读性较差,耦合度较高,不易于维护。

2.事件驱动

javascript 可以称之为是基于对象的语言,而基于对象的基本特征就是事件驱动事件驱动,指的是由鼠标和热键的动作引发的一连串的程序操作。

例如,为页面上的某个

$(‘#btn’).onclick(function(){ console.log(‘click button’); });

绑定事件相当于在元素上进行监听,是否执行注册的事件代码取决于事件是否发生。优点在于容易理解,一个元素上可以绑定多个事件,有利于实现模块化;但是缺点在于称为事件驱动的模型后,流程不清晰。

3.发布 / 订阅

发布订阅模式( publish-subscribe pattern )又称为观察者模式 (Observer pattern) 。该模式中,有两类对象:观察者和目标对象。目标对象中存在着一份观察者的列表,当目标对象的状态发生改变时,主动通知观察者,从而建立一种发布 /订阅的关系。

4.promise 模式

promise 对象是 CommonJS工作组提供的一种规范,用于异步编程的统一接口。promise 对象通常实现一种 then 的方法,用来在注册状态发生改变时作为对应的回调函数。

promise 模式在任何时刻都处于以下三种状态之一:

未完成( unfulfilled )、

已完成( resolved)

拒绝( rejected )。

以 CommonJSPromise/A 标准为例,

promise 对象上的 then 方法负责添加针对已完成

和拒绝状态下的处理函数。

then 方法会返回另一个 promise 对象,

以便于形成 promise 管道,

这种返回 promise 对象的方式能够支持开发人员把异步操作串联起来

then(resolvedHandler,rejectedHandler);

resolvedHandler 回调函数在 promise 对象进入完成状态时会

触发,并传递结果;rejectedHandler 函数会在拒绝状态下调用。

Jquery 在 1.5 的版本中引入了一个新的概念叫 Deferred ,

就是 CommonJS promise A 标准的一种衍生。

可以在 jQuery 中创建 $.Deferref 的对象。同时也对发送 ajax 请求以及数据类型有了新的修改,参考JQuery API。

除了以上四种, javascript 中还可以利用各种函数模拟异步方式

搜集总结

【软件资源】MarkEditor 2.0:让文字变成你想要的样子

发表评论

登录后才能评论