js执行机制

在写js代码的时候咱们每每都但愿js代码的执行顺序是按照本身所想那样执行,但结果总事与愿违。因此今天就想花时间搞懂js的事件执行机制究竟是什么样的。javascript

EventLoop事件循环机制

js中事件分为宏任务和微任务两类,任务又分为同步任务和异步任务。他们之间的执行顺序将依次进行说明html

js中事件的同步异步的执行顺序

js中事件分为两类java

  • 同步事件
  • 异步事件
  • js执行顺序:先执行同步再执行异步
  1. js在执行时若是遇到同步任务就直接放入主线程执行
  2. 若是遇到异步任务就将其放入event table 中并注册函数
  3. 当异步事件完成后会将他的放入Event Queue中等待执行(这里特别说明宏任务和微任务各自有一个Event Queue)

那么等到何时才能执行呢?就是等到主线程的任务执行完毕为空的时候。那咱们怎么知道何时主线程为空呢?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。 用流程图来归纳上述步骤:node

  • 代码示例
let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');
复制代码
  1. 从上往下,先遇到$.ajax异步请求,因此ajax进入event table中注册回调函数success
  2. 而后执行同步任务console.log("代码执行结束")
  3. ajax请求完成,回调函数success进入Event Queue
  4. 主线程任务执行完毕,从Event Queue中读取回调函数success并执行

js中事件的宏任务与微任务的执行顺序

js中任务分为两类ajax

  • 宏任务:包括总体代码script,setTimeout,setInterval
  • 微任务:Promise.then(非new Promise),process.nextTick(node中)
  • js执行顺序:先执行宏任务再执行微任务
  1. js执行事件时先执行线程或Event Queue的宏任务
  2. 当前事件的宏任务执行完毕查看此线程或Event Queue是否有微任务,有的话执行微任务,没有的话执行新的宏任务

用流程图表示上述流程:promise

  • 代码示例
setTimeout(function() {
    console.log('setTimeout');
},1000)

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');
复制代码
  1. 首先遇到了setTimeout函数,他是异步任务,因此将它放入event table中注册回调函数,又由于setTimeout是宏任务,因此1秒后将setTimeout放入宏任务的Event Queue中等待执行。
  2. 遇到同步代码new promise放入主线程直接开始执行
  3. 执行new promise的console.log('promise')而后看到.then是微任务所以将其放入微任务的Event Queue中
  4. 接下来执行同步代码console.log('console')
  5. 主线程的宏任务,已经执行完毕,接下来要执行微任务,所以会执行Event Queue中的Promise.then,到此,第一轮事件循环执行完毕
  6. 第二轮事件循环开始,先执行宏任务,即setTimeout的回调函数,而后查找是否有微任务,没有,时间循环结束

如今看一个比较复杂的例子浏览器

console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
输出:
1
2
4
3
5
复制代码
  1. 同步代码console.log('1')直接放入主线程执行
  2. setTimeout异步代码放到event table中注册回调函数,setTimeout事件完成将其回调函数放入宏任务的Event Queue中等待执行
  3. 而后主线程为空,先在宏任务的Event Queue中读取回调函数运行,直接执行console.log('2');
  4. 而后遇到process.nextTick放入微任务的Event Queue中
  5. 遇到new Promise执行console.log('4')而后将.then放入微任务的Event Queue中
  6. 宏任务执行完毕,查看微任务的Event Queue中是否有等待执行的微任务
  7. 执行process.nextTick的console.log('3')
  8. 执行.then的console.log('5')

js中的同步和异步

你们都知道js是是一门单线程语言,因此他的语句确定是一句一句执行的。可是js中又存在同步操做和异步操做,那么就会有疑问,js是如何经过单线程的方式来实现异步操做的呢?bash

什么是同步操做

当函数执行的时候,按照函数内部的顺序依次执行,好比:若是此调用的函数是很耗时的,但它依然会等待调用函数的返回值,直到拿到预期的结果(即拿到了预期的返回值或者看到了预期的效果)为止才会执行后面的操做,那么这个函数就是同步的。markdown

//在函数返回时,得到了预期值,即2的平方根
Math.sqrt(2);
//在函数返回时,得到了预期的效果,即在控制台上打印了'hello'
console.log('hello');

复制代码

什么是异步操做

若是函数是异步的,发出调用以后,立刻返回,可是不会立刻返回预期结果。调用者没必要主动等待,当被调用者获得结果以后会经过回调函数主动通知调用者。网络

//读取文件
fs.readFile('hello.txt', 'utf8', function(err, data) {
    console.log(data);
});
//网络请求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回调函数
xhr.open('GET', url);
xhr.send(); // 发起函数
复制代码

上述示例中读取文件函数 readFile和网络请求的发起函数 send都将执行耗时操做,虽然函数会当即返回,可是不能马上获取预期的结果,由于耗时操做交给其余线程执行,暂时获取不到预期结果(后面介绍)。而在JavaScript中经过回调函数 function(err, data) { console.log(data); }和 onreadystatechange ,在耗时操做执行完成后把相应的结果信息传递给回调函数,通知执行JavaScript代码的线程执行回调。

浏览器

前面说到js是一门单线程语言,他是怎么实现异步操做的呢。JS的运行一般是在浏览器中进行的,具体由JS引擎去解析和运行。下面咱们来具体了解一下浏览器。 浏览器的内核是多线程的。 一个浏览器一般由如下几个常驻的线程:

  • 渲染引擎线程:顾名思义,该线程负责页面的渲染
  • JS引擎线程:负责JS的解析和执行
  • 定时触发器线程:处理定时事件,好比setTimeout, setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求

须要注意的是,渲染线程和JS引擎线程是不能同时进行的。 渲染线程在执行任务的时候,JS引擎线程会被挂起。由于JS能够操做DOM,若在渲染中JS处理了DOM,浏览器可能就不知所措了。

JS引擎能够说是JS虚拟机,负责JS代码的解析和执行。之因此说JavaScript是单线程,就是由于浏览器在运行时只开启了一个JS引擎线程来解析和执行JS。那为何只有一个引擎呢?若是同时有两个线程去操做DOM,浏览器是否是又要不知所措了。 因此,虽然JavaScript是单线程的,但是浏览器内部不是单线程的。一些I/O操做、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其余线程来完成的。也就是以前说的Event Queue。

参考文章

JavaScript异步机制详解
这一次,完全弄懂 JavaScript 执行机制
简单总结下JS中EventLoop事件循环机制

相关文章
相关标签/搜索