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

  • 相关阅读:
    关于javaScript substring()方法的一点点应用
    解决Vue报错:Invalid prop: type check failed for prop "id". Expected Number with value 14, got String with value "14".
    better-scroll滚动无效的查错
    Vue程序化导航---又称编程式导航
    Vue路由初始化Can't read prooerty '$createElement' of undefined
    在前台利用jquery对dom元素进行排序
    js/jquery如何获取获取父窗口的父窗口的元素
    jquery使鼠标滚轮暂时失效
    introJs写法
    用Intro.js创建站点分步指南
  • 原文地址:https://www.cnblogs.com/stone-lyl/p/12431353.html
Copyright © 2011-2022 走看看