关于异步:新技能码住在前端表格中花式使用异步函数的奥义

背景60年代时,操作系统中独立运行的单元通常是过程。但随着计算机技术的倒退,人们发现在过程运行过程中,创立、撤销与切换都要花费较大的时空开销。 到了80年代为了解决这一问题,呈现了更小的独立运行根本单位——线程。 操作系统把 CPU 解决工夫划分成许多更小的工夫片,在每一个独立工夫片执行一个线程的指令,到下一个工夫片继续执行下一线程的指令,各线程轮流执行,因为每一个工夫片工夫都比拟短,所有线程都会运行,对于使用者而言就如同所有线程在同时进行。最终达到的成果就是在编程时能够创立多个线程,同一时间运行,各线程能够"并行"运行,以实现不同的工作。 这时新的问题也呈现了,在独自线程的运行模式之下,一段代码调用另一段代码时,只能采纳同步调用,只有以后代码执行实现返回后果之后,调用能力持续往下执行。用一个例子就是当初只有一个水槽,一匹马想喝水只能等上一匹马走了能力持续喝。 而有了多线程的反对,能够采纳异步函数的调用,这个问题就迎刃而解了。 异步函数原理介绍程序中会有很多内容,计算内容简单、渲染内容繁多,在处理过程中须要破费比拟多的工夫。当某个模块A调用了模块B的解决内容时,这时模块B中的内容就须要一些工夫解决,此时模块A如果不停地期待,就会重大影响程序性能。在理论状况中,就比方在前端页面中须要进行在线填报的数据处理,须要对数据内容进行计算后放入表格中展现,这是因为计算并未实现,页面内容也不显示,给用户带来的感觉就是内容都点击运行了,然而页面迟迟没有任何反馈。 呈现了异步函数的调用之后,此时执行的模块A和模块B别离属于不同的线程。 在异步调用中,模块A不须要等到模块B返回内容,就能够继续执行后续代码。 模块B中的内容执行结束后,会告诉模块A:我这边处理完毕,你记得解决后续内容。 借助异步调用,能够把刚刚咱们提到的前端页面中显示问题进行优化:把整个初始化解决放进一个独自线程,主线程启动此线程后接着往下走,让主窗口霎时显示进去。等考虑须要进行的操作的内容时,数据计算解决就曾经在暗中处理完毕;程序开始稳固运行当前,异步调用还能够进一步优化人机交互的过程。用户点击鼠标时进行操作的时候,操作内容比拟费时,点击后零碎没有立马作出回应,这会让用户的应用体验很蹩脚。将更费时、速度更慢的操作内容转为异步调用,让主线程随时恭候下一条音讯,这样用户的鼠标操作动作响应速度更快,应用体验天然大大晋升。 实际:专家用户的花式应用实例演示咱们用一个简略的例子,看看在前端电子表格单元格计算中,如何应用异步函数。 var ServerDecode = function () {};ServerDecode.prototype = new GC.Spread.CalcEngine.Functions.AsyncFunction("DECODE", 1, 255);ServerDecode.prototype.evaluateAsync = function (context, arg1) { $.get("decode/" + arg1, function (data, status) { context.setAsyncResult(data); });};spread.addCustomFunction(new ServerDecode());sheet.setFormula(0, 1, '=DECODE(A1)');在这个算法中咱们将设定的计算解析办法局部放在服务器上,办法名称叫DECODE 下一步将参数用jquery.get申请发送到服务器中,而后获取申请内容后实现设置 而后将整个异步函数注册进入Spread中 最初在B1单元格中,输出DECODE(A1) 这样当A1单元格内容发生变化的时候,B1就会依据咱们设定的计算规定重算成对应内容 异步函数的花式应用工具总在不同人手中被挖掘出各种各样的用法,而在去年冬天咱们就收到了用户反馈的异步函数的各种微妙应用形式。 他们应用异步函数的参数组合成了一个SQL,发送给数据库进行数据查问,并在查问完结后显示查问后果。后果所有正确,然而却呈现了一个小问题。 在应用过程中,用户发现查问在整个过程中超过了 四次 ,询问咱们是否是公式出错? 咱们当即发展问题排查,在查看源代码的过程中咱们发现,在最早实现这个性能的时候为了强调数据重要性,当同一个公式中呈现多个异步函数调用时,再次计算下一个内容时咱们还会再计算一次曾经计算过的异步函数的内容。 没想到用户的确会这样应用异步函数,这一部分内容随后也进行整体调整。现已调整为每次调用只计算一次异步函数。 有了这次经验,再遇到用户对异步函数的其余花里胡哨的用法,咱们就见怪不怪了。 果不其然,没多久又收到了其余用户的花式应用反馈。 这一次用户应用异步函数从服务器获取以后服务名,并在SpreadJS显示进去。 咱们发现这个用户还在其中增加了格局字符串,用以获取用户的二维码。同时在这里还设置了条件格局,如果用户没有登录会有报错提醒。 这个例子内容虽短,但在这里用户将异步函数、条件、格局还有格局字符串三个性能都联合在一起应用。 总结以上就是咱们全副对异步函数诞生背景和原理,以及在前端电子表格中异步函数的应用和各种神仙用户的花式应用,到本节对于电子表格计算原理的全部内容就曾经介绍结束。 感觉内容不错点个赞再走吧~ ...

