美文网首页
Javascript的循环机制

Javascript的循环机制

作者: 捡了幸福的猪 | 来源:发表于2021-07-05 21:51 被阅读0次

总结:

  • 浏览器是多进程的; 其中一个进程是浏览器渲染进程
  • 浏览器渲染进程包括多个线程; 其中一个线程是 JS引擎线程(负责解析和执行Javascript脚本);其他线程是 GUI渲染线程、事件触发线程、定时器触发线程、异步http请求线程。
  • 常说的主线程是指: JS引擎线程;
  • JS 是单线程是指: JS引擎线程只有1个;
  • 执行顺序:先执行宏任务中同步任务,再执行其对应微任务

下面进行分析:

进程 & 线程

  • 进程:cpu资源分配的最小单位, 系统会为每个进程分配独立的内存
  • 线程:cpu调度的最小单位

比喻:进程比作工厂,内存比作每个工厂拥有的资源,线程比作工厂里的工人。每个工厂中会有一个或者若干个工人来协作完成某个任务。每个工厂相互独立,但同一个工厂之间的工人共享资源。

通常所说的单线程和多线程,其中的“单”和“多”,一般指的是一个进程里有一个单线程或有多个线程。

执行规则

1、基本规则

  • 首先判断JS是同步还是异步,同步就进入主线程运行,异步就进入event table
  • 异步任务在event table中注册事件,当满足触发条件后(触发条件可能是延时也可能是ajax回调),被推入event queue
  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中。
setTimeout(() => {
  console.log('2秒到了')
}, 2000)

分析: setTimeout是异步操作首先进入event table,注册的事件就是他的回调,触发条件就是2秒之后,当满足条件回调被推入event queue,当主线程空闲时会去event queue里查看是否有可执行的任务。

2、细化规则: 宏任务&微任务
宏任务(macro-task):包括整体代码script、setTimeout、setInterval、MessageChannel、postMessage、setImmediate。
微任务(micro-task):Promise、process.nextTick、MutationObsever。

  • 「宏任务」、「微任务」都是队列,一段代码执行时,会先执行宏任务中的同步代码
  • 进行第一轮事件循环的时候会把全部的js脚本当成一个宏任务来运行
  • 如果执行中遇到setTimeout之类宏任务,那么就把这个setTimeout内部的函数推入「宏任务的队列」中,下一轮宏任务执行时调用。
  • 如果执行中遇到 promise.then() 之类的微任务,就会推入到当前宏任务的微任务队列中,在本轮宏任务的同步代码都执行完成后,依次执行所有的微任务。
  • 第一轮事件循环中当执行完全部的同步脚本以及微任务队列中的事件,这一轮事件循环就结束了,开始第二轮事件循环。
  • 第二轮事件循环同理先执行同步脚本,遇到其他宏任务代码块继续追加到「宏任务的队列」中,遇到微任务,就会推入到「当前宏任务的微任务队列」中,在本轮宏任务的同步代码执行都完成后,依次执行当前所有的微任务。
  • 开始第三轮,循环往复...
    栗子1:
setTimeout(function() {
    console.log('4')
})

new Promise(function(resolve) {
    console.log('1') // 同步任务
    resolve()
}).then(function() {
    console.log('3')
})
console.log('2')

分析:

  • 这段代码作为宏任务,进入主线程。
  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。
  • 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。
  • 遇到console.log(),立即执行。
  • 整体代码script作为第一个宏任务执行结束。查看当前有没有可执行的微任务,执行then的回调。

第一轮事件循环结束了,我们开始第二轮循环。

-从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。

  • 执行结果:1 - 2 - 3 - 4

栗子2:

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')
    })
})

process.nextTick(function() {
    console.log('6')
})

new Promise(function(resolve) {
    console.log('7')
    resolve()
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9')
    process.nextTick(function() {
        console.log('10')
    })
    new Promise(function(resolve) {
        console.log('11')
        resolve()
    }).then(function() {
        console.log('12')
    })
})
  • 整体script作为第一个宏任务进入主线程,遇到console.log(1)输出1。
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
  • 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
  • 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
  • 现在开始执行微任务,我们发现了process1和then1两个微任务,执行process1,输出6。执行then1,输出8。

第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮事件循环从setTimeout1宏任务开始:

  • 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。
  • new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
  • 现在开始执行微任务,我们发现有process2和then2两个微任务可以执行输出3,5。

第二轮事件循环结束,第二轮输出2,4,3,5。第三轮事件循环从setTimeout2宏任务开始:

  • 直接输出9,将process.nextTick()分发到微任务Event Queue中。记为process3。
  • 直接执行new Promise,输出11。将then分发到微任务Event Queue中,记为then3。
  • 执行两个微任务process3和then3。输出10。输出12。

第三轮事件循环结束,第三轮输出9,11,10,12。

  • 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
    标注一下:
console.log('1')   // 宏任务1
setTimeout(function() {
    console.log('2')    // 宏任务2
    process.nextTick(function() {
        console.log('3')  // 宏任务2-微任务1
    })
    new Promise(function(resolve) {
        console.log('4')  // 宏任务2
        resolve()
    }).then(function() {
        console.log('5')   // 宏任务2-微任务2
    })
})

process.nextTick(function() {
    console.log('6') // 宏任务1-微任务1
})

new Promise(function(resolve) {
    console.log('7')  // 宏任务1
    resolve()
}).then(function() {
    console.log('8')  // 宏任务1-微任务2
})

