javascript的运行机制

javascript引擎是基于事件驱动单线程执行的。单线程可以理解为过安检,每次只能检查一个,后面的只能排队等待,直到前面的检查完以后才能到你。事件驱动可以理解为比如点击了某个按钮(产生什么事件),然后电脑执行什么操作(即调用什么函数)

js引擎一直等待着任务队列的任务到来,然后加以处理,浏览器无论什么时候都只有一个JS引擎在运行程序,即 主线程。这种同一时间只能做一件事,也常被称为“阻塞性执行”。

单线程执行起来比较方便,执行环境相对单纯。但带来的影响就是当执行任务过多时,就会造成长时间的等待,触发某个事件无法响应。为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步执行就是单线程执行,而异步执行则是每一个任务有一个或多个回调函数,前一个任务执行完以后,并不会立即执行下一个任务,而是执行回调函数。而后一个任务不会等待前一个任务结束就执行。看到这里你可能有点懵逼了,在坚持一会,看完你或许就懂了。

首先需要清楚,javascript是一门语言,它是运行在浏览器里面的,而具体的操作则是由js引擎来解析和运行。下面我们来了解下浏览器,没准你就能懂单线程为什么可以有异步操作了。

目前主流的浏览器就分为:Chrome,IE,Safari,Firefox,opera,而浏览器的内核是多线程的。浏览器一般有以下几个常驻的线程。

  • 渲染引擎线程:这个就是负责渲染出我们最终展示的页面。
  • JS引擎线程:负责js的解析和执行。
  • 定时触发器线程:处理定时事件。比如setTimeout和setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求。

需要注意的是,渲染线程不能和js线程同时进行,渲染线程在执行的时候,js引擎会被挂起。因为js可以控制DOM,若在渲染中js处理了DOM,就需要重新在渲染了,这样会影响渲染的效率和用户的体验。

为什么说JS引擎为单线程呢?那是因为浏览器在运行时只会开启一个JS引擎线程来解析和执行javascript。那么为什么只是一个?如果同时有多个线程去操作DOM,那么浏览器处理起来就会变得复杂。

但是,虽然JS引擎是单线程,可以浏览器内部可不是单线程,一些I/O操作,定时器和事件监听等都是由浏览器所提供的线程来执行操作的。也就是说,javascript是通过js引擎和浏览器中其他线程共同合作实现异步操作的。但是回调函数具体何时加入到JS引擎线程中执行?执行顺序是怎么样的?

看上图,左边的stack表示栈储存的同步任务,就是同步任务的任务队列,储存那些能够立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。

右边的堆用来存储类函数的位置,对象等,下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步任务都和回调函数相关联。

JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。

JS引擎线程从消息队列中读取任务是不断循环的,每次栈被清空后,都会在消息队列中读取新的任务,如果没有新的任务,就会等待,直到有新的任务,这就叫事件循环。

常见的异步任务:

  • setInterval
  • setTimeout
  • Promise
  • process.nextTick
  • fs.readFile
  • http.get

sitTimeout运行机制

sitTimeout和setInterval运行机制是将制定的代码移出本次执行,等到下一轮Event Loop时,在检查是否到了指定时间。如果到了,就执行相应的代码。如果还没到,就等到下一轮Event Loop在检查执行。这就意味着,sitTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行。下面看一个例子

for(var i=0;i<4;i++){
  setTimeout(function(){
    console.log(i);
  },500)
}

最后输出的结果为4个4,这里还涉及到了闭包的问题。

setTimeout的作用是在间隔一定的时间后,将回调函数插入消息队列中,等栈中的同步任务都执行完毕后,再执行。因为栈中的同步任务也会耗时,所以间隔的时间一般会大于等于指定的时间。

那么该如何实时获取数据呢?我没可以采用IIFE声明来解决这个问题

for(var i=0;i<4;i++){
  (function(j){
      setTimeout(function(){
         console.log(j)
       },500)
   })(i)
}

利用JS中基本类型的参数传递是按值传递的特征实现

var put=function(i){
   setTimeout(function(){
       console.log(i)
   },500)
}
for(var i=0;i<4;i++){
  put(i);
}

sitTimeout是否及时执行取决于JS线程是拥挤还是空闲。浏览器的js引擎遇到setTimeout,拿走之后并不会立即放入异步队列中,同步任务执行之后,timer模块会到设置时间之后放到异步队列中。JS引擎发现同步队列中没有要执行的任务之后,即运行栈空了之后就会从异步队列中读取,然后放到运行栈中执行。所以setTimeout可能会多了等待线程的时间。

陈健的个人博客,记录生活所见所感、学习笔记。专注于Web前端_SEO教程_读书心得。

1 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

返回主页看更多
狠狠的抽打博主 支付宝 扫一扫