王文健的博客笔记


请多指教

从Event Loop谈JS的运行机制——单线程-异步

写在前面

对于前端开发人员来说,熟悉JavaScript在浏览器中的运行机制,写起代码来自然也更加得心应手
,但这方面的知识比较抽象,不易理解,网上的各种讲解也深浅不一,遂对此处以我的理解做个总结。

说起JavaScript在浏览器中的运行机制就不得不提到两个概念:单线程异步,很多同学不禁会想,这不是自相矛盾么?确实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式使得js具备了异步的特性。

为什么是单线程

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

浏览器环境

js是单线程语言,浏览器只分配给js一个主线程,用来执行同步任务,但一次只能执行一个同步任务,这些同步任务任务在主线程里排队形成一个执行栈等候执行。

但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的同步任务一样,排队等待执行的话,CPU就会一直等待任务执行完毕,执行效率会非常的低,形成阻塞。所以,浏览器为这些耗时任务开辟了另外的线程,这些任务是异步执行的。所以不会阻塞

有关线程

  • 事件触发线程
  • 定时器触发线程
  • HTTP异步请求线程

异步任务——事件与回调函数

所有的异步操作都可以看做一个事件,每个事件可以关联一个回调函数

任务队列"是一个事件队列,IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。

"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

注:当其他线程中的事件达到触发条件,就会把事件放到任务队列的队尾

拿ajax举例,当有一个ajax请求时

  1. 浏览器会把该请求放到另外一个浏览器提供的线程中去执行,js引擎线程不用一直等待请求响应
  2. 当请求响应时,就会把该事件放到任务队列的队尾
  3. 当主线程的执行栈内的任务全部执行完毕后就会去任务队列中查找有没有事件,把任务队列中的第一个事件提到js引擎线程的执行栈中去执行

任务队列

事件循环

  • js分为同步任务异步任务
  • 当js脚本被执行时,从上到下执行,普通的同步任务直接在js引擎主线程执行 形成一个执行栈
  • 执行到异步任务时(如事件,定时器,http请求)把事件放到浏览器提供的另外几个事件触发线程上执行
  • 任务队列: 当另外的事件触发线程中有事件达到了执行条件,就把该事件放到任务队列的队尾
  • 当执行栈中的同步任务全部执行完毕后(js引擎线程空闲时),js引擎就会读取任务队列,如果队列中有事件就把任务队列中第一个事件关联的回调函数放到js执行栈中执行

事件循环(Event Loop)

事件循环流程.png

主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

所谓事件驱动的的实现过程主要靠事件循环完成。进程启动后就进入主循环。主循环的过程就是不停的从任务队列里读取事件。如果事件有关联的handle(也就是注册的callback),就执行handle。一个事件并不一定有callback

定时器setTimeout 与 setInterval

看代码

console.log(1);
setTimeout(function(){
    console.log(2);
},0);
console.log(3);

执行结果1 3 2

  • 定时器与事件没什么区别,当js引擎线程执行到定时器时,就把该任务放到计时器线程中计时
  • 当计时完毕后就把事件放到任务队列中,等待js主线程空闲时读取任务队列再执行该回调函数
  • 在计时完毕后只是把事件推入任务队列中,所以计时器的计时并不一定是准确的
  • PS:在W3C标准中规定最小的计时时间为4ms,如果设置的事件小与4ms会按4ms执行

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。

setTimeout 而不是 setInterval

  • setInterval是每次都精确的隔一段时间推入一个事件 但是事件的执行也需要时间,有可能上一个事件还没执行完,下一个事件已经计时完毕,会造成连续执行该事件的累积效应
  • setTimeout 模拟 setInterval

用setTimeout就会在事件执行完毕后再把下一次定时器放到事件触发线程里,所以执行的事件间隔不会小于设置的时间

(function mocking () {
  setTimeout(function() {
       console.log('1');
    mocking();
    //setTimeout(arguments.callee,500); 
  }, 500)
})()

以上就是我对JavaScript在浏览器中的运行机制的理解,若有错误欢迎指出
由于本篇文章前期所写,现经过修改,内容排版可能有些不连贯


参考内容
JavaScript 运行机制详解:再谈Event Loop
【朴灵评注】JavaScript 运行机制详解:再谈Event Loop
并发模型与事件循环
从浏览器多进程到JS单线程

未经允许不得转载:王文健的博客笔记 » 从Event Loop谈JS的运行机制——单线程-异步

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
Copyright & copy; 2018 @王文健. All rights reserved.

小编留言

本网站是作者学习路上的一个见证,记录和敦促我的学习生活,也希望借此结交更多前辈好友。记录作者在学习生活的点点滴滴,愿与你一同进步,共勉!

Copyright & copy; 2018 @王文健. All rights reserved.

感谢 WordPress DUX 提供的参考模板与设计,阿里云提供的优质云服务,使我完成本网站的制作