JavaScript异步精讲,让你更加明白Js的执行流程!

clipboard.png

问题点

  • 什么是单线程,和异步有什么关系
  • 什么是 event-loop
  • jQuery的Deferred
  • Promise 的基本使用和原理
  • async/await(和 Promise的区别、联系)

1、什么是单线程,和异步有什么关系

单线程- 只有一个线程,只能作一件事
缘由-避免DOM渲染的冲突
解决方案-异步
1) 单线程- 只有一个线程,只能作一件事
基础事例
// 循环运行期间,JS执行和DOM 渲染暂时卡顿javascript

var i, sum = 0;
for (i = 0; i<100000000000; i++) {
  sum += i;
}
console.log(sum);

// alert不处理,JS执行和DOM 渲染暂时卡顿
console.log(1);
alert('hello');
console.log(2)

2) 缘由 - 避免DOM渲染的冲突
浏览器须要渲染DOM
JS能够修改DOM结构
因此JS执行的时候,浏览器 DOM 渲染会暂停
两段JS也不能同时执行(都修改 DOM 就冲突了)
webworker支持多线程,可是不能修改访问 DOM
3) 解决方案 - 异步
基础事例java

console.log(100)

setTimeout(function () {
  console.log(200); // 反正1000ms以后执行
}, 1000);           // 先无论他,先让其它 JS 代码运行

console.log(300);
console.log(400);

4) 异步 - 存在的问题
问题一:没有按照书写方式执行,可读性差
问题二:callback中不容易模块化web

2、什么event-loop

文字解释
事件轮询,JS实现异步的具体解决方案
同步代码,直接执行
异步函数先放在异步队列中
待同步函数执行完毕,轮询执行异步队列函数
例子分析
例子一:ajax

clipboard.png

先看左下角,是咱们要执行的代码,第一个是延迟100毫秒打印1,第二个是延迟0,打印2,按照上面讲的,同步代码先执行,异步代码放在异步队列中,最后执行完同步在执行异步队列的函数,执行第一个函数的时候,setTimeout是个异步函数,那咱们是否是应该当即放在右侧的队列中呢?答案是否认的,由于它有一个延时,咱们须要100毫秒以后才能放进去,执行第二个setTimeout函数,因为没有延时,因此会被放进异步队列中,最后一个是直接 在主进程,因此 直接执行打印,等同步执行完以后,马上看异步队列中,这时候只有第二个setTimeout,执行完以后,在去看异步队列有没有,由于100这毫秒对于计算机来讲,是一个至关长的时间,因此js引擎会一直轮询异步队列中有没有可执行函数,直接100毫秒以后第一个setTimeout被放进异队列中,而后才执行。数组

例子一:promise

clipboard.png

上图流程跟例子一同样,这边咱们疑惑的是,当咱们执行 ajax 的时候,里面的success 是异步函数,它要何时被放进异步队列中呢?
固然是请求成功的时候被放进异步队列中,但咱们不知道是 大于100毫秒仍是小于,因此打印结果有两种状况,先打印d c b a,或者打印 d c a b。浏览器

3、是否用过jQuery的Deferred
jQuery 1.5的变化 -1.5以前服务器

jQuery 1.5的变化 -1.5以后多线程

clipboard.png

jQuery 1.5的变化
没法改变JS异步和单线程的本质
只能从写法上杜绝 callback 这种形式
它是一种语法糖形式,可是解藕了代码
很好的体现:开放封装原则异步

1) 什么是deferred对象?
开发网站的过程当中,咱们常常遇到某些耗时很长的javascript操做。其中,既有异步的操做(好比ajax读取服务器数据),也有同步的操做(好比遍历一个大型数组),它们都不是当即能获得结果的。
一般的作法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

可是,在回调函数方面,jQuery的功能很是弱。为了改变这一点,jQuery开发团队就设计了deferred对象。
简单说,deferred对象就是jQuery的回调函数解决方案。

2) deferred应用
咱们来看一个具体的例子。假定有一个很耗时的操做wait:

var wait = function() {
var tasks = function() {

alert('执行完毕');

};
setTimeout(tasks,5000);
}

咱们为它指定回调函数,应该怎么作呢?

你可能会使用$when()

$.when(wait())
 .done(function(){
   alert('成功!')
 })
 .fail(function() {
   alert('出错!');
 })

可是,这样写的话,done()方法会当即执行,起不到回调函数的做用。缘由在于$.when()的参数只能是deferred对象,因此必须对wait()进行改写:

var dtd = $.Deferred(); //新建一个deferred

var wait = function (dtd) {
  var tasks = function () {
    console.log('执行完毕');
    dtd.resolve(); //改变deferred对象执行状态
  };

  setTimeout(tasks, 5000);

  return dtd;
}

如今,wait()函数返回的是deferred对象,这就能够加上链式操做了。

$.when(wait(dtd))

.done(function(){ alert("成功!"); })

.fail(function(){ alert("出错!"); });

更多内容可参考这里

4、Promise 的基本使用和原理
1) 什么是 Promise
一人Promise 对象表明一个目前还不可用,可是在将来的某个时间点能够被解析的值。它容许你以一种同步的方式编写异步代码。Promise的出现,本来是为了解决回调地狱的问题。全部人在讲解Promise时,都会以一个ajax请求为例,此处咱们也用一个简单的ajax的例子来带你们看一下Promise是如何使用的。

ajax请求的传统写法:

getData(method, url, successFun, failFun){
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open(method, url);
  xmlHttp.send();
  xmlHttp.onload = function () {
    if (this.status == 200 ) {
      successFun(this.response);
    } else {
      failFun(this.statusText);
    }
  };
  xmlHttp.onerror = function () {
    failFun(this.statusText);
  };
}

改成promise后的写法:

getData(method, url){
var promise = new Promise(function(resolve, reject){

var xmlHttp = new XMLHttpRequest();
xmlHttp.open(method, url);
xmlHttp.send();
xmlHttp.onload = function () {
  if (this.status == 200 ) {
    resolve(this.response);
  } else {
    reject(this.statusText);
  }
};
xmlHttp.onerror = function () {
  reject(this.statusText);
};

})
return promise;
}

getData('get','www.xxx.com').then(successFun, failFun)
很显然,咱们把异步中使用回调函数的场景改成了.then()、.catch()等函数链式调用的方式。基于promise咱们能够把复杂的异步回调处理方式进行模块化。

2)Promise的原理分析
其实 promise 原理提及来并不难,它内部有三个状态,分别是 pending, fulfilled 和 rejected。

peding 是对象建立后的初始状态,当对象fulfill(成功)时变为 fulfilled, 当对象 reject(失败)时变为 rejected。且只能从 pending 变为 fulfilled 或 rejected,而不能逆向或或从 fulfilled 变为rejected,从 rejected变为 fulfilled。如图所示:

clipboard.png

阅读原文

本文为云栖社区原创内容,未经容许不得转载

相关文章
相关标签/搜索