手机版

理解JS事件循环

时间:2021-09-19 来源:互联网 编辑:宝哥软件园 浏览:

随着web浏览器脚本语言JavaScript的流行,基本了解它的事件驱动交互模型,以及它与Ruby、Python、Java中常见的请求-响应模型的区别,对你是有好处的。在本文中,我将解释JavaScript并发模型的一些核心概念,包括它的事件循环和消息队列,希望能提高您对一种您可能已经使用过但可能还没有完全理解的语言的理解。

这篇文章是写给谁的?

本文面向在客户端或服务器端使用或计划使用JavaScript的web开发人员。如果你已经掌握了事件周期,这篇文章的大部分内容你都会很熟悉。对于不是很精通的人,希望能给大家提供一个基本的了解,从而更好的帮助大家阅读和编写日常代码。

非阻塞输入输出

在JavaScript中,几乎所有的I/O都是非阻塞的。这包括HTTP请求、数据库操作和磁盘读写。单线程执行需要在运行时执行操作时提供回调函数,然后继续做其他事情。操作完成后,消息将与提供的回调函数一起插入队列。在未来的某个时候,消息将从队列中移除,回调函数将会触发。

尽管这种交互模型对于习惯于使用用户界面的开发人员来说可能是熟悉的,例如,“mousedown”和“click”事件是在某个时刻触发的。这不同于通常在服务器端应用程序中执行的同步请求-响应模型。

让我们比较两段小代码,向www.google.com发送一个HTTP请求,向控制台输出一个响应。首先看看Ruby,使用Faraday(一个Ruby HTTP客户端开发库):

response=Faraday . get ' http://www . Google.com ' puts response puts ' Done!执行路径很容易跟踪:

1.执行get方法,执行线程等待,直到收到响应。2 .从Google接收响应并将其返回给调用者,该响应存储在一个变量中。3 .将变量的值(在本例中是我们的响应)输出到控制台。4 .值“Done!”输出到控制台,让我们使用Node.js和Request库在JavaScript 3360中做同样的事情。

request('http://www.google.com ',函数(错误、响应、正文){ console.log(正文);});控制台. log('完成!');表面上看略有不同,但实际行为却大相径庭。

1.执行请求的函数,传递一个匿名函数作为回调,并在将来某个时候响应可用时执行回调。2、“完成!”立即输出到控制台3。在将来的某个时候,当响应返回并执行回调时,将其内容输出到控制台事件循环。

调用者和响应的解耦使JavaScript能够在等待异步操作完成和回调在运行时触发的同时做其他事情。但是这些回调是如何在内存中组织的,又是以什么顺序执行的呢?是什么导致他们被称为?

JavaScript运行时包含一个消息队列,其中存储了要处理的消息列表和相关的回调函数。这些消息以队列的形式响应回调函数中涉及的外部事件(如鼠标点击或对HTTP请求的响应)。例如,如果用户单击了一个按钮,但没有提供回调函数,则不会向队列中添加任何消息。

在一个周期中,队列提取下一条消息(每次提取都被称为“滴答”),当事件发生时,执行该消息的回调。

回调函数作为调用堆栈中的初始化框架被调用。由于JavaScript是单线程的,未来的消息提取和处理将会因为等待堆栈上的所有调用返回而停止。后续(同步)函数调用向堆栈中添加新的调用帧(例如,函数init调用函数changeColor)。

function init(){ var link=document . getelementbyid(' foo ');link.addEventListener('click ',function change color(){ this . style . color=' burlywood ';});} init();在本例中,当用户单击“foo”元素时,一条消息(及其回调函数changeColor)将被插入队列并触发“onclick”事件。当消息离开队列时,它的回调函数changeColor被调用。当changeColor返回(或抛出错误)时,事件循环继续。只要函数changeColor存在,并且被指定为“foo”元素的onclick方法的回调,那么点击这个元素就会导致更多的消息(以及相关的回调changeColor)被插入到队列中。

