• JS三座大山_单线程&EventLoop


    JS三座大山_单线程&EventLoop

    JavaScript是单线程、单并发、非阻塞语言

    单线程:主程序只有一个线程,即同一时间片段内其只能执行单个任务。

    单线程背景:浏览器执行环境中,若多线程同时对同一DOM进行操作,无法保证程序执行一致性。

    引发的问题:

    • 单线程,意味着任务都需要排队,前一个任务结束,才会执行后一个任务。
    • 若前一个任务耗时很长,后一个任务就需要一直等待。导致IO操作耗时,但CPU闲置,造成性能浪费。

    如何解决:

    方案一:异步。

    主线程可以不管IO操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到IO操作返回了结果,再回过头,把挂起的任务执行下去。于是,所有任务可以分为两种,一种是同步任务,另一种是异步任务。
    ps:当主线程阻塞时,任务队列仍然能被推入任务。

    方案二:web worker
    • 子线程完全受制于主线程,不能独立执行,且不能操作DOM
    • 子线程没有I/O等操作权限,只能为主线程分担一些诸如计算等任务
    • web worker并未改变js单线程的本质

    非阻塞。事件驱动程序模型基本的实现原理基本上都是事件循环Event Loop.

    1.JavaScript内存模型:

    'js内存模型'

    • 调用栈(Call Stack):用于主线程任务的执行。
    • 堆(Heap):用于存放非结构化的数据,譬如程序分配的变量与对象。
    • 任务队列(Queue):用于存放异步任务与定时任务。

    2.JavaScript代码执行机制:

    • 所有同步任务都在主线程上的栈中执行,形成一个执行栈。
    • 主线程之外,还存在一个“任务队列”。只有异步任务有了运行结果,就在“任务队列”之中放置一个事件,排队。
    • 一旦“栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,选择出需要首先执行的任务(由浏览器决定,并不按序),于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
    • 主线程不断重复第三步。

    3.Event Loop

    • MacroTask(Task): 宏任务。整体代码的script、setTimeout、setInterval、 setImmediate、requestAnimationFrame、I/O、UI rendering;

    • MicroTask:微任务。process.nextTick(Node环境中),Promise,Object.observe(基本废弃),MutationObserver.

    执行步骤:

    • 执行宏任务(先进先出),一次循环只执行一个宏任务

    • 执行栈:同步任务顺序执行,异步方法交给异步处理模块

    • 执行栈为空时,取出微任务执行(先进先出),直到微任务队列为空
      更新UI渲染。完成一轮循环,反复执行1-4步

    • 渲染更新:
      在一轮Event
      Loop中多次修改同一DOM,只有最后一次会进行绘制;
      渲染更新会在Event Loop的tasks和microTasks完成后进行。但并不是每轮Event Loop都会更新渲染,浏览器有自己的机制来确定是否要更新渲染。如果在一帧(16.7ms)里多次修改DOM,浏览器只会渲染绘制一次。
      如果希望每轮Event Loop都即时呈现变动,可以使用requestAnimationFrame

    规范:

    • 每个浏览器环境,至多有一个event loop。
    • 一个event loop可以有1个或多个task queue,而仅有一个MicroTask Queue。
    • 一个task queue是一列有序的task,每个task定义时都有一个task source,从同一个task source来的task必须放到同一个task queue,从不同源来的则被添加到不同的队列。
      tasks are scheduled,所以浏览器可以从内部到JS/DOM,保证动作按序发生。
    • Microtasks are scheduled,Microtask queue在当前task queue的结尾执行。microtask中添加的microtask也被添加到Microtask queue的末尾并处理。

    4.JS的运行环境:浏览器 & Node

    • 浏览器中事件循环给予规范来实现。
    • Node是基于libuv这个库来实现的。

    *JS是单线程执行的,而基于事件循环模型,形成了基本没有阻塞(除了alert或同步XHR等操作)的状态。

        setTimeout(function(){
            console.log(1);
         });
    
        new Promise(function(resolve){
            console.log(2);
            resolve();
         }).then(function(){
            console.log(3);
         });
    
        console.log(4);
    

    执行结果:4 2 3 1
    分析:

    • 从宏任务队列(task)中取出script,将所有同步代码推入执行栈中执行,遇到异步代码交给异步处理模块,异步处理模块完成后按规则推入事件队列,宏任务推宏任务队列,微任务推微任务队列。所以输出2和4.
    • 执行玩script中的同步代码,再将微任务队列中最老的任务推入执行栈执行,直到清空微任务。所以输出3;
      浏览器更新渲染,再去宏任务队列中取出嘴来的任务推入执行栈中执行,循环以上步骤,所以输出1.

    5.使用

    • 在microtask中不要放置复杂的处理程序,防止阻塞UI的渲染;
    • 可以使用process.nextTick处理一些比较紧急的事情。因为process.nextTick优先级高于其他微任务。
    • 可以在setTimeout回调中处理上轮事件循环中UI渲染的结果;
    • 注意不要滥用setInterval和setTimeout,它们并不能保证按时处理的。
    • 一些可能会影响到UI的异步操作,可放在promise回调中处理,防止多一轮事件循环导致重复执行UI的渲染。
    • 在Node中使用immediate来处理可能会得到更多的保证。
  • 相关阅读:
    剖析C语言中a=a+++++a的无聊问题
    [转]精确到1%秒的单片机计时器汇编程序
    [转]学DSP、FPGA、ARM,哪个更有前途?
    【Java】Eclipse导出JAR包
    二维码生成器(支持历史记录点击和清空)
    移动端开发注意之一二
    localStorage实现按钮点击禁用
    JavaScript之查找元素
    扒拉扒拉table
    解惑之JavaScript
  • 原文地址:https://www.cnblogs.com/nanhuaqiushui/p/14406133.html
Copyright © 2020-2023  润新知