js的单线程和异步

js的单线程和异步

前言

说到js的单线程(single threaded)和异步(asynchronous),很多同学不禁会想,这不是自相矛盾么?其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。往下看,你会发现js的机制是多么的简单高效!

说说浏览器

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

图片来自popAnt 画得太好,忍不住引过来 (http://blog.csdn.net/kfanning/article/details/5768776

再说说任务队列

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

setTimeout(function(){
    console.log(time is out);
},50);

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

说回主线程

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

图片来自Philip Roberts的演讲《Help, I‘m stuck in an event-loop》非常深刻!
我们把刚才了解的概念和图中做一个对应,上文中说到的浏览器为异步任务单独开辟的线程可以统一理解为WebAPIs,上文中说到的任务队列就是callback queue,我们所说的主线程就是有虚线组成的那一部分,堆(heap)和栈(stack)共同组成了js主线程,函数的执行就是通过进栈和出栈实现的,比如图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还需要执行上面的那几个函数,所以又把这几个函数推入栈中,等到函数执行完,就让函数出栈。等到stack清空时,说明一个任务已经执行完了,这时就会从callback queue中寻找下一个人任务推入栈中(这个寻找的过程,叫做event loop,因为它总是循环的查找任务队列里是否还有任务)。

借以解释几个容易困惑的问题

  1. setTimeout(f1,0)是什么鬼
    这个语句最大的疑问是,f1是不是立刻执行?答案是不一定,因为要看主线程内的命令是否已经执行完了,如下代码:
setTimeout(function(){
    console.log(1);
},0);
console.log(2);

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

  1. Ajax请求是否异步
    了解完上文内容,我们就知道了,ajax请求内容的时候是异步的,当请求完成后,会触发请求完成的事件,然后把回调函数放入callback queue,等到主线程执行该回调函数时还是单线程的。
  2. 界面渲染线程是单独开辟的线程,是不是DOM一变化,界面就立刻重新渲染?
    如果DOM一变化,界面就立刻重新渲染,效率必然很低,所以浏览器的机制规定界面渲染线程和主线程是互斥的,主线程执行任务时,浏览器渲染线程处于挂起状态。

如何利用浏览器的异步机制

我们已经知道,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. 异步的好处
    我们直接通过一个例子对同步和异步进行对比,假设有四个任务(编号为1,2,3,4),它们的执行时间都是10ms,其中任务2是任务3的前置任务,任务2需要20ms的响应时间。下面我们做下对比,你就知道怎么实现的非阻塞I/O了。

    图片来自Soham Kaman的文章
  2. 适合的场景
    可以看出,当我们的程序需要大量I/O操作和用户请求时,js这个具备单线程,异步,事件驱动多种气质的语言是多么应景!相比于多线程语言,它不必耗费过多的系统开销,同时也不必把精力用于处理多线程管理,相比于同步执行的语言,宿主环境的异步和事件驱动机制又让它实现了非阻塞I/O,所以你应该知道它适合什么样的场景了吧!

参考内容:

http://www.sohamkamani.com/blog/2016/03/14/wrapping-your-head-around-async-programming/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
https://vimeo.com/96425312
http://blog.csdn.net/kfanning/article/details/5768776
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

转自:https://www.cnblogs.com/woodyblog/p/6061671.html

原文地址:https://www.cnblogs.com/lightpro/p/10562393.html

时间: 2024-10-09 04:19:17

js的单线程和异步的相关文章

多进程浏览器、多线程页面渲染与js的单线程

线程与进程 说到单线程,就得从操作系统进程开始说起.在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位.任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离.后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了.于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元.这里把线程比喻一个车间

Javascript的单线程和异步编程

运行时概念 下面的内容解释了一个理论上的模型.现代 JavaScript 引擎着重实现和优化了描述的几个语义. 可视化描述 栈 函数调用形成了一个栈帧. function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); 当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量.当bar调用foo时,第二个帧就被

迭代器、生成器以及利用生成器实现单线程的异步并发

1.迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素.迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁.这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件 特点: 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容 不能随机访问集合

javascript实现js脚本的的异步加载

javascript实现js脚本的的异步加载,和图片的异步加载稍有不同,script的异步加载需要借助dom,因此有了局限性 <html> <head> <meta charset="utf-8" /> <title>Javascript</title> <meta http-equiv="X-UA-Compatible" content="IE=7"> </head&

详解回调函数——以JS为例解读异步、回调和EventLoop

转自:http://blog.csdn.net/tywinstark/article/details/48447135#comments 很多人在问什么是回调?百度出来的答案基本都不正确,看了只会让人更加迷惑.下文试着用尽量简单的例子帮大家梳理清楚,因为回调并不是一句话下定义就能明白的概念,需要用一段文字像讲故事一样来说明,回调如同很多重要的计算机概念一样,它是有历史文化的,你需要知道它从哪里来,用来干什么,才能理解及在实际生产中应用. 回调,是非常基本的概念,尤其在现今NodeJS诞生与蓬勃发

Eclipse RCP中超长任务单线程,异步线程处理

转自:http://www.blogjava.net/mydearvivian/articles/246028.html在RCP程序中,常碰到某个线程执行时间比较很长的情况,若处理不好,用户体验度是很糟糕的,如用户点了一个功能按钮后,进入功能执行,发现程序长时间没有任何响应,无疑这种情况下,用户是无法忍受的.    在这种情况下我们可以通过提供进度条对话框来改善用户体验度:     1)单步执行            程序执行期间不允许用户终止,贴下程序代码段                 P

从Vue.js源码看异步更新DOM策略及nextTick

写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出.文章的原地址:https://github.com/answershuto/learnVue.在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助.可能会有理解存在偏差的地方,欢迎提issue指出,共同学

爬虫第四章 单线程+多任务异步协程

单线程+多任务异步协程: asyncio 事件循环 loop: 无限循环的对象,事件循环中最终需要将一些特殊的函数注册到该事件循环中 特殊的函数: 被ansyc关键字修饰的函数 协程: 本质上是一个对象,可以把协程对象注册到事件循环中, 任务对象:就是对协程对象进一步的封装. 绑定回调函数(即在执行完特殊函数之后执行这个回调函数):task.add_done_callback(func) - func(task) :task 参数表示的就是绑定的任务对象 - task.result() 返回的就

JS高阶---为什么说JS是单线程执行的???

大纲: [主体] (1)如何证明JS运行是单线程的? 上述代码执行顺序结果为:fn()→timeout 1111→timeout 2222 接下来对上述代码做下修改 执行结果如右图所示 接下来点击确认,关闭弹框,再往后1s执行timeoout 1111,再过1s执行timeout 2222 从这里可以看出,alert暂停了计时,点击确定后才会恢复程序执行和计时 最后总结如下 (2)为什么JS是单线程模式? . 原文地址:https://www.cnblogs.com/jianxian/p/119