队列附加消息。

如果在代码中异步调用一个函数(如setTimeout),提供的回调最终将作为不同消息队列的一部分执行,并且它将在事件循环的某些未来操作中发生。例如:

函数f(){ console . log(' foo ');setTimeout(g,0);console . log(' baz ');h();}函数g(){ console . log(' bar ');}函数h(){ console . log(' Blix ');} f();由于setTimeout的非阻塞特性,它的回调将在至少0毫秒后被触发,而不是作为消息的一部分被处理。在本例中,调用setTimeout,传入回调函数g,并在0毫秒的延迟后执行。当我们指定的时间到达时(在当前情况下,它几乎是立即执行的),一条消息将被添加到队列中(g作为回调函数)。控制台打印出来的结果会是这样的:“foo”、“baz”、“blix”,然后事件循环的下一个动作就是:“bar”。如果在同一个调用片段中,两个调用都被设置为setTimeout,并且传递给第二个参数的值是相同的,那么它们的回调将按照调用的顺序插入队列。

网络工作者

使用Web Workers允许您在单个线程中执行耗时的操作,从而释放主线程来做其他事情。Worker (worker thread)包括一个独立的消息队列,事件在循环中循环,它的内存空间独立于实例化它的原始线程。工作线程和主线程之间的通信是通过消息传输进行的,这看起来像我们通常的传统事件代码示例。

首先,我们的工人。

//我们的工作人员,该工作人员执行一些CPU密集型操作var reportResult=function(e){ pi=somelib.computeCignatedcimals(e . data);后期消息(pi);};onmessage=reportResult然后,主要代码块作为脚本标签存在于我们的HTML中:

//我们的主要代码,在我们的HTML页面中的一个脚本标签中piWorker=new Worker(' pi _ calculator . js ');var logResult=function(e){ console . log(' PI: ' e . data);};piworker . addeventlistener(' message ',logResult,false);piworker . postmessage(100000);在这个例子中,主线程创建一个工作线程,并将logResult回调函数注册到它的“消息”事件中。在工作程序中,reportResult函数在其自己的“消息”事件中注册。当工作线程从主线程接收到消息时,工作线程使用reportResult回调函数加入一条消息。当消息出列时,新消息被发送回主线程,新消息在主线程队列中排队(带有logResult回调函数)。这样,开发人员可以将cpu密集型操作委托给一个线程,从而释放主线程来继续处理消息和事件。

关于闭包。

JavaScript闭包的支持允许您以这种方式注册回调函数,并且当回调函数被执行时,您保持对创建它们的环境的访问(即使在回调被执行时创建了一个全新的调用堆栈)。理解我们的回调是作为不同消息的一部分执行的,而不是创建它的消息的一部分,这将是很有趣的。请看下面的例子:

函数changeheaderferred(){ var header=document . getelementbyid(' header ');setTimeout(函数change header(){ header . style . color=' red ';返回false}, 100);返回false} changeheaderferred();在本例中,changeHeaderDeferred函数在执行时包含变量头。调用setTimeout函数,使消息(带有changeHeader回调)被添加到消息队列中,并在大约100毫秒后执行。然后changeHeaderDeferred函数返回false,结束对第一条消息的处理,但是头变量仍然可以通过闭包引用,而不是被垃圾收集。当第二个消息被处理时(changeHeader函数),它保持对在外部函数范围内声明的头变量的访问。一旦第二条消息(changeHeader函数)的执行完成,就可以对Header变量进行垃圾收集。

提醒

JavaScript的事件驱动交互模型不同于很多程序员使用的请求-响应模型,但是可以看到,它并不复杂。使用简单的消息队列和事件循环,JavaScript使开发人员能够在构建系统时使用大量异步触发的回调函数,这样运行时环境就可以在等待外部事件触发的同时处理并发操作。然而,这只是一种并发的方法。

以上就是本文的全部内容,希望对大家的学习有所帮助。

版权声明:理解JS事件循环是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。