Skip to main content

moregeek program

#yyds干货盘点#常见的javascript内存错误_文本、的博客-多极客编程

JavaScript 不提供任何内存管理操作。相反,内存由 JavaScript VM 通过内存回收过程管理,该过程称为垃圾收集。

既然我们不能强制的垃圾回收,那我们怎么知道它能正常工作?我们对它又了解多少呢?

  • 脚本执行在此过程中暂停
  • 它为不可访问的资源释放内存
  • 它是不确定的
  • 它不会一次检查整个内存,而是在多个周期中运行
  • 它是不可预测的,但它会在必要时执行

这是否意味着无需担心资源和内存分配问题?当然不是。如果我们一不小心,可能会产生一些内存泄漏。


什么是内存泄漏?

内存泄漏是软件无法回收的已分配的内存块。

Javascript 提供了一个垃圾收集程序,但这并不意味着我们就能避免内存泄漏。为了符合垃圾收集的条件,该对象必须不被其他地方引用。如果持有对未使用的资源的引用,这将会阻止这些资源被回收。这就是所谓的无意识的内存保持

泄露内存可能会导致垃圾收集器更频繁地运行。由于这个过程会阻止脚本的运行,它可能会让我们程序卡起来,这么一卡,挑剔的用户肯定会注意到,一用不爽了,那这个产品离下线的日子就不完了。更严重可能会让整个应用奔溃,那就gg了。

如何防止内存泄漏? 主要还是我们应该避免保留不必要的资源。来看看一些常见的场景。


1.计时器的监听

​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 (
<div>Loading ...</div>
);
}

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 (
<div className={styles.container}>
{showTimer ? (
<Timer cicles={10} onFinish={onFinish} />
): (
<button onClick={() => setShowTimer(true)}>
Retry
</button>
)}
</div>
)
}

在 ​Retry​ 按钮上单击几次后,这是使用Chrome Dev Tools获取内存使用的结果:

#yyds干货盘点#常见的JavaScript内存错误_javascript

当我们点击重试按钮时,可以看到分配的内存越来越多。这说明之前分配的内存没有被释放。计时器仍然在运行而不是被替换。

怎么解决这个问题?​​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 ()



©著作权归作者所有:来自51CTO博客作者尼羲的原创作品,请联系作者获取转载授权,否则将追究法律责任

javascript 中「thunk」怎么理解?_安东尼漫长的技术岁月的博客-多极客编程

降低阅读负担,启发创作心智,轻松学习 JavaScript 技巧,日拱一卒,jym,冲~本篇带来 Thunk 理解,这也是本瓜最津津乐道的 JS 函数式编程中延迟处理的思想核心之一!什么是 Thunk ?简单理解:在计算机编程中,Thunk 就是一种实现延迟执行的手段。举个栗子🌰🌰🌰我要计算 99 的 9 次方,然后再把它打印出来,你会怎么写?大聪明肯定是直接一句话:console.log(Mat

#yyds干货盘点# 前端歌谣的刷题之路-第六十一题-数组合并_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目合并数组 arr1 和数组 arr2。不要直接修改数组 arr,结果返回新的数组​编辑 核心代码<!DOCTYPE htm

#yyds干货盘点# 前端歌谣的刷题之路-第六十二题-添加元素_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目在数组 arr 的 index 处添加元素 item。不要直接修改数组 arr,结果返回新的数组​编辑核心代码<!DOCT

#yyds干货盘点# 前端歌谣的刷题之路-第六十四题-查找元素位置前端歌谣的刷题之路-第六十四题-查找元素位置_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目在数组 arr 中,查找值与 item 相等的元素出现的所有位置示例1输入:['a','b','c','d','e','f','

#yyds干货盘点# 前端歌谣的刷题之路-第六十三题-二次方_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目为数组 arr 中的每个元素求二次方。不要直接修改数组 arr,结果返回新的数组​编辑 核心代码<!DOCTYPE htm

javascript 剪贴板 clipboard 的那些事儿!_安东尼漫长的技术岁月的博客-多极客编程

减轻阅读负担,启发创作心智,轻松学习 JavaScript 技巧,日拱一卒,jym,冲~本篇带来 —— JavaScript 剪贴板 Clipboard 的那些事儿!复制粘贴就是互联网最伟大的发明,无论是使用快捷键 ctrl c 、ctrl v,还是鼠标选中右键复制再粘贴,Copy 操作都是非常简单的。然而要注意的是,你的剪贴板 Clipboard 或许在被有些软件无时无刻的监听着,有时候在知乎上

javascript 中「thunk」怎么理解?_安东尼漫长的技术岁月的博客-多极客编程

降低阅读负担,启发创作心智,轻松学习 JavaScript 技巧,日拱一卒,jym,冲~本篇带来 Thunk 理解,这也是本瓜最津津乐道的 JS 函数式编程中延迟处理的思想核心之一!什么是 Thunk ?简单理解:在计算机编程中,Thunk 就是一种实现延迟执行的手段。举个栗子🌰🌰🌰我要计算 99 的 9 次方,然后再把它打印出来,你会怎么写?大聪明肯定是直接一句话:console.log(Mat

每个程序员都应该知道的 redis 知识 - string 底层原理_crmeb中邦科技的博客-多极客编程

本文将讲述如下内容:1.Redis 中 ​​Srting 类型​​的底层实现原理2. 通过 String 底层实现原理的学习,我们可以学习到哪些底层优化方法3.Redis 中关于 String 命令介绍我们知道 Redis 是由 C 语言实现的,在介绍 Sring 类型的实现之前我们先复习一下 C 语言的字符串类型。C 语言中的字符串是以空字符结尾的字符数组,详细说明见下图:C 语言中的字符串1、

#yyds干货盘点# 前端歌谣的刷题之路-第六十一题-数组合并_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目合并数组 arr1 和数组 arr2。不要直接修改数组 arr,结果返回新的数组​编辑 核心代码<!DOCTYPE htm

#yyds干货盘点# 前端歌谣的刷题之路-第六十四题-查找元素位置前端歌谣的刷题之路-第六十四题-查找元素位置_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目在数组 arr 中,查找值与 item 相等的元素出现的所有位置示例1输入:['a','b','c','d','e','f','

#yyds干货盘点# 前端歌谣的刷题之路-第六十三题-二次方_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目为数组 arr 中的每个元素求二次方。不要直接修改数组 arr,结果返回新的数组​编辑 核心代码<!DOCTYPE htm

javascript 剪贴板 clipboard 的那些事儿!_安东尼漫长的技术岁月的博客-多极客编程

减轻阅读负担,启发创作心智,轻松学习 JavaScript 技巧,日拱一卒,jym,冲~本篇带来 —— JavaScript 剪贴板 Clipboard 的那些事儿!复制粘贴就是互联网最伟大的发明,无论是使用快捷键 ctrl c 、ctrl v,还是鼠标选中右键复制再粘贴,Copy 操作都是非常简单的。然而要注意的是,你的剪贴板 Clipboard 或许在被有些软件无时无刻的监听着,有时候在知乎上