JavaScript内存错误包括几种,怎么理解
Admin 2022-10-13 群英技术资讯 718 次浏览
这篇文章给大家介绍了“JavaScript内存错误包括几种,怎么理解”的相关知识,讲解详细,步骤过程清晰,有一定的借鉴学习价值,因此分享给大家做个参考,感兴趣的朋友接下来一起跟随小编看看吧。既然我们不能强制的垃圾回收,那我们怎么知道它能正常工作?我们对它又了解多少呢?
这是否意味着无需担心资源和内存分配问题?当然不是。如果我们一不小心,可能会产生一些内存泄漏。

内存泄漏是软件无法回收的已分配的内存块。
Javascript 提供了一个垃圾收集程序,但这并不意味着我们就能避免内存泄漏。为了符合垃圾收集的条件,该对象必须不被其他地方引用。如果持有对未使用的资源的引用,这将会阻止这些资源被回收。这就是所谓的无意识的内存保持。
泄露内存可能会导致垃圾收集器更频繁地运行。由于这个过程会阻止脚本的运行,它可能会让我们程序卡起来,这么一卡,挑剔的用户肯定会注意到,一用不爽了,那这个产品离下线的日子就不完了。更严重可能会让整个应用奔溃,那就gg了。
如何防止内存泄漏? 主要还是我们应该避免保留不必要的资源。来看看一些常见的场景。
setInterval() 方法重复调用函数或执行代码片段,每次调用之间有固定的时间延迟。它返回一个时间间隔ID,该ID唯一地标识时间间隔,因此您可以稍后通过调用 clearInterval() 来删除它。
我们创建一个组件,它调用一个回调函数来表示它在x个循环之后完成了。我在这个例子中使用React,但这适用于任何FE框架。
import React, { useRef } from 'react';
const Timer = ({ cicles, onFinish }) => {
const currentCicles = useRef(0);
setInterval(() => {
if (currentCicles.current >= cicles) {
onFinish();
return;
}
currentCicles.current++;
}, 500);
return (
Loading ...
);
}
export default Timer; 登录后复制 一看,好像没啥问题。不急,我们再创建一个触发这个定时器的组件,并分析其内存性能。
import React, { useState } from 'react';
import styles from '../styles/Home.module.css'
import Timer from '../components/Timer';
export default function Home() {
const [showTimer, setShowTimer] = useState();
const onFinish = () => setShowTimer(false);
return (
{showTimer ? (
): (
setShowTimer(true)}> Retry
)}
)
} 登录后复制 在 Retry 按钮上单击几次后,这是使用Chrome Dev Tools获取内存使用的结果:

当我们点击重试按钮时,可以看到分配的内存越来越多。这说明之前分配的内存没有被释放。计时器仍然在运行而不是被替换。
怎么解决这个问题?setInterval 的返回值是一个间隔 ID,我们可以用它来取消这个间隔。在这种特殊情况下,我们可以在组件卸载后调用 clearInterval。
useEffect(() => {
const intervalId = setInterval(() => {
if (currentCicles.current >= cicles) {
onFinish();
return;
}
currentCicles.current++;
}, 500);
return () => clearInterval(intervalId);
}, []) 登录后复制 有时,在编写代码时,很难发现这个问题,最好的方式,还是要把组件抽象化。
这里使用的是React,我们可以把所有这些逻辑都包装在一个自定义的 Hook 中。
import { useEffect } from 'react';
export const useTimeout = (refreshCycle = 100, callback) => {
useEffect(() => {
if (refreshCycle <= 0) {
setTimeout(callback, 0);
return;
}
const intervalId = setInterval(() => {
callback();
}, refreshCycle);
return () => clearInterval(intervalId);
}, [refreshCycle, setInterval, clearInterval]);
};
export default useTimeout; 登录后复制 现在需要使用setInterval时,都可以这样做:
const handleTimeout = () => ...;
useTimeout(100, handleTimeout);
登录后复制 现在你可以使用这个useTimeout Hook,而不必担心内存被泄露,这也是抽象化的好处。
Web API提供了大量的事件监听器。在前面,我们讨论了setTimeout。现在来看看 addEventListener。
在这个事例中,我们创建一个键盘快捷键功能。由于我们在不同的页面上有不同的功能,所以将创建不同的快捷键功能
function homeShortcuts({ key}) {
if (key === 'E') {
console.log('edit widget')
}
}
// 用户在主页上登陆,我们执行
document.addEventListener('keyup', homeShortcuts);
// 用户做一些事情,然后导航到设置
function settingsShortcuts({ key}) {
if (key === 'E') {
console.log('edit setting')
}
}
// 用户在主页上登陆,我们执行
document.addEventListener('keyup', settingsShortcuts); 登录后复制 看起来还是很好,除了在执行第二个 addEventListener 时没有清理之前的 keyup。这段代码不是替换我们的 keyup 监听器,而是将添加另一个 callback。这意味着,当一个键被按下时,它将触发两个函数。
要清除之前的回调,我们需要使用 removeEventListener :
document.removeEventListener(‘keyup’, homeShortcuts);
登录后复制 重构一下上面的代码:
function homeShortcuts({ key}) {
if (key === 'E') {
console.log('edit widget')
}
}
// user lands on home and we execute
document.addEventListener('keyup', homeShortcuts);
// user does some stuff and navigates to settings
function settingsShortcuts({ key}) {
if (key === 'E') {
console.log('edit setting')
}
}
// user lands on home and we execute
document.removeEventListener('keyup', homeShortcuts);
document.addEventListener('keyup', settingsShortcuts); 登录后复制 根据经验,当使用来自全局对象的工具时,需要灰常小心。
Observers 是一个浏览器的 Web API功能,很多开发者都不知道。如果你想检查HTML元素的可见性或大小的变化,这个就很强大了。
IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
尽管它很强大,但我们也要谨慎的使用它。一旦完成了对对象的观察,就要记得在不用的时候取消它。
看看代码:
const ref = ...
const visible = (visible) => {
console.log(`It is ${visible}`);
}
useEffect(() => {
if (!ref) {
return;
}
observer.current = new IntersectionObserver(
(entries) => {
if (!entries[0].isIntersecting) {
visible(true);
} else {
visbile(false);
}
},
{ rootMargin: `-${header.height}px` },
);
observer.current.observe(ref);
}, [ref]); 登录后复制 上面的代码看起来不错。然而,一旦组件被卸载,观察者会发生什么?它不会被清除,那内存可就泄漏了。我们怎么解决这个问题呢?只需要使用 disconnect 方法:
const ref = ...
const visible = (visible) => {
console.log(`It is ${visible}`);
}
useEffect(() => {
if (!ref) {
return;
}
observer.current = new IntersectionObserver(
(entries) => {
if (!entries[0].isIntersecting) {
visible(true);
} else {
visbile(false);
}
},
{ rootMargin: `-${header.height}px` },
);
observer.current.observe(ref);
return () => observer.current?.disconnect();
}, [ref]); 登录后复制 
向 Window 添加对象是一个常见的错误。在某些场景中,可能很难找到它,特别是在使用 Window Execution上下文中的this关键字。看看下面的例子:
function addElement(element) {
if (!this.stack) {
this.stack = {
elements: []
}
}
this.stack.elements.push(element);
} 登录后复制 它看起来无害,但这取决于你从哪个上下文调用addElement。如果你从Window Context调用addElement,那就会越堆越多。
另一个问题可能是错误地定义了一个全局变量:
var a = 'example 1'; // 作用域限定在创建var的地方
b = 'example 2'; // 添加到Window对象中
登录后复制 要防止这种问题可以使用严格模式:
"use strict"
登录后复制 通过使用严格模式,向JavaScript编译器暗示,你想保护自己免受这些行为的影响。当你需要时,你仍然可以使用Window。不过,你必须以明确的方式使用它。
严格模式是如何影响我们前面的例子:
addElement 函数,当从全局作用域调用时,this 是未定义的const | let | var,你会得到以下错误:Uncaught ReferenceError: b is not defined
登录后复制 DOM节点也不能避免内存泄漏。我们需要注意不要保存它们的引用。否则,垃圾回收器将无法清理它们,因为它们仍然是可访问的。
用一小段代码演示一下:
const elements = [];
const list = document.getElementById('list');
function addElement() {
// clean nodes
list.innerHTML = '';
const pElement= document.createElement('p');
const element = document.createTextNode(`adding element ${elements.length}`);
pElement.appendChild(element);
list.appendChild(pElement);
elements.push(pElement);
}
document.getElementById('addElement').onclick = addElement;注意,addElement 函数清除列表 p,并将一个新元素作为子元素添加到它中。这个新创建的元素被添加到 elements 数组中。
下一次执行 addElement 时,该元素将从列表 p 中删除,但是它不适合进行垃圾收集,因为它存储在 elements 数组中。
我们在执行几次之后监视函数:

在上面的截图中看到节点是如何被泄露的。那怎么解决这个问题?清除 elements 数组将使它们有资格进行垃圾收集。
在这篇文章中,我们已经看到了最常见的内存泄露方式。很明显,JavaScript本身并没有泄漏内存。相反,它是由开发者方面无意的内存保持造成的。只要代码是整洁的,而且我们不忘自己清理,就不会发生泄漏。
了解内存和垃圾回收在JavaScript中是如何工作的是必须的。一些开发者得到了错误的意识,认为由于它是自动的,所以他们不需要担心这个问题。
作者: Jose Granja
原文:https://betterprogramming.pub/5-common-javascript-memory-mistakes-c8553972e4c2
关于“JavaScript内存错误包括几种,怎么理解”的内容今天就到这,感谢各位的阅读,大家可以动手实际看看,对大家加深理解更有帮助哦。如果想了解更多相关内容的文章,关注我们,群英网络小编每天都会为大家更新不同的知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了js属性对象的hasOwnProperty方法的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
目录引言class 第一个好:私有变量class 第二个好:super 继承引言在很早以前,写过一篇文章 “类”设计模式和“原型”设计模式——“复制”和“委托”的差异 ,大致意思就是说:代码复用,也就是继承、重写,有两种思路:1. 面向对象的类继承;2. 基于 JavaScript 原型链的原型继承;前者的主要特点是:
这篇文章主要介绍了vue长按事件touch,文末给大家补充介绍了Vue长按触摸事件的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
这篇文章主要为大家详细介绍了JS实现网页导航条特效,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
这篇文章主要介绍了如何解决el-checkbox选中状态更改问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008