November 17, 2021 · 1 min · jiezi

关于异步:处理可能超时的异步操作

自从 ECMAScript 的 Promise ES2015 和 async/await ES2017 个性公布当前,异步在前端界曾经成为特地常见的操作。异步代码和同步代码在解决问题程序上会存在一些差异,编写异步代码须要领有跟编写同步代码不同的“意识”,为此我还专门写了一篇「异步编程须要“意识”」,不过看的人不多,可能的确“无趣”。 本文要聊的问题可能依然“无趣”,但很事实 —— 如果一段代码久久不能执行实现,会怎么样? 如果这是同步代码,咱们会看到一种叫做“无响应”的景象,或者艰深地说 —— “死掉了”;然而如果是一段异步代码呢?可能咱们等不到后果,但别的代码仍在持续,就如同这件事件没有产生个别。 当然事件并不是真的没产生,只不过在不同的状况下会产生不同的景象。比方有加载动画的页面,看起来就是始终在加载;又比方应该进行数据更新的页面,看不到数据变动;再比方一个对话框,怎么也关不掉 …… 这些景象咱们统称为 BUG。但也有一些时候,某个异步操作过程并没有“回显”,它就默默地死在那里,没有人晓得,待页面刷新之后,就连一点陈迹都不会留下。 当然,这不是小说,咱们得聊点“闲事”。 Axios 自带超时解决应用 Axios 进行 Web Api 调用就是一种常见的异步操作过程。通常咱们的代码会这样写: try { const res = await axios.get(url, options); // TODO 失常进行后续业务} catch(err) { // TODO 进行容错解决,或者报错}这段代码个别状况下都执行良好,直到有一天用户埋怨说:怎么等了半天没反馈? 而后开发者意识到,因为服务器压力增大,这个申请曾经很难刹时响应了。思考到用户的感触,加了一个 loading 动画: try { showLoading(); const res = await axios.get(url, options); // TODO 失常业务} catch (err) { // TODO 容错解决} finally { hideLoading();}然而有一天,有用户说:“我等了半个小时,竟然始终在那转圈圈!”于是开发者意识到,因为某种原因,申请被卡死了,这种状况下应该重发申请,或者间接报告给用户 —— 嗯,得加个超时查看。 ...

November 16, 2021 · 2 min · jiezi

关于异步:JavaScript-异步编程

残缺高频题库仓库地址:https://github.com/hzfe/awesome-interview 残缺高频题库浏览地址:https://febook.hzfe.org/ 相干问题JavaScript 异步编程计划有哪些JavaScript 异步编程计划各有什么优缺点答复关键点阻塞 事件循环 回调函数 JavaScript 是一种同步的、阻塞的、单线程的语言,一次只能执行一个工作。但浏览器定义了非同步的 Web APIs,将回调函数插入到事件循环,实现异步工作的非阻塞执行。常见的异步计划有异步回调、定时器、公布/订阅模式、Promise、生成器 Generator、async/await 以及 Web Worker。 知识点深刻1. 异步回调异步回调函数作为参数传递给在后盾执行的其余函数。当后盾运行的代码完结,就调用回调函数,告诉工作曾经实现。具体示例如下: // 第一个参数是监听的事件类型,第二个就是事件产生时调用的回调函数。btn.addEventListener("click", () => { console.log("You clicked me!"); const pElem = document.createElement("p"); pElem.textContent = "hello, hzfe."; document.body.appendChild(pElem);});异步回调是编写和解决 JavaScript 异步逻辑的最罕用形式,也是最根底的异步模式。然而随着 JavaScript 的倒退,异步回调的问题也不容忽视: 回调表白异步流程的形式是非线性的,非程序的,了解老本较高。回调会受到管制反转的影响。因为回调的控制权在第三方(如 Ajax),由第三方来调用回调函数,无奈确定调用是否合乎预期。多层嵌套回调会产生回调天堂(callback hell)。2. 定时器:setTimeout/setInterval/requestAnimationFrame这三个都能够用异步形式运行代码。次要特色如下: setTimeout:通过任意工夫后运行函数,递归 setTimeout 在 JavaScript 线程不阻塞情的况下可保障执行距离雷同。setInterval:容许反复执行一个函数,并设置工夫距离,不能保障执行距离雷同。requestAnimationFrame:以以后浏览器/零碎的最佳帧速率反复且高效地运行函数的办法。个别用于解决动画成果。setInterval 会按设定的工夫距离固定调用,其中 setInterval 外面的代码的执行工夫也蕴含在内,所以理论距离小于设定的工夫距离。而递归 setTimeout 是调用时才开始算工夫,能够保障屡次递归调用时的距离雷同。 如果以后 JavaScript 线程阻塞,轮到的 setInterval 无奈执行,那么本次工作就会被抛弃。而 setTimeout 被阻塞后不会被抛弃,等到闲暇时会继续执行,但无奈保障执行距离。 3. 公布/订阅模式(publish-subscribe pattern)公布/订阅模式是一种对象间一对多的依赖关系,当一个对象的状态产生扭转时,所有依赖于它的对象都将失去状态扭转的告诉。 下面异步回调的例子也是一个公布/订阅模式(publish-subscribe pattern)的实现。订阅 btn 的 click 事件,当 btn 被点击时向订阅者发送这个音讯,执行对应的操作。 ...

October 30, 2021 · 2 min · jiezi

关于异步:以两种异步模型应用案例深度解析Future接口

摘要:本文以理论案例的模式剖析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类。本文分享自华为云社区《【精通高并发系列】两种异步模型与深度解析Future接口(一)!》,作者:冰 河 。 本文以理论案例的模式剖析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类,心愿大家踏下心来,关上你的IDE,跟着文章看源码,置信你肯定播种不小! 一、两种异步模型在Java的并发编程中,大体上会分为两种异步编程模型,一类是间接以异步的模式来并行运行其余的工作,不须要返回工作的后果数据。一类是以异步的模式运行其余工作,须要返回后果。 1.无返回后果的异步模型无返回后果的异步工作,能够间接将工作丢进线程或线程池中运行,此时,无奈间接取得工作的执行后果数据,一种形式是能够应用回调办法来获取工作的运行后果。 具体的计划是:定义一个回调接口,并在接口中定义接管工作后果数据的办法,具体逻辑在回调接口的实现类中实现。将回调接口与工作参数一起放进线程或线程池中运行,工作运行后调用接口办法,执行回调接口实现类中的逻辑来处理结果数据。这里,给出一个简略的示例供参考。 定义回调接口package io.binghe.concurrent.lab04;/** * @author binghe * @version 1.0.0 * @description 定义回调接口 */public interface TaskCallable<T> { T callable(T t);}便于接口的通用型,这里为回调接口定义了泛型。 定义工作后果数据的封装类package io.binghe.concurrent.lab04;import java.io.Serializable;/** * @author binghe * @version 1.0.0 * @description 工作执行后果 */public class TaskResult implements Serializable { private static final long serialVersionUID = 8678277072402730062L; /** * 工作状态 */ private Integer taskStatus; /** * 工作音讯 */ private String taskMessage; /** * 工作后果数据 */ private String taskResult; //省略getter和setter办法 @Override public String toString() { return "TaskResult{" + "taskStatus=" + taskStatus + ", taskMessage='" + taskMessage + '\'' + ", taskResult='" + taskResult + '\'' + '}'; }}创立回调接口的实现类回调接口的实现类次要用来对工作的返回后果进行相应的业务解决,这里,为了不便演示,只是将后果数据返回。大家须要依据具体的业务场景来做相应的剖析和解决。 ...

July 30, 2021 · 8 min · jiezi

关于异步:处理实时搜索-异步数据问题

最近在写我的项目的过程中,遇到一个问题。就是实时搜寻,然而异步数据返回的工夫不统一,导致,搜寻的后果和文本其实并不能齐全匹配解法一在申请胜利后,判断参数是否和搜寻条件统一,如果统一,才 setState。这种解法 解法二应用申请库的 cancel 办法 axios cancel: https://github.com/axios/axio... umi-request cancel: https://github.com/umijs/umi-... 很显著,2 的解法要比 1 的解法高级很多

December 18, 2020 · 1 min · jiezi

关于异步:问世间异步为何物

异步定义对于异步的定义,网上有很多不同的模式,然而归根结底中心思想是不变的。无论是在http申请调用的层面,还是在cpu内核态和用户态传输数据的层面,异步这个行为针对的是调用方: 一个能够无需期待被调用方的返回值就让操作持续进行的办法在少数程序员的概念中个别是指线程解决的层面: 异步是计算机多线程的异步解决。与同步解决绝对,异步解决不必阻塞以后线程来期待解决实现,而是容许后续操作,直至其它线程将解决实现,并回调告诉此线程 能够这样艰深的了解,异步次要解决的问题是不阻塞调用方,调用方这里能够是http申请的发起者,也能够是一个线程。 但此处须要明确的是:异步与多线程与并行不是同一个概念。 CPU密集型操作我听有的同学说,异步解决的是IO密集型的操作,菜菜感觉是不精确的。异步同样能够解决CPU密集型操作,只不过场景无限而已。有一个前提:利用异步解决CPU密集型操作要求以后运行环境反对多线程才行,比方javascript这个语言,实质上它的运行环境是单线程的,所以对于CPU密集型操作,javascript会显得力不从心。 异步解决CPU密集操作个别状况下产生在同过程中,为什么这么说呢,如果产生在不同机器或者不同过程在很多状况下曾经属于IO密集型的范畴了。这里顺便揭示一下:IO操作可不单单是指磁盘的操作,所有有输出/输入(Input/Output)操作的都能够泛称为IO。 举个栗子吧:在一个带有UI的软件上点击一个按钮,UI线程会产生操作行为,如果UI线程在执行过程中有一个计算比拟耗时的操作(你能够设想成计算1--999999999的和),UI线程在同步操作的状况下会始终期待计算结果,在计算结束之后才会继续执行残余操作,在期待的这个过程中,出现给用户的状况就是UI卡住了,俗称假死了,带给用户的体验是十分不好的。这种状况下,咱们能够新启动一个线程去执行这个耗时的操作,当执行结束,利用某种告诉机制来告诉原来线程,以便原来线程持续本人的操作。 启动新线程执行CPU密集型操作利用的其实就是多线程的劣势,如果是单核CPU,其实这种劣势并不显著IO密集型操作异步的劣势在IO密集型操作中体现的酣畅淋漓,无论是读取一个文件还是发动一个网络申请,菜菜的倡议是尽量应用异步。这里首先遍及一个小常识:其实每个外设设施都有本人的处理器,比方磁盘,所以每个外设设施都能够解决本人相应的申请操作。然而解决外设设施信息的速度和cpu的执行速度来比拟有着天壤之别。 上图展现了不同的 IO 操作所占用的 CPU 时钟周期,在计算机中,CPU 的运算速度最快,以其的运算速度为基准,时钟周期为1。其次是一级缓存、二级缓存和内存,硬盘和网络最慢,它们所破费的时钟周期和内存所破费的时钟周期差距在五位数以上,更不必提跟 CPU 和一级缓存、二级缓存的差距了。 因为速度的差距,所以简直所有的IO操作都举荐应用异步。比方当读取磁盘一个文件的时候,同步状态下以后线程在期待读取的后果,这个线程闲置的工夫简直能够用蛋疼来形容。所以古代的简直所有的出名第三方的操作都是异步操作,尤其以Redis,Nodejs 为代表的单线程运行环境令人另眼相看。 当初是微服务流行的时代,UI往往一个简略的按钮操作,其实在后台程序可能调用了几个甚至更多的微服务接口(对于微服务这里不开展),如果程序是同步操作的话,那响应工夫是这些服务接口响应工夫的和,然而如果采纳的是异步操作,调用方能够在霎时把调用服务接口的操作发送进来,线程能够继续执行下边代码或者期待所有的服务接口返回值也能够。最差的状况下,接口的响应工夫为最慢的那个服务接口响应工夫,这有点相似于木桶效应。 异步的回调通过以上介绍,咱们肯定要记住一个知识点:异步须要回调机制。异步操作之所以能在执行后果实现之后继续执行上面程序齐全归功于回调,这也是所有异步场景的外围所在,前到js的异步回调,后到cpu内核空间copy数据到用户空间实现告诉 等等异步场景,回调无处不在。说道回调大部分语言都是注册一个回调函数,比方js会把回调的办法注册到执行的队列,c#会把回调注册到IOCP。这里延长一下,在很多零碎里,很多IO网络模型其实是属于同步领域的,比方多路复用技术,真正异步非阻塞的举荐windows下的IOCP。 当初很多古代语言都反对更优良的回调形式,比方js和c# 当初都反对async 和await形式来进行异步操作。 据说windows下的IOCP才是真正的异步非阻塞模型,求留言区验证! 异步的特点劣势异步操作毋庸额定的线程累赘,应用回调的形式进行后续解决,在设计良好的状况下,处理函数能够不用应用共享变量(即便无奈齐全不必,最起码能够缩小 共享变量的数量),缩小了死锁的可能。线程数量的缩小,缩小了线程上下文在cpu切换的开销。微服务环境(调用多个服务接口的状况下)放慢了下层接口的响应工夫,意味着减少了下层接口的吞吐量劣势异步操作传统的做法都是通过回调函数来实现,与同步的思维有些差别,而且难以调试如果以后环境有操作程序的要求,异步操作为了保障执行的程序须要做额定的工作因为少数状况下异步的回调过程中的执行线程并非原来的线程,所以在捕捉异样,上下文传递等方面须要做非凡解决,特地是不同线程共享代码或共享数据时容易出问题。写在最初在并发量较小的状况下,阻塞式 IO和异步IO的差距可能不是那么显著,但随着并发量的减少,异步IO的劣势将会越来越大,吞吐率和性能上的差距也会越来越显著。在压力比拟小的状况下,个别异步申请的响应工夫大于同步申请的响应工夫,因为异步的回调也是须要工夫的在大并发的状况下,采纳异步调用的程序所用线程数要远远小于同步调用程序所用的线程数,cpu使用率也一样(因为防止了太多线程上下文切换的老本)为了零碎性能,不要让任何设施停下来劳动更多精彩文章 分布式大并发系列架构设计系列趣学算法和数据结构系列设计模式系列

September 24, 2020 · 1 min · jiezi

关于异步:阻塞与非阻塞-同步与异步

同步与异步我了解同步与异步是指被调用者一端的,同步与异步在意的是音讯通信机制,例如程序调用了一个办法,同步与异步指的是该办法会以怎么样的模式进行返回,同步:就是在该办法没有运行出后果前都不会返回,但一返回就有后果;异步:就是在该办法运行后就会返回,然而没有返回后果,会通过回调的机制返回运行后果;例子:若你在网上买了一个商品.然而呈现了售后问题,你给客服发了音讯询问处理结果,若是同步模式,客服和你说他去询问一下售后工程师处理结果,而后你就始终等啊等,直到他们探讨出后果告知你才算完结;而若是异步模式,客服和你说他去询问一下售后工程师处理结果,有了后果再给你回电话,而后此次通话就到此结束,第二天早上他给你打电话告知后果. 阻塞与非阻塞我了解阻塞与非阻塞是在调用者一端的,例如程序调用了一个办法,阻塞与非阻塞指的是以后程序在期待调用后果时的状态,若肯定要等到返回调用后果该线程才会返回,等于将线程挂起,这即是阻塞;若调用不会立即失去后果,该次调用也不会妨碍线程的运行,即是非阻塞;例子: 若你在网上买了一个商品.然而呈现了售后问题,你给客服发了音讯询问处理结果,若你是阻塞式的,不论客服有没有分割到售后能不能给你后果,你都会始终期待;而若你是非阻塞的,当你询问客服后,不论她有没有立即回复,你就先去忙本人的事件,然而也会偶然回来查看后果.

September 18, 2020 · 1 min · jiezi

关于异步:Spring-AOP-异步操作

Spring业务的异步实现在基于注解形式的配置中,借助@EnableAsync注解进行异步启动申明(1)在Spring Boot我的项目中,在启动类Application上利用@EnableAsync注解(2)在须要异步执行的业务办法上,应用@Async办法进行异步申明如果须要获取业务层异步办法的执行后果,AsyncResult对象能够对异步办法的执行后果进行封装,如果外界须要异步办法后果时,能够通过Future对象的get办法获取后果 Spring 自定义异步池import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.ThreadFactory;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;public class TestThreadPool { public static void main(String[] args) { BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<Runnable>(1);//线程容量:阻塞队列数+最大线程数 //如果默认线程名不满足你业务须要,能够本人创立线程工厂,而后定义线程名 ThreadFactory threadFactory=new ThreadFactory() { //AtomicLong 提供了一种线程平安的自增或自减算法对一个整数进行计算 private AtomicLong al=new AtomicLong(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "db-thread-"+al.getAndIncrement()); } }; //1.构建一个线程池 ThreadPoolExecutor tp=new ThreadPoolExecutor( 2,//corePoolSize 外围线程数(当应用池对象执行工作时,池中线程没有达到corePoolSize设置定值时,每来一个新的工作都会创立一个新的线程) 3,//maximumPoolSize 最大线程(当外围线程都在忙,队列也满,再来新的工作则创立新线程) 60,//keepAliveTime 最大闲暇工夫 TimeUnit.SECONDS, //unit 工夫单位 workQueue,//workQueue阻塞式队列 threadFactory,//创立线程的工厂 new ThreadPoolExecutor.CallerRunsPolicy());//工作拒绝执行策略,这里抉择了CallerRunsPolicy对象(示意最初由调用者线程执行) //2.启动池中线程执行工作 tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 01"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 02"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 03"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 04"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 05"); try{Thread.sleep(5000);}catch (Exception e) {} } }); }}执行后果 ...

September 17, 2020 · 1 min · jiezi

关于异步:宏任务macroTask和微任务microTask

宏工作:setTimeout,setInterval,ajax,DOM事件微工作:Promise,async/await微工作执行机会比宏工作早console.log(100)//宏工作setTimeout(()=>{ console.log(200)})//微工作Promise.resolve.then(()=>{ console.log(300)})console.log(400)//打印程序:100,400,300,200 为什么微工作比宏工作执行的早宏工作:DOM渲染后触发,如setTimeOut微工作:DOM渲染前触发,如Promise宏工作和微工作的基本区别宏工作是浏览器规定的;微工作是es6语法规定的;微工作期待机会放在micro task queue中,首先执行同步代码,当代码执行实现,call stack清空之后,执行以后的微工作,再尝试DOM渲染,最初触发event loop机制;

September 8, 2020 · 1 min · jiezi