五个常见的JavaScript内存错误
时间:2023-03-13 04:59:51
科技观察
JavaScript不提供任何内存管理原语。相反,内存由JavaScriptVM通过内存回收过程管理。这个过程称为垃圾收集。既然我们不能强迫它运行,我们怎么知道它会起作用呢?我们对此了解多少?脚本执行在此过程中暂停。它为不可访问的资源释放内存。这是不确定的。它不会一次检查整个内存,而是会运行多个周期。这是不可预测的。它会在必要时执行。这是否意味着我们不必担心资源和内存分配?当然不是。如果不小心,可能会造成一些内存泄漏。什么是内存泄漏?内存泄漏是分配给软件无法回收的内存。仅仅因为javascript为您提供垃圾收集过程并不意味着您可以避免内存泄漏。为了符合垃圾收集的条件,该对象必须在别处被引用。如果您持有对未使用资源的引用,则无法分配这些资源。这称为无意的记忆保留。内存泄漏会导致垃圾收集器运行更频繁。由于此过程会阻止脚本运行,因此可能会降低您的Web应用程序的速度。这将使您的性能降低,用户会注意到这一点。它甚至会导致您的Web应用程序崩溃。我们如何防止我们的Web应用程序泄漏内存?很简单:通过避免保留不必要的资源。让我们看看可能发生的最常见的情况。计时器监听器让我们看一下SetInterval计时器。它是一个常用的WebAPI函数。"window和worker接口提供的setInterval()方法,重复调用一个函数或执行一段代码,每次调用之间有一个固定的时间延迟。它返回一个唯一标识间隔的间隔ID,所以你可以稍后删除它通过调用ClearInterval()它。该方法由WindoworWorkerglobalscopeMixin定义。-MDNWeb文档让我们创建一个组件,该组件调用回调函数以在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;起初,它看起来没有任何问题。让我们创建一个触发此计时器的组件,并分析其内存性能:exportdefaultfunctionHome(){const[showTimer,setShowTimer]=useState();constonFinish=()=>setShowTimer(false);return(
{showTimer?():(setShowTimer(true)}>重试)} )}在重试按钮上点击几次后,这就是我们对Chrome所做的DevTools获取内存使用情况的结果:您可以看到在您点击重试按钮时分配了越来越多的内存。这意味着之前分配的内存没有被释放。间隔计时器仍在运行而不是被替换。我们如何解决这个问题?setInterval的返回是我们可以用来取消间隔的间隔ID。在此特定场景中,一旦组件在组件上卸载,我们就可以调用ClearInterval。useEffect(()=>{constintervalId=setInterval(()=>{if(currentCicles.current>=cicles){onFinish();return;}currentCicles.current++;},500);return()=>clearInterval(intervalId);},[])有时,代码审查很难发现这些问题。最佳实践是创建可以管理所有复杂性的抽象。当我们在这里使用React时,我们可以将所有这些逻辑包装在自定义钩子中: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);现在,您可以使用这个USETIMEOUT钩子无需担心内存泄漏,这一切都由抽象来管理。2.事件监听器WebAPI提供了大量的事件监听器,你可以自己hook。以前,我们覆盖settimout。现在我们来看看addeventlistener。让我们为我们的网络应用程序创建一个键盘快捷键。由于我们在不同的页面有不同的功能,所以我们会创建不同的快捷方式功能:'keyup',homeShortcuts);//userdoessomestuffandnavigatessettingsfunctionsettingsShortcuts({key}){if(key==='E'){console.log('editsetting')}}//userlandsonhomeandweexecutedocument.addEventListener('keyup',settingsShortcuts);一切似乎都很好,除了我们在执行第二个AddeventListener时没有清除之前的键。此代码没有替换我们的keyup侦听器,而是替换了keyup侦听器。这意味着当一个键被按下时,它会触发两个函数。要清除以前的回调,我们需要使用removeeventListener。让我们看一个代码示例:document.removeEventListener('keyup',homeShortcuts);让我们重构代码以防止这种不良行为:;//userdoessomestuffandnavigatessettingsfunctionsettingsShortcuts({key}){if(key==='E'){console.log('editsetting')}}//userlandsonhomeandweexecuteddocument.removeEventListener('keyup',homeShortcuts);document.addEventListener('keyup',设置快捷方式);根据经验,使用来自全局对象的工具时需要小心和负责。3.ObserversObservers是浏览器WebAPI特性,很多开发者都不知道。如果您想检查HTML元素的可见性或大小的变化,它们就非常有用。让我们检查一下IntersectionObserverAPI:“IntersectionObserverAPI提供了一种异步观察目标元素与祖先元素或顶级文档视口相交处的变化的方法。”-MDNWebDocs尽可能强大,您需要负责任地使用它。完成对对象的观察后,您需要取消监视过程。让我们看一些代码: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);},[ref]);上面的代码看起来不错。但是一旦组件被卸载,观察者会发生什么?它没有被清除,所以你正在泄漏内存。我们如何解决这个问题?只需使用disconnect方法:现在我们可以确定当组件卸载时,我们的观察者将断开连接。4.窗口对象向窗口添加对象是一个常见的错误。在某些情况下,可能很难找到-特别是如果您在窗口执行上下文中使用此关键字。让我们看下面的例子:functionaddElement(element){if(!this.stack){this.stack={elements:[]}}this.stack.elements.push(element);}它看起来无害,但这取决于在您调用addelement的上下文中。如果您从窗口上下文中调用AddElement,您将开始查看堆叠的项目。另一个问题可能是错误地定义了全局变量:vara='example1';//scopedtotheplacewherevarwascreatedb='example2';//addedtotheWindowobject为防止此类问题,请始终在严格模式下执行JavaScript:“usestrict”通过使用严格模式,您将向JavaScript编译器暗示您希望保护自己免受这些类型的行为的影响。您仍然可以在需要时使用Windows。但是,您必须以明确的方式使用它。严格模式如何影响我们之前的示例:在Addelement函数中,从全局范围调用时this将是未定义的。如果您没有在变量上指定const|lefthandedvar,您将收到以下错误:UncaughtReferenceError:bisnotdefined5.持有DOM引用的DOM节点也没有内存泄漏。您需要注意不要抓住他们的参考资料。否则,垃圾收集器将无法清除它们,因为它们仍然可以访问。让我们看一个小代码示例来说明这一点:constelements=[];constlist=document.getElementById('list');functionaddElement(){//cleannodeslist.innerHTML='';constdivElement=document.createElement('div');constelment=document.createTextNode(`addingelement${elements.length}`);divElement.appendChild(element);list.appendChild(divElement);elements.push(divElement);}document.getElementById('addElement').onclick=添加元素;请注意,AddElement函数清除列表DIV并将新元素添加为子元素。这个新创建的元素将被添加到元素数组中。下次执行AddElement时,该元素将从列表Div中删除。但是,它不符合垃圾回收条件,因为它存储在元素数组中。这使得它可达。这将为您提供每个addelement执行的节点。让我们在几次执行后监控函数:我们可以在上面的屏幕截图中看到节点是如何泄漏的。我们如何解决这个问题?清除元素数组将使它们符合垃圾回收条件。结论在本文中,我们了解了可能发生泄漏的最常见方式。显然,JavaScript本身不会泄漏内存。相反,它是由开发人员无意的内存保留引起的。只要代码干净并且我们不忘记自己清理,就不会发生泄漏。了解内存和垃圾回收在JavaScript中的工作原理是必须的。一些开发人员误以为因为它是自动的,所以他们不需要担心。建议定期在Web应用程序上运行浏览器分析器工具。这是确保没有泄漏并留下来的唯一方法。ChromeDeveloperPerformance选项卡是开始检测某些异常的地方。浏览问题后,您可以使用Profiler选项卡通过拍摄快照并进行比较来更深入地研究它。有时,我们花时间优化方法而忘记了内存在我们的Web应用程序的性能中起着很大的作用。干杯!原文链接:https://betterprogramming.pub/5-common-javascript-memory-mistakes-c8553972e4c2