JavaScript之异步与单线程

问题

  • Ajax请求是否异步

ajax请求内容的时候是异步的,当请求完成后,会触发请求完成的事件,而后把回调函数放入callback queue,等到主线程执行该回调函数时仍是单线程的前端

  • 一个关于setTimeout(f1,0)的笔试题

答案: 这个语句最大的疑问是,f1是否是马上执行?答案是不必定,由于要看主线程内的命令是否已经执行完了,以下代码:ajax

setTimeout(function(){
   console.log(1);
},0);
console.log(2);
复制代码

这段代码的输出结果是2,1。由于执行setTimeout后,会当即把匿名函数放到callback queue里面等待主线程的召唤,但这个时候stack里面并非空的,由于还有一句console.log(2)。等到执行完console.log(2)后,才经过event loop把匿名函数放到stack里面。因此setTimeout(f1,0)这个语句并非没有意义,若是f1是很耗时的任务,那就应该把任务放到callback queue里面,等到主程序执行完后再执行。浏览器

  • 界面渲染线程是单独开辟的线程,是否是DOM一变化,界面就马上从新渲染?

若是DOM一变化,界面就马上从新渲染,效率必然很低,因此浏览器的机制规定界面渲染线程和主线程是互斥的,主线程执行任务时,浏览器渲染线程处于挂起状态bash

前言

说到js的单线程(single threaded)和异步(asynchronous),不少同窗不由会想,这不是自相矛盾么?其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,因此它自己不多是异步的,但js的宿主环境(好比浏览器,Node)是多线程的,宿主环境经过某种方式(事件驱动,下文会讲)使得js具有了异步的属性网络

宿主环境

js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务造成一个任务队列排队等候执行,但前端的某些任务是很是耗时的,好比网络请求,定时器和事件监听,若是让他们和别的任务同样,都老老实实的排队等待执行的话,执行效率会很是的低,甚至致使页面的假死。因此,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。下图说明了浏览器的主要线程多线程

队列

刚才说到浏览器为网络请求这样的异步任务单独开了一个线程,那么问题来了,这些异步任务完成后,主线程怎么知道呢?答案就是回调函数,整个程序是事件驱动的,每一个事件都会绑定相应的回调函数,举个栗子,有段代码设置了一个定时器异步

setTimout(function(){
     console.log('time is out');
 }, 50)
复制代码

执行这段代码的时候,浏览器异步执行计时操做,当50ms到了后,会触发定时事件,这个时候,就会把回调函数放到任务队列里。整个程序就是经过这样的一个个事件驱动起来的。
因此说,js是一直是单线程的,浏览器才是实现异步的那个家伙async

主线程

js一直在作一个工做,就是从任务队列里提取任务,放到主线程里执行。下面咱们来进行更深一步的理解函数

咱们把刚才了解的概念和图中作一个对应,上文中说到的浏览器为异步任务单独开辟的线程能够统一理解为WebAPIs,上文中说到的任务队列就是callback queue,咱们所说的主线程就是有虚线组成的那一部分,堆(heap)和栈(stack)共同组成了js主线程,函数的执行就是经过进栈和出栈实现的,好比图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还须要执行上面的那几个函数,因此又把这几个函数推入栈中,等到函数执行完,就让函数出栈。等到stack清空时,说明一个任务已经执行完了,这时就会从callback queue中寻找下一我的任务推入栈中(这个寻找的过程,叫作event loop,由于它老是循环的查找任务队列里是否还有任务)oop

异步机制的使用

js一直是单线程执行的,浏览器为几个明显的耗时任务单独开辟线程解决耗时问题,可是js除了这几个明显的耗时问题外,可能咱们本身写的程序里面也会有耗时的函数,这种状况怎么处理呢?咱们确定不能本身开辟单独的线程,但咱们能够利用浏览器给咱们开放的这几个窗口,浏览器定时器线程和事件触发线程是好利用的,网络请求线程不适合咱们使用
假设耗时函数是f1,f1是f2的前置任务。

  • 利用定时器触发线程
function f1(callback){
setTimeout(function(){
    // f1 的代码
    callback();
},0);
}
f1(f2);
复制代码
  • 利用事件触发线程
$f1.on('custom',f2);  //这里绑定事件以jQuery写法为例
function f1(){
setTimeout(function(){
    // f1的代码
    $f1.trigger('custom');
},0);
}
复制代码

这种方法经过绑定自定义事件,对方法一解耦,这样能够经过绑定不一样的事件,实现不一样的回调函数,但若是应用这种方法过多,不利于阅读程序 使用异步的场景

异步的好处

咱们直接经过一个例子对同步和异步进行对比,假设有四个任务(编号为1,2,3,4),它们的执行时间都是10ms,其中任务2是任务3的前置任务,任务2须要20ms的响应时间。下面咱们作下对比,你就知道怎么实现的非阻塞I/O了

适合的场景

一、在可能发生等待、有阻塞程序的状况
一、定时任务:setTimeout,setInterval
二、网络请求:ajax请求,动态img加载
三、事件绑定

console.log(100);
setTimeout(function () {
    console.log(200);
}, 1000);
console.log(300); // 100,300,200
复制代码
相关文章
相关标签/搜索