5个常见的JavaScript内存错误

文章介绍了如何在React应用中避免内存泄漏,通过合理使用useEffect、setInterval/clearInterval和自定义HookuseTimeout,以及处理事件监听器的清除。作者还强调了正确使用IntersectionObserver和Window对象的重要性,以确保性能和资源管理。
摘要由CSDN通过智能技术生成

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 ? (

): (

<button onClick={() => 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,而不必担心内存被泄露,这也是抽象化的好处。

2.事件监听

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);

根据经验,当使用来自全局对象的工具时,需要灰常小心。

3.Observers

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]);

4. Window Object

向 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对象中

要防止这种问题可以使用严格模式:

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
系的自学效果低效漫长且无助。**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-Be38wifL-1714816900909)]

[外链图片转存中…(img-2E4giBpU-1714816900910)]

[外链图片转存中…(img-EV8oVQeV-1714816900910)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值