这是JavaScript框架系列的第二篇。在本章中,我将讨论在浏览器中执行异步代码的不同方式。您将了解定时器和事件循环之间的不同之处,例如setTimeout和Promises。本系列是关于名为NX的开源客户端框架。在这个系列中,我主要阐述了编写框架所必须克服的主要困难。如果您对NX感兴趣,可以访问我们的主页。本系列包含以下章节:项目结构定时执行(当前章节)沙盒代码评估数据绑定简介数据绑定和ES6代理自定义元素客户端路由异步代码执行你可能熟悉Promise、process.nextTick()、setTimeout()、也许requestAnimationFrame()是异步执行代码的方法。它们都在内部使用事件循环,但在精确计时方面存在一些差异。在本章中,我将解释它们之间的区别,然后向您展示如何在NX这样的高级框架中实现计时系统。我们将使用本机事件循环来实现我们的目的,而不是创建一个新的。事件循环ES6规范中甚至没有提到事件循环。JavaScript本身只有任务(Job)和任务队列(jobqueue)。更复杂的事件循环在NodeJS和HTML5规范中分别定义,由于本文是针对前端的,所以我将详细解释后者。事件循环可以被认为是条件循环。它不断寻找要运行的新任务。此循环中的迭代称为滴答。在一个tick期间执行的代码称为任务。while(eventLoop.waitForTask()){eventLoop.processNextTask()}任务是同步代码,可以在循环中调度其他任务。调用新任务的一种简单方法是setTimeout(taskFn)。然而,任务可能来自许多来源,例如用户事件、网络或DOM操作。任务队列让事情变得更复杂一点的是,一个事件循环可以有多个任务队列。这里有两个约束,来自同一个任务源的事件必须在同一个队列中,任务必须按照插入的顺序进行处理。除此之外,浏览器可以做任何它想做的事情。例如,它可以决定接下来要处理哪个任务队列。while(eventLoop.waitForTask()){consttaskQueue=eventLoop.selectTaskQueue()if(taskQueue.hasNextTask()){taskQueue.processNextTask()}}使用这个模型,我们无法精确控制时间。如果您使用setTimeout(),浏览器可能会决定在运行我们的队列之前运行其他几个队列。微任务队列幸运的是,事件循环还提供了一个称为微任务队列的队列。当当前任务结束时,微任务队列将在每个滴答中清空任务。while(eventLoop.waitForTask()){consttaskQueue=eventLoop.selectTaskQueue()if(taskQueue.hasNextTask()){taskQueue.processNextTask()}constmicrotaskQueue=eventLoop.microTaskQueuewhile(microtaskQueue.hasNextMicrotask()){microtaskQueue.hasNextMicrotask()){microtask}调用微任务最简单的方法是Promise.resolve().then(microtaskFn)。微任务按插入顺序处理,并且由于只有一个微任务队列,浏览器不会弄乱时间。此外,微任务可以调度新的微任务,这些微任务将被插入到同一个队列中并在同一个tick内处理。DrawingRendering***正在绘制渲染调度。与事件处理和分解不同,绘图不是在单独的后台任务中完成的。这是一种在每个循环节拍结束时运行的算法。在这里,浏览器再次拥有很大的自由度:它可能会在每个任务之后绘制,但也可能在执行了数百个任务后才绘制。幸运的是,我们有requestAnimationFrame()可以在下一次绘制之前执行传递的函数。我们最终的事件模型是这样的:{microtaskQueue.processNextMicrotask()}if(shouldRender()){applyScrollResizeAndCSS()runAnimationFrames()render()}}现在使用我们所知道的来创建一个计时系统!利用事件循环像大多数现代框架一样,NX是基于DOM操作和数据绑定。批量操作和异步执行以获得更好的性能。由于上述原因,我们使用Promises,MutationObservers和requestAnimationFrame()。我们期望的计时器是这样的:代码来自开发人员数据绑定,DOM操作由NX开发人员执行定义事件钩子浏览器绘制步骤1NX基于ES6代理的注册对象和基于MutationObserver(变化观察者)的DOM变化同步运行(下一节详述)作为微延迟到步骤2执行之后的任务。这个defer在Promise.resolve().then(reaction)中有对象转换,它会通过mutationobserver自动运行。第2步开发人员的代码(任务)运行到完成。微任务由NX注册以开始执行。因为它们是微任务,所以它们是顺序执行的。请注意,我们仍在同一个滴答循环中。Step3.开发者通过requestAnimationFrame(hook)通知NX运行hook。这可能发生在滴答循环之后。挂钩在下一次绘制之前运行并且在所有数据操作、DOM和CSS更改完成之后运行很重要。第4步浏览器绘制下一个视图。这也可能发生在tick循环之后,但绝不会发生在tick的第3步之前。请记住,我们已经在本机事件循环之上实现了一个简单而有效的计时系统。理论上它工作得很好,但它仍然很脆弱,一个小错误就会导致严重的错误。在一个复杂的系统中,最重要的是建立一定的规则,并在以后维护它们。在NX中有以下规则:永远不要使用setTimeout(fn,0)进行内部操作使用相同的方法注册microtasksmicrotasks仅用于内部操作不干扰developerhooks运行时规则1和2数据反射和DOM操作将遵循操作顺序。只要它们不混合,这将很好地延迟它们的执行。混合执行会导致莫名其妙的问题。setTimeout(fn,0)的行为是完全不可预测的。使用不同的方法注册微任务也会引起混淆。例如,下例中的microtask2将不会在microtask1之前正确运行。Promise.resolve().then().then(microtask1)Promise.resolve().then(microtask2)规则3和4将开发者的代码执行和内部操作的时间窗口分开是非常重要的。混合使用这两种行为可能会导致不可预测的事情发生,并且需要开发人员了解框架内部结构。我想很多前端开发者都有过类似的经历。结束语如果您对NX框架感兴趣,可以访问我们的主页。我们的源代码也可以在GIT上找到。下一节见,我们将讨论沙盒代码执行!您也可以给我们留言。
