当前位置: 首页 > Web前端 > vue.js

JavaScript的内部原理:浏览器的内幕

时间:2023-04-01 01:58:13 vue.js

作者:VladOstrenko译者:前端小智来源:mediumum点赞阅读,养成习惯本文已收录在GitHubhttps://github.com/qq44924588...以前好评文章的更多分类,也整理了很多我的文档和教程资料。欢迎来到星和完美。面试时可参考考点复习。我希望我们能在一起。简介Javascript是一种奇怪的语言,有人喜欢也有人讨厌。它有很多其他流行语言所没有的独特机制,也没有对应的机制,最明显的就是代码的执行顺序。了解浏览器环境,它的组成以及它是如何工作的,会让我们在写JS的时候更有信心,为可能出现的潜在问题做好充分的准备。在这篇文章中,我们试图解释在Chrome浏览器中发生了什么,让我们来看看:V8Javascript引擎编译步骤、堆和内存管理、调用堆栈。浏览器运行时并发模型、事件循环、阻塞和非阻塞代码。JavaScript引擎最流行的JavaScript引擎是V8,它是用C++编写的,被基于Chrome的浏览器(如Chrome、Opera甚至Edge)使用。基本上,这个引擎是一个将JS转换为机器代码并在计算机的中央处理器(CPU)上执行结果的程序。编译当浏览器加载JS文件时,V8的解析器将其转换为抽象语法树(AST)。这棵树被生成字节码的解释器使用。字节码是机器代码的抽象,可以通过编译为非优化的机器代码来执行。V8在主线程中进行,而优化编译器TurboFan在另一个线程中进行一些优化并生成优化的机器代码。此管道称为即时(JIT)编译。调用堆栈JavaScript是一种单线程编程语言,只有一个调用堆栈。这意味着我们的代码是同步执行的。每当一个函数运行时,它会在任何其他代码运行之前完全运行。当V8调用JS函数时,它必须将运行时数据存储在某个地方。调用堆栈是内存中由堆栈帧组成的位置。每个堆栈帧对应一个尚未被调用的函数。堆栈结构由以下部分组成:局部变量argumentargument返回地址如果我们执行一个函数,V8会将帧压入堆栈顶部。当我们从函数返回时,V8跳出框架。如上例所示,在每次函数调用时都会创建一个框架,并在每次返回语句时删除。其他所有内容都动态分配到一个称为堆的大型非结构化内存块中。堆(Heap)有时V8在编译时并不知道一个对象变量需要多少内存。此类数据的所有内存分配都发生在堆上。退出分配内存的函数后,堆上的对象继续存在。V8有一个内置的垃圾收集器(GC)。垃圾收集是内存管理的一种形式。它就像一个收集器,试图释放不再使用的对象占用的内存。换句话说,当一个变量失去所有引用时,GC会将那块内存标记为不可访问并释放它。我们可以通过在Chrome开发工具中创建快照来研究堆。每个实例化的JS对象都分组在其构造函数类下。括号中的分组表示不能直接调用的本机构造函数。可以看到有很多(编译代码)和(系统)实例,还有一些传统的JS对象,比如Math、String、Array等。浏览器运行时V8可以按照标准使用调用栈来同步执行JS。但是,我们需要渲染UI并需要处理用户与UI的交互。此外,我们需要在发出网络请求时处理用户交互,对此我们无能为力。当所有代码都同步时,我们如何实现并发?感谢浏览器引擎。浏览器引擎负责渲染带有HTML和CSS的页面。在Chrome中它被称为Blink。作为WebCore的一个分支,Blink是一个布局、渲染和文档对象模型(DOM)库。Blink是用c++实现的,它提供了DOM元素和事件、XMLHttpRequest、fetch、setTimeout、setInterval等WebAPI,可以通过JS访问。让我们考虑以下使用setTimeout(onTimeout,0)的示例:正如您所见,浏览器首先将f1()和f2()函数压入堆栈,然后执行onTimeout。那么上面的例子是如何工作的呢?并发setTimeout函数执行后,浏览器引擎立即将setTimeout回调函数放入事件表中。它是一种将注册的回调映射到事件的数据结构,在我们的例子中,onTimeout函数映射到超时事件。一旦计时器到期,在本例中我们将延迟设置为0毫秒,事件将立即触发并将onTimeout函数放入事件队列(也称为回调队列、消息队列或任务队列)。事件队列是由将来要处理的回调函数(任务)组成的数据结构。最后但同样重要的是,事件循环(一个连续运行的循环)检查调用堆栈是否为空。如果是,则执行从事件队列添加的第一个回调,从而向上移动调用堆栈。函数的处理继续进行,直到调用堆栈再次为空。然后事件循环将处理事件队列中的下一个回调(如果有的话)。constfn1=()=>console.log('fn1')constfn2=()=>console.log('fn2')constcallback=()=>console.log('timeout')fn1()setTimeout(callback,1000)fn2()//运行结果//fn1//fn2//timeout注意onResolve1、onResolve2、onTimeout回调的执行顺序。阻塞和非阻塞简单的说,所有的JS代码都被认为是阻塞的。当V8忙于处理堆栈帧时,浏览器卡住了,应用程序的UI被阻塞了。用户将无法单击、导航或滚动。在V8完成其工作之前,不会处理来自网络请求的响应。想象一下,我们正在浏览器中运行的程序中解析图像。constfn1=()=>console.log('fn1')constonResolve=()=>console.log('resolved')constparseImage=()=>{/*这里会长时间运行解析算法*/}fn1()Promise.resolve().then(onResolve)//或任何其他WebAPI异步函数parseImage()在上面的示例中,事件循环被阻止。它无法处理事件/作业队列中的回调,因为调用堆栈包含此帧。WebAPI为我们提供了通过异步回调编写非阻塞代码的可能性。当调用setTimeout或fetch等函数时,我们将所有工作委托给在单独线程中运行的C++本机代码。操作完成后,回调将放入事件队列。同时,V8可以继续执行JS代码。使用这种并发模型,我们可以在不阻塞JS执行线程的情况下处理网络请求、用户与UI的交互等。总结对于每个希望能够解决复杂任务的开发人员来说,理解JS环境的组成是至关重要的。现在我们知道了异步JavaScript是如何工作的,调用堆栈、事件循环、事件队列和作业队列在其并发模型中的作用。正如您可能已经猜到的那样,V8引擎和浏览器引擎的幕后工作还有很多。然而,我们大多数人只需要对所有这些概念有一个基本的了解。如果以上文章对您有帮助,请点击“正在看”。代码部署后可能存在的bug,无法实时获知。事后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的bug监控工具Fundebug。https://medium.com/better-pro...每周更新交流文章。可以微信搜索“大千世界”阅读即时更新(比博文早一两篇)。这篇文章在GitHubhttps://github.com/qq449245884/xiaozhi已经收录,整理了很多我的文档。欢迎star和改进。可以参考考点面试。另外,关注公众号,后台会回复福利,看到福利就知道了。