setTimeout(function() {
    console.log('9')   // 宏任务3
    process.nextTick(function() {
        console.log('10')  // 宏任务3-微任务1
    })
    new Promise(function(resolve) {
        console.log('11') // 宏任务3
        resolve()
    }).then(function() {
        console.log('12')  // 宏任务3-微任务2
    })
})

栗子3:

new Promise(function (resolve) { 
    console.log('1')// 宏任务一
    resolve()
}).then(function () {
    console.log('3') // 宏任务一的微任务
})
setTimeout(function () { // 宏任务二
    console.log('4')
    setTimeout(function () { // 宏任务五
        console.log('7')
        new Promise(function (resolve) {
            console.log('8')
            resolve()
        }).then(function () {
            console.log('10')
            setTimeout(function () {  // 宏任务七
                console.log('12')
            })
        })
        console.log('9')
    })
})
setTimeout(function () { // 宏任务三
    console.log('5')
})
setTimeout(function () {  // 宏任务四
    console.log('6')
    setTimeout(function () { // 宏任务六
        console.log('11')
    })
})
console.log('2') // 宏任务一

结果: 1、2、3、4、5、6、7、8、9、10、11、12

3、结合async/await 执行规则

  • async/await 在底层转换成了 promise 和 then 回调函数,是 promise 的语法糖。async函数返回一个 Promise 对象。

  • async函数内部return语句返回的值,会成为then方法回调函数的参数。async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

  • async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

    async function func () {
     const res1 = await new Promise((resolve) => {
       setTimeout(()=>resolve('promise1'), 1000);
     });
     const res2 = await new Promise((resolve) => {
        setTimeout(()=>resolve('promise2'), 1000);
      });
      return [res1, res2]
    }
    func().then(console.log)   // ["promise1", "promise2"]
  • 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
//  js 实现休眠
    function sleep(interval) {
      return new Promise(resolve => {
        setTimeout(resolve, interval);
      })
    }

    async function sleepUse() {
      for(let i = 1; i <= 5; i++) {
        console.log(i);
        await sleep(1000);
      }
    }
    sleepUse();

上面是关于async/await 的基础知识。 结合async/await 的事件规则 就是: 将async/await 转化为promise理解。

  • async 是一个Promise
  • 遇到await, await 右边的 表达式会立即执行
  • await 下一行的 所有的 都可以理解成 是放在then 中

🌰1:

setTimeout(function () {
  console.log('6')
}, 0)
console.log('1')
async function async1() {
  console.log('2')
  await async2()
  console.log('5')
}
async function async2() {
  console.log('3')
}
async1()
console.log('4')

结果: 1 2 3 4 5

栗子2:

console.log('1')   // 宏任务1
async function async1() {
  console.log('2')  // 宏任务1
  await 'await的结果' 
  console.log('5')   // 宏任务1-微任务1
}

async1()  // 宏任务1
console.log('3')   // 宏任务1

new Promise(function (resolve) {
  console.log('4')  // 宏任务1
  resolve()
}).then(function () {
  console.log('6')  // 宏任务1-微任务2
})

🌰3:

async function async1() {
  console.log('2')   // 宏任务1
  await async2()
  console.log('7')  // 宏任务1 - 微任务1
}

async function async2() {
  console.log('3')    // 宏任务1
}

setTimeout(function () {
  console.log('8')   // 宏任务2
}, 0)

console.log('1')  // 宏任务1
async1()

new Promise(function (resolve) {
  console.log('4')   // 宏任务1
  resolve()
}).then(function () {
  console.log('6') // 宏任务1-微任务2
})
console.log('5') // 宏任务1

结果: 1 -> 2 -> 3 -> 4 -> 5 -> 7 -> 6 -> 8

🌰4:

setTimeout(function () {
  console.log('9')
}, 0)  // 宏任务2
console.log('1')   // 宏任务1
async function async1() {
  console.log('2')   // 宏任务1
  await async2()
  console.log('8')   // 宏任务1-微任务3
}
async function async2() {
  return new Promise(function (resolve) {
    console.log('3')   // 宏任务1
    resolve()
  }).then(function () {
    console.log('6')  // 宏任务1-微任务1
  })
}
async1()

new Promise(function (resolve) {
  console.log('4')   // 宏任务1
  resolve()
}).then(function () {
  console.log('7') // 宏任务1-微任务2
})
console.log('5') // 宏任务1

栗子:

async function async1() {
  console.log('2')   // 宏任务1
  const data = await async2()
  console.log(data)   // await的结果
  console.log('8')  // 宏任务1-微任务3
}

async function async2() {
  return new Promise(function (resolve) {
    console.log('3')   // 宏任务1
    resolve('await的结果')
  }).then(function (data) {
    console.log('6')    // 宏任务1-微任务1
    return data
  })
}
console.log('1')  // 宏任务1

setTimeout(function () {
  console.log('9')
}, 0)  // 宏任务2

async1()

new Promise(function (resolve) {
  console.log('4')    // 宏任务1
  resolve()
}).then(function () {
  console.log('7')  // 宏任务1-微任务2
})
console.log('5')    // 宏任务1

🌰:

const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)

此例子需要注意的点是:

  • 函数中await的new Promise要是没有返回值的话则不执行后面的内容
  • .then函数中的参数期待的是函数,如果不是函数的话会发生透传

备注:本文总结自「从event loop到async await来了解事件循环机制」原文链接为https://juejin.cn/post/6844903740667854861

hi ~ 你好鸭~

相关文章

网友评论

      本文标题:Javascript的循环机制

      本文链接:https://www.haomeiwen.com/subject/hhaiultx.html