对于任何一个程序员来说,最关心的无非就是两个问题:时间复杂度和空间复杂度。第一部分介绍了V8为缩短JavaScript执行时间所做的加速和优化,而第二部分则侧重于内存管理。在本文中,我简要概述了一种编程语言的一般工作原理,并深入研究了V8引擎的管道。第二部分将介绍一些每个JavaScript程序员都必须了解的更重要的概念,它不仅与V8引擎有关。内存堆Orinoco的标志:V8的垃圾收集器每当你在JavaScript程序中定义一个变量、常量或对象时,你都需要一个地方来存储它。这个地方就是内存堆。当遇到语句vara=10时,内存会分配一个位置来存放a的值。可用内存是有限的,复杂的程序可能有很多变量和嵌套对象,因此合理使用可用内存非常重要。与像C这样需要显式分配和释放内存的语言不同,JavaScript提供了自动垃圾收集。一旦对象/变量离开上下文不再使用,它??的内存就会被回收并返回到空闲内存池中。在V8中,垃圾收集器被称为Orinoco,其处理效率非常高。本文讲解MarkandSweepAlgorithmMarkandSweepAlgorithmMarkandSweepAlgorithm我们通常使用这种简单有效的算法来确定哪些对象可以安全地从内存堆中清除。该算法的工作原理与其名称相同:对象被标记为可到达/不可到达,不可到达的对象将被清除。定期地,垃圾收集器从根对象或全局对象开始,移动到它们引用的对象,然后移动到这些对象引用的对象,依此类推。之后将清除所有无法访问的对象。内存泄漏尽管垃圾收集器非常高效,但开发人员不应该对内存管理问题置之不理。管理内存是一个非常复杂的过程,哪一块内存不再需要了,不能通过单一的算法来判断。内存泄漏是指程序之前需要使用部分内存,而这部分内存用完后没有归还到内存池中。以下是一些可能导致程序内存泄漏的常见错误:全局变量:如果您不断创建全局变量,无论是否使用它们,它们都会在程序的整个执行过程中一直存在。如果这些变量是深度嵌套的对象,就会浪费大量的内存。vara={...}varb={...}functionhello(){c=a;//这是一个你没有意识到的全局变量}如果你试图访问一个之前没有声明过的变量,它将在全局范围内创建一个变量。在上面的示例中,c是一个未使用var关键字显式创建的变量/对象。事件监听器:为了增强网站的交互性或者制作一些花哨的动画,你可能会创建大量的事件监听器。而当用户移动到你的单页应用程序中的其他页面时,你忘记移除这些监听器,这也可能导致内存泄漏。当用户在这些页面之间来回移动时,这些侦听器会增加。varelement=document.getElementById('button');element.addEventListener('click',onClick)间隔和超时:当在这些闭包中引用对象时,相关对象将不会被清除,除非闭包本身被清除。setInterval(()=>{//referenceobject}//这个时候忘记清除定时器//那么会造成内存泄漏!移除DOM元素:这个问题很常见,类似于全局变量造成的内存泄漏.DOM元素存在于objectgraphmemory和DOMtree中。最好用一个例子来解释:varterminator=document.getElementById('terminate');varbadElem=document.getElementById('toDelete');terminator.addEventListener('click',function(){memorybadElem.remove();});当你点击id='terminate'的按钮后,toDelete会从DOM中移除。但是,由于它仍然被监听器引用,分配的内存对于这个对象是不会被释放的。varterminator=document.getElementById('terminate');terminator.addEventListener('click',function(){varbadElem=document.getElementById('toDelete');badElem.remove();});操作完成后,内存将被垃圾收集器回收。调用堆栈是一种数据结构,它遵循LIFO(先进后出)规则来存储和检索数据。JavaScript引擎使用堆栈来记住函数中最后执行的语句所在的位置。functionmultiplyByTwo(x){returnx*2;}functioncalculate(){constsum=4+2;returnmultiplyByTwo(sum);}calculate()varhello="somemorecodefollows"1.引擎知道我们的程序中有两个函数2.运行calculate()函数3.calculate入栈,计算两个数的和4.运行multiplyByTwo()函数5.multiplyByTwo函数入栈,进行算术运算x*26。在返回结果的同时,将multiplyByTwo()出栈7.当calculate()函数返回结果时,从栈中弹出calculate()并继续执行下面的代码。连续压入的次数取决于栈的大小。如果超过这个限制后继续压栈,最终会导致栈溢出。chrome浏览器会抛出一个错误和一个称为栈帧的栈快照。递归:递归是指函数调用自身。递归可以大大减少执行算法所需的时间(时间复杂度),但理解和实现起来很复杂。在下面的例子中,baseevent一直没有执行,lonley函数一直调用自己没有返回值,最终导致栈溢出。functionlonely(){if(false){return1;//基本事件}lonely();//递归调用}为什么JavaScript是单线程的?一个线程代表同一时间段内可以独立执行的程序部分的数量。找出一种语言是单线程还是多线程的最简单方法是知道它有多少个调用堆栈。JS只有一个,所以它是一种单线程语言。这不会妨碍程序运行吗?如果我运行多个耗时的阻塞操作,比如HTTP请求,那么程序必须在每个操作被响应后执行下面的代码。为了解决这个问题,我们需要想办法在单线程下异步完成任务。事件循环就是用来扮演这个角色的。事件循环到目前为止,我们讨论的大部分内容都包含在V8中,但如果您查看V8代码库,它不包含setTimeout或DOM等实现。实际上,JS除了运行引擎外,还包括浏览器提供的WebAPI,用于扩展JS。总结:要制作一门编程语言还有很多话要说,多年来语言的实现也发生了变化。我希望这两个博客能帮助您成为更好的JS程序员并拥抱JS中的黑暗事物。您现在应该熟悉“V8”、“事件循环”、“调用堆栈”等术语。大多数学生(像我一样)从一个新的框架开始,然后转向原生JS。现在他们应该熟悉幕后发生的事情,这反过来将帮助他们编写更好的代码。
