JavaScript单线程的运行机制

1. js是一门单线程的语言

单线程通俗的来说就是同一时间只能做一件事情,不能同时做几件事情,与传统的后台编程语言如java一般都是多线程的可以同时做几件事情,也就是所谓的并发。 说道为什么不把js设计为多线程并发的呢。?这样不是功能更加强大么。?这是因为在js的设计之初就是作为浏览器的脚本语言,主要作用就是与用户做交互,如果设计为多线程的话,那就会带来很多复杂的同步问题了,势必会影响浏览器的交互效果,不妨假设一下若js有两个线程,A线程需要将页面信息删除,B线程需要将页面信息修改显示,那就会让前端处理逻辑变得复杂了,也就会影响与用户的交互体验了。

主要用途为和用户交互,以及操作dom,会带来很多同步上的复杂问题

所以从javascript诞生之初就被设计为单线程语言,估计这个特性在以后也不会被改变。

当然现在HTML5提出了Web Worker标准,允许javascript创建多线程,但是创建的子线程完全受主线程控制。而且子线程是不能访问DOM的,子线程的全局对象也不是window。这样当前端有一些巨大的数据计算的时候,可以充分利用多核CPU的计算能力。关于web worker后期我们会专门有一篇文章来讲述,在此不做过多说明。



2. 什么是任务队列

由于JS是一个单线程的语言,就需要一个专门来管理任务执行的队列,只有前一个任务执行完了,才会接着去执行下一个任务。但是有些任务耗费的时间很长,如果是cpu一直在忙碌在干事情,那倒也没什么,最怕的是此时cpu是处于空闲状态的。比如I/O操作,或者Ajax请求,有时候我们发出一个ajax到后端请求数据到浏览器接受到后端返回的数据,这段时间比较长。此时若是cpu一直处于空闲等待的状态的话,那岂不是会浪费很多性能。所以设计者就考虑不妨将这些需要等待的任务挂起,去执行后面的任务,等到所请求的数据返回或者I/O操作结束有了结果之后再回过头去执行刚刚被挂起的任务。

因此根据上述我们又可以将任务分为两类,一种是同步任务,一种是异步任务。我们将同步任务放在浏览器的主线程之上,只有前面一个任务执行完成之后才会执行后面的任务,异步任务是指 不进入主线程,而进入任务队列的任务。只有任务队列通知主线程,某个异步任务可以执行了,那么此时该任务才会进入主线程执行。

异步任务的执行流程:
1:有一个主线程用于执行一些同步任务,形成一个执行栈。

2:与此同时还有一个任务队列,当异步任务有了运行结果的时候,就会在任务队列中放置一个事件。

3:一旦主线程上的同步任务都执行完了,系统就会依次读取任务队列上的任务。而此时正处于“等待”状态的异步任务也就结束等待了,开始进入执行栈中,开始执行。

4:主线程不断反复的重复上面第三部。

一旦主线程上面的任务运行完之后就会从任务队列中读取新的任务继续运行。这就是javascript的运行机制,整个过程不断循环反复直至所有任务全部运行结束。

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

“任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列”,等待主线程读取。

3. 回调函数

所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。若是一个异步任务没有回调函数的话,那等异步任务返回数据后,执行栈执行什么呀。

一般而言,任务队列是一个队列的数据结构(即先进先出),排在前面位置的任务会被主线程优先的读取执行。主线程从任务队列读取的过程基本上是自动的,即当主线程上任务执行完之后,就会自动从任务队列上读取任务去执行。

4. Event Loop

主线程从任务队列循环不断读取任务的这个过程我们称之为Event Loop循环即事件循环。

下面配一张图(图片来源于阮一峰老师的博客)来帮助大家更好的理解整个JS的运行机制

在这里插入图片描述

我们可以看到,这个V8引擎在解析的时候有一个任务栈,任务栈可以调用各种外部API来,而他们又可以不断的像任务队列中增加各种事件(click,load…)只要主线程运行完之后就会读取任务队列上的回调函数。

var a = 10;
var arr = []
$.get(url,{}, callback);
arr.push(a)

上面这demo中的回调函数与向arr中增加数字a的执行顺序和下面代码的执行是完全一样的

var a = 10;
var arr = [];
arr.push(a);
$.get(url,{},callback)

因为浏览器总是会先执行主线程上的任务,之后再会执行异步任务的回调函数。即总是先执行arr.push(a)然后在执行回调函数callback,所以一般都会将这个异步请求先发送出去。然后再去执行同步任务。

5. 定时器

我们一般常用的定时器是setTimeout()和setInterval(),他么的内部机制以及传参是一模一样,区别在于前者只会执行一次,而后者会在一定的时间段之后反复执行。很多初学者都认为用定时器在设置了一定时间之后就会执行定时器中的回调函数,那了解了js的运行机制之后,严格来说并不是这样。我们以setTimeout()为例说明

  setTimeout(function () {
   console.log("timeout 2222")

 }, 2000)

 setTimeout(function () {
   console.log("timeout 1111")

 }, 1000)
 function fun() {
   console.log("fun()")

 }
 fun()
 console.log("alert之前")
 alert("alert")
 console.log("alert之后")

//顺序为fun()->alert之前->点击alert->alert之后->timeout 1111->timeout 2222
//alert暂停了当前主线程的运行,同时暂停计时,点击之后恢复程序和计时器

定时器不能保证真正定时执行,一般会延迟一丁点

js引擎的执行流程

先执行初始化代码

再执行一些特殊代码{
设置定时器

绑定监听

发送ajax请求

}

最后再执行回调偏函数代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值