您好,登錄后才能下訂單哦!
這篇文章主要介紹“JavaScript常見的五個(gè)內(nèi)存錯(cuò)誤是什么”,在日常操作中,相信很多人在JavaScript常見的五個(gè)內(nèi)存錯(cuò)誤是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”JavaScript常見的五個(gè)內(nèi)存錯(cuò)誤是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
JavaScript 不提供任何內(nèi)存管理操作。相反,內(nèi)存由 JavaScript VM 通過內(nèi)存回收過程管理,該過程稱為垃圾收集。
既然我們不能強(qiáng)制的垃圾回收,那我們怎么知道它能正常工作?我們對它又了解多少呢?
腳本執(zhí)行在此過程中暫停
它為不可訪問的資源釋放內(nèi)存
它是不確定的
它不會(huì)一次檢查整個(gè)內(nèi)存,而是在多個(gè)周期中運(yùn)行
它是不可預(yù)測的,但它會(huì)在必要時(shí)執(zhí)行
這是否意味著無需擔(dān)心資源和內(nèi)存分配問題?當(dāng)然不是。如果我們一不小心,可能會(huì)產(chǎn)生一些內(nèi)存泄漏。
內(nèi)存泄漏是軟件無法回收的已分配的內(nèi)存塊。
Javascript 提供了一個(gè)垃圾收集程序,但這并不意味著我們就能避免內(nèi)存泄漏。為了符合垃圾收集的條件,該對象必須不被其他地方引用。如果持有對未使用的資源的引用,這將會(huì)阻止這些資源被回收。這就是所謂的無意識的內(nèi)存保持。
泄露內(nèi)存可能會(huì)導(dǎo)致垃圾收集器更頻繁地運(yùn)行。由于這個(gè)過程會(huì)阻止腳本的運(yùn)行,它可能會(huì)讓我們程序卡起來,這么一卡,挑剔的用戶肯定會(huì)注意到,一用不爽了,那這個(gè)產(chǎn)品離下線的日子就不完了。更嚴(yán)重可能會(huì)讓整個(gè)應(yīng)用奔潰,那就gg了。
如何防止內(nèi)存泄漏? 主要還是我們應(yīng)該避免保留不必要的資源。來看看一些常見的場景。
setInterval()
方法重復(fù)調(diào)用函數(shù)或執(zhí)行代碼片段,每次調(diào)用之間有固定的時(shí)間延遲。它返回一個(gè)時(shí)間間隔ID
,該ID
唯一地標(biāo)識時(shí)間間隔,因此您可以稍后通過調(diào)用 clearInterval()
來刪除它。
我們創(chuàng)建一個(gè)組件,它調(diào)用一個(gè)回調(diào)函數(shù)來表示它在x
個(gè)循環(huán)之后完成了。我在這個(gè)例子中使用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;
一看,好像沒啥問題。不急,我們再創(chuàng)建一個(gè)觸發(fā)這個(gè)定時(shí)器的組件,并分析其內(nèi)存性能。
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獲取內(nèi)存使用的結(jié)果:
當(dāng)我們點(diǎn)擊重試按鈕時(shí),可以看到分配的內(nèi)存越來越多。這說明之前分配的內(nèi)存沒有被釋放。計(jì)時(shí)器仍然在運(yùn)行而不是被替換。
怎么解決這個(gè)問題?setInterval
的返回值是一個(gè)間隔 ID,我們可以用它來取消這個(gè)間隔。在這種特殊情況下,我們可以在組件卸載后調(diào)用 clearInterval
。
useEffect(() => { const intervalId = setInterval(() => { if (currentCicles.current >= cicles) { onFinish(); return; } currentCicles.current++; }, 500); return () => clearInterval(intervalId); }, [])
有時(shí),在編寫代碼時(shí),很難發(fā)現(xiàn)這個(gè)問題,最好的方式,還是要把組件抽象化。
這里使用的是React,我們可以把所有這些邏輯都包裝在一個(gè)自定義的 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;
現(xiàn)在需要使用setInterval
時(shí),都可以這樣做:
const handleTimeout = () => ...; useTimeout(100, handleTimeout);
現(xiàn)在你可以使用這個(gè)useTimeout Hook
,而不必?fù)?dān)心內(nèi)存被泄露,這也是抽象化的好處。
Web API提供了大量的事件監(jiān)聽器。在前面,我們討論了setTimeout
。現(xiàn)在來看看 addEventListener
。
在這個(gè)事例中,我們創(chuàng)建一個(gè)鍵盤快捷鍵功能。由于我們在不同的頁面上有不同的功能,所以將創(chuàng)建不同的快捷鍵功能
function homeShortcuts({ key}) { if (key === 'E') { console.log('edit widget') } } // 用戶在主頁上登陸,我們執(zhí)行 document.addEventListener('keyup', homeShortcuts); // 用戶做一些事情,然后導(dǎo)航到設(shè)置 function settingsShortcuts({ key}) { if (key === 'E') { console.log('edit setting') } } // 用戶在主頁上登陸,我們執(zhí)行 document.addEventListener('keyup', settingsShortcuts);
看起來還是很好,除了在執(zhí)行第二個(gè) addEventListener
時(shí)沒有清理之前的 keyup
。這段代碼不是替換我們的 keyup
監(jiān)聽器,而是將添加另一個(gè) callback
。這意味著,當(dāng)一個(gè)鍵被按下時(shí),它將觸發(fā)兩個(gè)函數(shù)。
要清除之前的回調(diào),我們需要使用 removeEventListener
:
document.removeEventListener(‘keyup', homeShortcuts);
重構(gòu)一下上面的代碼:
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);
根據(jù)經(jīng)驗(yàn),當(dāng)使用來自全局對象的工具時(shí),需要灰常小心。
Observers 是一個(gè)瀏覽器的 Web API功能,很多開發(fā)者都不知道。如果你想檢查HTML元素的可見性或大小的變化,這個(gè)就很強(qiáng)大了。
IntersectionObserver
接口 (從屬于Intersection Observer API) 提供了一種異步觀察目標(biāo)元素與其祖先元素或頂級文檔視窗(viewport
)交叉狀態(tài)的方法。祖先元素與視窗(viewport
)被稱為根(root
)。
盡管它很強(qiáng)大,但我們也要謹(jǐn)慎的使用它。一旦完成了對對象的觀察,就要記得在不用的時(shí)候取消它。
看看代碼:
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]);
上面的代碼看起來不錯(cuò)。然而,一旦組件被卸載,觀察者會(huì)發(fā)生什么?它不會(huì)被清除,那內(nèi)存可就泄漏了。我們怎么解決這個(gè)問題呢?只需要使用 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 添加對象是一個(gè)常見的錯(cuò)誤。在某些場景中,可能很難找到它,特別是在使用 Window Execution上下文中的this
關(guān)鍵字??纯聪旅娴睦樱?/p>
function addElement(element) { if (!this.stack) { this.stack = { elements: [] } } this.stack.elements.push(element); }
它看起來無害,但這取決于你從哪個(gè)上下文調(diào)用addElement
。如果你從Window Context調(diào)用addElement,那就會(huì)越堆越多。
另一個(gè)問題可能是錯(cuò)誤地定義了一個(gè)全局變量:
var a = 'example 1'; // 作用域限定在創(chuàng)建var的地方 b = 'example 2'; // 添加到Window對象中
要防止這種問題可以使用嚴(yán)格模式:
"use strict"
通過使用嚴(yán)格模式,向JavaScript編譯器暗示,你想保護(hù)自己免受這些行為的影響。當(dāng)你需要時(shí),你仍然可以使用Window。不過,你必須以明確的方式使用它。
嚴(yán)格模式是如何影響我們前面的例子:
對于 addElement
函數(shù),當(dāng)從全局作用域調(diào)用時(shí),this
是未定義的
如果沒有在一個(gè)變量上指定const | let | var
,你會(huì)得到以下錯(cuò)誤:
Uncaught ReferenceError: b is not defined
DOM節(jié)點(diǎn)也不能避免內(nèi)存泄漏。我們需要注意不要保存它們的引用。否則,垃圾回收器將無法清理它們,因?yàn)樗鼈內(nèi)匀皇强稍L問的。
用一小段代碼演示一下:
const elements = []; const list = document.getElementById('list'); function addElement() { // clean nodes list.innerHTML = ''; const divElement= document.createElement('div'); const element = document.createTextNode(`adding element ${elements.length}`); divElement.appendChild(element); list.appendChild(divElement); elements.push(divElement); } document.getElementById('addElement').onclick = addElement;
注意,addElement
函數(shù)清除列表 div
,并將一個(gè)新元素作為子元素添加到它中。這個(gè)新創(chuàng)建的元素被添加到 elements
數(shù)組中。
下一次執(zhí)行 addElement
時(shí),該元素將從列表 div
中刪除,但是它不適合進(jìn)行垃圾收集,因?yàn)樗鎯υ?nbsp;elements
數(shù)組中。
我們在執(zhí)行幾次之后監(jiān)視函數(shù):
在上面的截圖中看到節(jié)點(diǎn)是如何被泄露的。那怎么解決這個(gè)問題?清除 elements
數(shù)組將使它們有資格進(jìn)行垃圾收集。
到此,關(guān)于“JavaScript常見的五個(gè)內(nèi)存錯(cuò)誤是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。