• 技术文章 >web前端 >前端问答

    技术解答之JavaScript的执行机制

    长期闲置长期闲置2022-01-14 17:57:53转载110
    本篇文章带大家带来了JavaScript中执行机制的相关问题,不论是工作还是面试,我们可能都经常会碰到需要知道代码的执行顺序的场景,希望对大家有帮助。

    进程与线程

    我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。

    进程

    进程是一个具有独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体 进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。

    进程具有的特征:

    线程

    线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。

    进程与线程的区别

    JS为什么是单线程?

    JavaScript从它诞生之初就是作为浏览器的脚本语言,主要用来处理用户交互以及操作DOM,这就决定了它只能是单线程的,否则会带来非常复杂的同步问题。

    举个例子: 如果JS是多线程的,其中一个线程要修改一个DOM元素,另外一个线程想要删除这个DOM元素,这时候浏览器就不知道该听谁的。所以为了避免复杂性,从一诞生,JavaScript就被设计成单线程。

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质

    浏览器原理

    作为前端工程师,浏览器想必都不陌生,并且浏览器是多进程的。

    浏览器组成部分

    注意:与大多数浏览器不同的是,谷歌(Chrome)浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程

    浏览器包含哪些进程

    浏览器进程

    第三方插件进程

    负责管理第三方插件

    GPU进程

    负责3D绘制与硬件加速(最多一个)

    渲染进程

    负责页面文档解析,执行与渲染

    渲染进程包含哪些线程

    GUI渲染线程

    主要负责解析HTML,CSS,构建DOM树,布局,绘制等

    该线程与JavaScript引擎线程互斥,当执行JavaScript引擎线程时,GUI渲染线程会被挂起,当任务队列空闲时,主线程才会执行GUI渲染

    JavaScript引擎线程

    主要负责处理JavaScript脚本,执行代码(如V8引擎)

    浏览器同时只能有一个JS引擎线程在运行JS程序,即JS是单线程的

    JS引擎线程与GUI渲染线程是互斥的,所以JS引擎会阻塞页面渲染

    定时触发器线程

    负责执行定时器函数(setTimeout,setInterval)

    浏览器定时计数器并不是由JS引擎计数的(因为JS是单线程的,如果处于阻塞状态就会影响计数器的准确性)

    通过单独线程来计时并触发定时(计时完毕后,添加到事件触发线程的事件队列中,等待JS引擎空闲后执行),这个线程就是定时触发器线程,也叫定时器线程

    W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms

    事件触发线程

    负责将准备好的事件交给JS引擎线程执行

    当事件被触发时,该线程会把对应的事件添加到待处理队列的队尾,等待JS引擎处理

    异步请求线程

    在XMLHttpRequest连接后浏览器会开一个线程

    检测请求状态变更时,如果有对应的回调函数,异步请求线程就会产生状态变更事件,并把对应的回调函数放入队列中等待JS引擎执行

    同步与异步

    由于JavaScript是单线程的,这就决定了它的任务不可能只有同步任务,那些耗时很长的任务如果也按同步任务执行的话将会导致页面阻塞,所以JavaScript任务一般分为两类:

    同步任务

    同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

    异步任务

    异步任务指的是,不进入主线程、而进入"任务队列"(Event queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

    常见的异步任务: 定时器,ajax,事件绑定,回调函数,promise,async await等

    宏任务与微任务

    JavaScript除了广义上的同步任务与异步任务,还有更精细的任务定义:

    不同类型的任务会进入到不同的任务队列:

    22.png

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

    执行栈与任务队列

    执行栈

    JavaScript代码都是在执行上下文中执行的,在JavaScript中有三种执行上下文:

    通常来说我们的JS代码都不止一个上下文,那么这些上下文的执行顺序是怎样的呢?

    我们都知道栈是一种后进先出的数据结构,我们JavaScript中的执行栈就是一种这样的栈结构,当JS引擎执行代码时,会产生一个全局上下文并把它压入执行栈,每当遇到函数调用时,就会产生函数执行上下文并压入执行栈。引擎从栈顶开始执行函数,执行完后会弹出该执行上下文。

    function add(){
      console.log(1)
      foo()
      console.log(3)
    }
    function foo(){
      console.log(2)
    }
    add()

    我们来看下上面这段代码的执行栈是怎样的:

    23.png

    任务队列

    前面我们说到了JavaScript中所有的任务分为同步任务与异步任务,同步任务,顾名思义就是立即执行的任务,它一般是直接进入到主线程中执行。而我们的异步任务则是进入任务队列等待主线程中的任务执行完再执行。

    任务队列是一个事件的队列,表示相关的异步任务可以进入执行栈了。主线程读取任务队列就是读取里面有哪些事件。

    队列是一种先进先出的数据结构。

    上面我们说到异步任务又可以分为宏任务与微任务,所以任务队列也可以分为宏任务队列与微任务队列

    事件循环(Event-Loop)

    同步任务直接放入到主线程执行,异步任务(点击事件,定时器,ajax等)挂在后台执行,等待I/O事件完成或行为事件被触发。

    系统后台执行异步任务,如果某个异步任务事件(或者行为事件被触发),则将该任务添加到任务队列,并且每个任务会对应一个回调函数进行处理。

    这里异步任务分为宏任务与微任务,宏任务进入到宏任务队列,微任务进入到微任务队列。

    执行任务队列中的任务具体是在执行栈中完成的,当主线程中的任务全部执行完毕后,去读取微任务队列,如果有微任务就会全部执行,然后再去读取宏任务队列

    上述过程会不断的重复进行,也就是我们常说的事件循环(Event-Loop)。

    24.png

    例题验证

    我们来看道题目进行验证

    (async ()=>{
        console.log(1) 
      
        setTimeout(() => {
        console.log('setTimeout1')
        }, 0);
      
        function foo (){
            return new Promise((res,rej) => {
                console.log(2)
                res(3)
            })
        }
      
        new Promise((resolve,reject)=>{
        console.log(4)
        resolve() 
        console.log(5)
        }).then(()=> {
        console.log('6')
        })
      
        const res = await foo();
        console.log(res);
        console.log('7')
      
        setTimeout(_ => console.log('setTimeout2'))
    })()

    打印顺序是:1,4,5,2,6,3,7,setTimeout1,setTimeout2

    分析:

    代码自上而下执行,先遇到console.log(1),直接打印1,接着遇到定时器属于宏任务,放入宏任务队列

    再遇到promise,由于new Promise是一个同步任务,所以直接打印4,遇到resolve,也就是后面的then函数,放入微任务队列,再打印5

    然后再执行await foo,foo函数里面有个promise,new promise属于同步任务,所以会直接打印2,await返回的是一个promise的回调,await后面的任务放入微任务队列

    最后遇到一个定时器,放入宏任务队列

    执行栈任务执行完了,先去微任务队列获取微任务执行,先执行第一个微任务,打印6,再执行第二个微任务,打印3,7

    微任务执行完,再去宏任务队列获取宏任务执行,打印setTimeout1,setTimeout2

    【相关推荐:javascript学习教程

    以上就是技术解答之JavaScript的执行机制的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金,如有侵犯,请联系admin@php.cn删除
    专题推荐:javascript 前端 html
    上一篇:jquery怎样点击按钮去掉一行样式 下一篇:十分钟教你使用css实现烟雾效果

    相关文章推荐

    • javascript怎样控制不能输入汉字• javascript splice方法怎么用• init在JavaScript中的意思是什么• javascript中writeln的意思是什么• 33个非常实用的JavaScript一行代码,建议收藏!

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网