zoukankan      html  css  js  c++  java
  • 原本准备的 event loop 分享

    基础介绍

    Stack 栈

    一种先入后出的数据结构。
    两个基本操作: 推入,弹出

    Queue 队列

    一种先入先出的数据结构
    操作: 入队,出队

    两种任务: 同步任务,异步任务

    同步任务: 在调用栈中等待主线程依次执行。

      console.log('sync');
    

    异步任务:结果不会立即返回,一般会有一个回调函数来处理返回结果。

      setTimeout(() => {
        console.log('async');
      }, 1000);
    
    

    对于Javascript的疑惑

    • 什么是JavaScript?
      • answer: Javascript 是一个单线程脚本语言。
        单线程的运行环境,它有且只有一个调用栈,它同时只能够做一件事。
    • 那问题来了,如果使用Javascript执行上面的异步任务,那我们就需要等待它 1000ms 吗?如果这个异步任务是操作某个dom元素,是否会和用户同时操作同一个DOM元素,从而造成竞态问题?
      我们开始探究

    我们称 Javascript 单线程的运行环境为主线程:
    之所以叫主线程是因为很多东西在它上面运行,包括 JavaScript 和 样式渲染,也是DOM存在的地方。
    但在我们进行渲染,接口请求和某些用户交互时,需要等待。这样它们就会造成阻塞。
    所以,但是我们还是希望有其他的现场,用来处理网络,编码解码或是监听输入设备。
    当这些设备完成了任务后,会把信息转交回主线程。实际上,是事件循环在指导这一切。

    Event Loop

    但是,我们在讲解 event loop 之前,先了解一下调用栈和任务队列

    调用栈 (call stack)

    所有的同步任务都在主线程上执行,形成一个执行栈。只有等待栈被清空,它才能执行下一个任务。

    function multiply(a, b) {
      return a*b;
    }
    function square(n) {
      return multiply(n, n);
    }
    function printSquare(n) {
     var squared = square(n);
     console.log(squared);
    }
    printSquare(4); 
    

    调用栈是一个记录当前程序所在位置的数据结构,如果当前进入了某个函数,这个函数就会被放入栈中。如果当前离开了某个函数,这个函数就会被弹出栈外,这是栈所做的事。

    使用动画可能更加生动:
    stack.gif

    而等待异步任务将会造成阻塞,幸运的是,浏览器提供了一些 JS 引擎不具备的功能:Web APIs。它可以帮助我们处理异步操作,它包括 DOM API,setTimeout,HTTP 请求 等等。

    而web Api 处理完的异步操作如何回到 调用栈中呢?那就需要任务队列登场了。

    任务队列 (callback queue)

    只有异步任务有了结果,就会被放入“任务队列”中,在任务队列中进行排队,进入调用栈。

    我们再看一段代码

    setTimeout(() => {
        console.log('setTimeout one');
    }, 1000);
    setTimeout(() => {
        console.log('setTimeout two');
    }, 1000);
    

    如果有两个事件,每个事件都会成为一个todo 项放到队列中。
    通过事件循环依次的去处理他们回调。就像这样:

    事件循环 (Event Loop)

    Event Loop的任务:连接任务队列和调用栈。
    它不停的检查 调用栈 中是否还有任务在执行,如果没有,就检查任务队列,从中弹出一个任务,放入调用栈,循环往复。

    回调被放入调用栈执行,得到结果后被弹出,只留下了一个 Hey!

    当执行栈执行完毕时,会立刻先处理所以微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远比宏任务之前执行。

    渲染

    当我们考虑到渲染,事情可能就会更加复杂,我们可以把渲染看做另一个弯道。渲染发生在每一帧的开始,包括样式计算,布局和绘制。

    因为事件循环会保证在渲染之前,Javascript会将任务执行完。所以,浏览器只能默默的看着你装逼,然而他只执行最后一行代码。

    button.addEventListener('click',() => {
      box.style.display = 'none';
      box.style.display = 'block';
      box.style.display = 'none';
      box.style.display = 'block';
      box.style.display = 'none';
      box.style.display = 'block';
      box.style.display = 'none';
      box.style.display = 'block';
      box.style.display = 'none';
    });
    

    再一些老的动画库中,人们喜欢使用setTimeout来绘制动画,如果更加了解 requestAnimationFrame,我相信他们将选择后者。
    首先我们来对比一段代码

    move() {
        let x = 0;
        let x1 = 0;
        setInterval(() => {
            x += 1;
            this.timeoutX = { marginLeft: `${x}px` };
        }, 16.6);
        const animate = () => {
            x1 += 1;
            this.animationX = { marginLeft: `${x1}px` };
            requestAnimationFrame(animate);
        };
        requestAnimationFrame(animate);
    }
    

    动画结果:

    requestAnimationFrame, setTimeout 同时执行时,setTimout 执行的频率更高一些,而RAF则和显示刷新频率一致,有固定的执行间隔。(这是其他大佬通过实验得来的,我上面的测试,可能是因为电脑每秒的刷新频率不足60帧)

    所以使用setTimeout 来模拟帧率十分不准确,可能会出现下图的情况。

    如果使用requestAnimationFrame 一切就显得井然有序多了

    测试一下

    彻底搞懂浏览器Event-loop - 前端进阶 - SegmentFault 思否 --你可以通过这篇文章底部的代码进行测试,你对 event loop 和任务执行顺序是否了解了。

    参考

    彻底搞懂浏览器Event-loop - 前端进阶 - SegmentFault 思否

    动图学 JavaScript 之:事件循环(Event Loop) - 码力全开 - SegmentFault 思否

    Jake Archibald: 在循环 - JSConf.Asia -- YouTube

    菲利普·罗伯茨:到底什么是Event Loop呢?- YouTube

  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/stone-lyl/p/12431353.html
Copyright © 2011-2022 走看看