当前位置: 首页 > 科技观察

五个常见的JavaScript内存错误_0

时间:2023-03-15 13:35:15 科技观察

JavaScript不提供任何内存管理操作。相反,内存由JavaScriptVM通过称为垃圾收集的内存回收过程进行管理。既然我们不能强制垃圾收集,我们怎么知道它在工作?我们对它了解多少?脚本执行过程中暂停它为无法访问的资源释放内存它是非确定性的它不会一次检查所有内存,但在多个循环中运行它是不可预测的,但它会在必要时执行。这是否意味着无需担心资源和内存分配问题?当然不是。如果我们不小心,可能会有一些内存泄漏。什么是内存泄漏?内存泄漏是软件无法回收的已分配内存块。Javascript提供了垃圾收集器,但这并不意味着我们可以避免内存泄漏。要符合垃圾收集的条件,该对象不得在其他地方引用。如果保留对未使用资源的引用,这将防止回收未使用的资源。这称为无意识的记忆保留。内存泄漏可能会导致垃圾收集器更频繁地运行。由于这个过程会阻止脚本运行,可能会导致我们的程序卡死。如此卡顿,明眼人肯定会发现,一旦用起来不爽,这款产品下线的日子就没完没了。如果比较严重的话,可能会导致整个应用崩溃,所以gg。如何防止内存泄漏?最主要的是我们应该避免保留不必要的资源。让我们看看一些常见的场景。1.定时器监控setInterval()方法重复调用一个函数或执行一段代码,每次调用之间有固定的时间延迟。它返回一个唯一标识间隔的间隔ID,因此您可以稍后通过调用clearInterval()将其删除。我们创建一个调用回调函数的组件,以在x次循环后发出信号表示它已完成。我在此示例中使用的是React,但这应该适用于任何FE框架。importReact,{useRef}from'react';constTimer=({cicles,onFinish})=>{constcurrentCicles=useRef(0);setInterval(()=>{if(currentCicles.current>=cicles){onFinish();return;}currentCicles.current++;},500);return(

Loading...
);}exportdefaultTimer;乍一看,好像没什么问题。别着急,我们再创建一个触发这个定时器的组件,分析一下它的内存性能。importReact,{useState}from'react';importstylesfrom'../styles/Home.module.css'importTimerfrom'../components/Timer';exportdefaultfunctionHome(){const[showTimer,setShowTimer]=useState();constonFinish=()=>setShowTimer(false);return({showTimer?():(setShowTimer(true)}>重试)}
)}点击几次重试按钮后,这是使用ChromeDevTools获取内存使用情况的结果:当我们点击重试按钮时,我们可以看到越来越多的内存被分配。这意味着之前分配的内存还没有被释放。计时器仍在运行而不是被替换。如何解决这个问题呢?setInterval的返回值是一个区间ID,我们可以用它来取消这个区间。在这种特殊情况下,我们可以在组件卸载后调用clearInterval。useEffect(()=>{constintervalId=setInterval(()=>{if(currentCicles.current>=cicles){onFinish();return;}currentCicles.current++;},500);return()=>clearInterval(intervalId);},[])有时候,在写代码的时候,很难发现这个问题。最好的方法是抽象组件。在这里使用React,我们可以将所有这些逻辑包装在一个自定义Hook中。import{useEffect}from'react';exportconstuseTimeout=(refreshCycle=100,callback)=>{useEffect(()=>{if(refreshCycle<=0){setTimeout(callback,0);return;}constintervalId=setInterval(()=>{callback();},refreshCycle);return()=>clearInterval(intervalId);},[refreshCycle,setInterval,clearInterval]);};exportdefaultuseTimeout;现在当你需要使用setInterval时,你可以这样做:constandleTimeout=()=>...;useTimeout(100,handleTimeout);现在你可以使用这个useTimeoutHook而不必担心内存泄漏,这也是抽象的好处。2.事件监听WebAPI提供了大量的事件监听器。早些时候,我们讨论了setTimeout。现在让我们看看addEventListener。在这个例子中,我们创建了一个键盘快捷键功能。由于我们在不同的页面有不同的功能,所以会创建不同的快捷键功能functionhomeShortcuts({key}){if(key==='E'){console.log('editwidget')}}//用户登录在主页上,我们执行document.addEventListener('keyup',homeShortcuts);//用户做了一些事情,然后导航到设置functionsettingsShortcuts({key}){if(key==='E'){console.log('editsetting')}}//用户在首页登录,我们执行document.addEventListener('keyup',settingsShortcuts);它看起来仍然不错,只是在执行第二个addEventListener时没有清除之前的keyup。此代码将添加另一个回调,而不是替换我们的keyup侦听器。这意味着,当按下一个键时,它会触发两个功能。要清除之前的回调,我们需要使用removeEventListener:document.removeEventListener('keyup',homeShortcuts);重构以上代码:functionhomeShortcuts({key}){if(key==='E'){console.log('editwidget')}}//userlandsonhomeandweexecutedocument.addEventListener('keyup',homeShortcuts);//userdoessomestuffandnavigatesosesettingsfunctionsettingsShortcuts({key}){if(key==='E'){console.log('editsetting')}}//userlandsonhomeandweexecuteddocument.removeEventListener('keyup',homeShortcuts);document.addEventListener('keyup',settingsShortcuts);根据经验,使用全局对象中的工具时需要非常小心。3.ObserversObservers是一个浏览器的WebAPI函数,很多开发者不知道。如果您想检查HTML元素的可见性或大小的变化,这将非常有用。IntersectionObserver接口(IntersectionObserverAPI的一部分)提供了一种异步观察目标元素与其祖先元素或顶级文档视口的交集状态的方法。祖先元素和视口称为根。虽然它很强大,但我们还是要谨慎使用。一旦你观察完一个对象,记得在不使用时取消它。看代码:constref=...constvisible=(visible)=>{console.log(`Itis${visible}`);}useEffect(()=>{if(!ref){return;}observer。当前=newIntersectionObserver((entries)=>{if(!entries[0].isIntersecting){visible(true);}else{visbile(false);}},{rootMargin:`-${header.height}px`},);observer.current.observe(ref);},[ref]);上面的代码看起来不错。然而,一旦组件被卸载,观察者会发生什么?它不会被清除,内存泄漏。我们如何解决这个问题?只需使用断开连接方法:constref=...constvisible=(visible)=>{console.log(`Itis${visible}`);}useEffect(()=>{if(!ref){return;}observer.current=newIntersectionObserver((entries)=>{if(!entries[0].isIntersecting){visible(true);}else{visbile(false);}},{rootMargin:`-${header.height}px`},);observer.current.observe(ref);return()=>observer.current?.disconnect();},[ref]);4.Window对象向Window添加对象是一个常见的错误。在某些情况下,可能很难找到它,尤其是在窗口执行上下文中使用this关键字时。看看下面的例子:取决于您从哪个上下文调用addElement。如果您从窗口上下文中调用addElement,它将越来越多。另一个问题可能是一个全局变量定义错误:vara='example1';//作用域被限制在创建var的地方b='example2';//添加到Window对象中为了防止这个问题,你可以使用严格模式:“usestrict”向JavaScript编译器提示您希望通过使用严格模式来保护自己免受这些行为的影响。您仍然可以在需要时使用Window。但是,您必须以明确的方式使用它。严格模式如何影响我们之前的示例:对于addElement函数,从全局范围调用时,这是未定义的。如果您没有在变量上指定const|let|var,您将收到以下错误:UncaughtReferenceError:bisnotdefined5。持有对DOM节点的DOM引用也不能防止内存泄漏。我们需要注意不要保存对它们的引用。否则,垃圾收集器将无法清理它们,因为它们仍然可以访问。用一小段代码演示:constelements=[];constlist=document.getElementById('list');functionaddElement(){//cleannodeslist.innerHTML='';constdivElement=document.createElement('div');constelement=文档.createTextNode(`addingelement${elements.length}`);divElement.appendChild(元素);list.appendChild(divElement);元素.push(divElement);,addElement函数清除列表div并添加一个新元素作为子元素。这个新创建的元素被添加到元素数组中。下一次执行addElement时,该元素将从列表div中删除,但它不符合垃圾回收条件,因为它存储在elements数组中。我们在几次执行后监控函数:在上面的屏幕截图中查看节点是如何泄漏的。那么你如何解决这个问题?清除元素数组将使它们符合垃圾回收条件。总结在本文中,我们已经看到了最常见的内存泄漏方式。很明显,JavaScript本身并没有泄漏内存。相反,它是由开发人员无意的内存保留引起的。只要代码干净,不忘清理自己,就不会出现泄露。了解内存和垃圾回收在JavaScript中的工作原理是必须的。一些开发人员误以为因为它是自动的,所以他们不需要为此担心。