• JavaScript 的执行机制


    一、关于javascript

      javascript是一门单线程语言,在最新的HTML5中提出了Web Worker,但javascript是单线程这一核心仍未改变。

      为什么js是单线程的语言?因为最初的js是用来在浏览器验证表单操纵DOM元素的如果js是多线程的话,两个线程同时对一个DOM进行了相互冲突的操作,那么浏览器的解析是无法执行的。

      Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。

    二、javascript事件循环

      当我们调用一个方法的时候,js会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,这个作用域中定义的变量以及这个作用域的this对象。 而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈

      js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。

      当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码。如此反复,这样就形成了一个无限的循环

    三、setTimeout

      setTimeout这个函数,是经过指定时间后,把要执行的任务加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着。

    setTimeout(() => {
        task()
    },3000)
    
    sleep(10000000)

       上述代码执行task()需要的时间远远超过3秒,执行过程如下:

    • task()进入Event Table并注册,计时开始。
    • 执行sleep函数,很慢,非常慢,计时仍在继续。
    • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep还没执行完,只好等着。
    • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

      setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

    四、setInterval

       对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。

      唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。

    五、Promise与process.nextTick(callback)
      Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

      所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

      process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

    六、宏任务和微任务
    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
    • micro-task(微任务):Promise.then,process.nextTick

      不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

      事件循环的顺序,决定js代码的执行顺序。

      进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

    setTimeout(function() {
        console.log('setTimeout');
    })
    
    new Promise(function(resolve) {
        console.log('promise');
    }).then(function() {
        console.log('then');
    })
    
    console.log('console');
    • 这段代码作为宏任务,进入主线程。
    • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue
    • 接下来遇到了Promisenew Promise立即执行,因为new Promise回调函数中的代码是同步任务then函数分发到微任务Event Queue
    • 遇到console.log(),立即执行。
    • 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
    • 第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
    • 结束。

    七、async await

    1.async 做一件什么事情?

      带 async 关键字的函数,它使得你的函数的返回值必定是 promise 对象

      也就是,如果async关键字函数返回的不是promise,会自动用 Promise.resolve() 包装。如果async关键字函数显式地返回promise,那就以你返回的promise为准。

    2.await 在等什么?

      await等的是右侧「表达式」的结果。也就是说,右侧如果是函数,那么函数的return值就是「表达式的结果」。右侧如果是一个 'hello' 或者什么值,那表达式的结果就是 'hello'。

    3.await 等到之后,做了一件什么事情?

      await右侧表达式的结果,就是await要等的东西。等到之后,对于await来说,分2个情况:

    • 不是promise对象
    • 是promise对象

      如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果。

      如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

    4.示例

    async function async1() {
        console.log('async1 start');
        await async2();
        console.log('async1 end');
    }
    
    async function async2() {
        console.log('async2');
    }
    
    console.log('script start');
    setTimeout(function () {
        console.log('setTimeout');
    }, 0);
    async1();
    new Promise(function (resolve) {
        console.log('promise1');
        resolve();
    }).then(function () {
        console.log('promise2');
    });
    console.log('script end');
    • 这段代码作为宏任务,进入主线程。
    • 先打印出script start。接着执行函数async1。
    • 打印出async1 start,执行到await,执行函数async2,打印出async2。
    • 此时await会阻塞async1后面的代码,会先执行async1外面的同步代码。
    • setTimeout放入事件循环的宏任务。接着执行到Promise,打印出promise1,promise.then放入事件循环的微任务。接着打印script end。
    • 现在async1外面的同步代码执行完毕,回到async1内部打印出async1 end。
    • 整体代码执行完毕,执行微任务promise.then,打印出promise2。
    • 最后执行宏任务setTimeout,打印setTimeout。

    所以这段代码的执行顺序为:script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> setTimeout

  • 相关阅读:
    祝贺我的博客訪问量过万(訪问量:10260次)
    【LeetCode-面试算法经典-Java实现】【107-Binary Tree Level Order Traversal II(二叉树层序遍历II)】
    Hibernate之HQL检索(查询)方式
    使用Nexus搭建Maven仓库
    poj2151之概率DP
    《从零開始学Swift》学习笔记(Day 71)——Swift与C/C++混合编程之数据类型映射
    D3D 点列练习
    poj 1733 Parity game
    命令行參数
    高速乘法
  • 原文地址:https://www.cnblogs.com/gg-qq/p/10932219.html
Copyright © 2020-2023  润新知