新聞中心
JavaScript沒有提供任何內(nèi)存管理原語。相反,內(nèi)存由JavaScript VM通過內(nèi)存回收過程管理。該過程稱為垃圾收集。

定西網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)!從網(wǎng)頁設計、網(wǎng)站建設、微信開發(fā)、APP開發(fā)、成都響應式網(wǎng)站建設等網(wǎng)站項目制作,到程序開發(fā),運營維護。成都創(chuàng)新互聯(lián)于2013年開始到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設就選成都創(chuàng)新互聯(lián)。
由于我們不能強迫它運行,我們?nèi)绾沃浪鼤9ぷ?我們對此了解了什么?
- 腳本執(zhí)行在此過程中暫停。
- 它釋放內(nèi)存以實現(xiàn)無法訪問的資源。
- 這是非確定性的。
- 它不會一次性檢查整個內(nèi)存,但將在多個周期中運行。
- 這是不可預測的。它將在必要時執(zhí)行。
這是否意味著我們不必擔心資源和內(nèi)存分配?當然不是。如果您不小心,您可能會創(chuàng)建一些內(nèi)存泄漏。
什么是內(nèi)存泄漏?
內(nèi)存泄漏是軟件無法回收的分配的存儲器。
javascript為您提供垃圾收集過程并不意味著您可以從內(nèi)存泄漏中安全。為了有資格獲得垃圾收集,必須在其他地方引用對象。如果您持有對未使用的資源的引用,則會阻止這些資源未分配。這被稱為無意的記憶保留。
泄漏內(nèi)存可能導致更頻繁的垃圾收集器運行。由于此過程將阻止腳本運行,因此可能會減慢您的Web應用程序。這將使您的表現(xiàn)較少,這將由用戶注意到。它甚至可以導致您的Web應用程序崩潰。
我們?nèi)绾畏乐刮覀兊腤eb應用程序泄漏內(nèi)存?這很簡單:通過避免保留不必要的資源。讓我們看看可能發(fā)生的最常見的場景。
計時器監(jiān)聽器
讓我們來看看SetInterval定時器。它是一個常用的Web API功能。
| “窗口和工作接口提供的setInterval()方法,重復調(diào)用函數(shù)或執(zhí)行代碼片段,每個呼叫之間的固定時間延遲。它返回唯一標識間隔的間隔ID,因此您可以通過調(diào)用ClearInterval()稍后刪除它。該方法由WindoworWorkerglobalscope Mixin定義。“ - MDN Web Docs |
讓我們創(chuàng)建一個調(diào)用回調(diào)函數(shù)的組件,以發(fā)出x循環(huán)后的完成。我正在為這個特定的例子做出反應,但這適用于任何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;
起初,看起來沒有什么是錯的。讓我們創(chuàng)建一個觸發(fā)此計時器的組件,并分析其內(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 (
- {showTimer ? (
- ): (
- Retry
- )}
- )
- }
在重試按鈕上單擊幾次后,這是我們使用Chrome Dev Tools獲得內(nèi)存使用的結果:
您可以看到在擊中重試按鈕時分配了越來越多的內(nèi)存。這意味著分配的先前內(nèi)存并沒有釋放。間隔計時器仍在運行而不是被替換。
我們?nèi)绾谓鉀Q這個問題?setInterval的返回是我們可以使用的間隔ID來取消間隔。在這個特定的方案中,我們可以在組件上卸載一旦組件才能調(diào)用ClearInterval。
- useEffect(() => {
- const intervalId = setInterval(() => {
- if (currentCicles.current >= cicles) {
- onFinish();
- return;
- }
- currentCicles.current++;
- }, 500);
- return () => clearInterval(intervalId);
- }, [])
有時,在代碼審查中發(fā)現(xiàn)這些問題很難。最好的做法是創(chuàng)建抽象,您可以管理所有復雜性。
正如我們在此使用的反應,我們可以在自定義掛鉤中包裝所有這些邏輯:
- 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,您都可以執(zhí)行以下操作:
- const handleTimeout = () => ...;
- useTimeout(100, handleTimeout);
現(xiàn)在,您可以使用此USETIMEOUT掛鉤而無需擔心內(nèi)存泄露,它都是由抽象管理的。
2. 事件監(jiān)聽器
Web API提供了大量的事件偵聽器,您可以自己掛鉤。以前,我們覆蓋了settimout。現(xiàn)在我們將看addeventlistener。
讓我們?yōu)槲覀兊腤eb應用程序創(chuàng)建一個鍵盤快捷功能。由于我們在不同頁面上有不同的功能,因此我們將創(chuàng)建不同的快捷函數(shù):
- 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.addEventListener('keyup', settingsShortcuts);
一切似乎很好,除了我們在執(zhí)行第二個AddeventListener時沒有清潔先前的鍵。此代碼而不是更換我們的keyup偵聽器,而不是更換keyup偵聽器。這意味著當按下鍵時,它將觸發(fā)兩個功能。
要清除以前的回調(diào),我們需要使用remove eventListener。讓我們看看代碼示例:
- 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);
作為拇指的規(guī)則,當使用來自全局對象的工具時,您需要謹慎且負責任。
3. 觀察者
觀察者是大量開發(fā)人員未知的瀏覽器Web API功能。如果您想檢查HTML元素的可見性或大小的更改,它們是強大的。
讓我們檢查交叉點觀察者API:
| “Intersection Observer API提供了一種異步地觀察目標元素與祖先元素或頂級文檔的視口的交叉點的變化?!?/p> - MDN Web Docs |
盡可能強大,您需要負責任地使用它。完成觀察對象后,您需要取消監(jiān)視過程。
讓我們看一些代碼:
- 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]);
上面的代碼看起來很好。但是,一旦組件未安裝,觀察者會發(fā)生什么?它不會被清除,所以你會泄漏內(nèi)存。我們怎樣才能解決這個問題?只需使用斷開連接方法:
現(xiàn)在我們可以確定,當組件卸載時,我們的觀察者將被斷開連接。
4. 窗口對象
將對象添加到窗口是一個常見的錯誤。在某些情況下,可能很難找到 - 特別是如果您使用窗口執(zhí)行上下文中的此關鍵字。
讓我們來看看以下例子:
- function addElement(element) {
- if (!this.stack) {
- this.stack = {
- elements: []
- }
- }
- this.stack.elements.push(element);
- }
它看起來無害,但這取決于你調(diào)用一個addelement的上下文。如果從窗口上下文中調(diào)用AddElement,則會開始查看堆積的項目。
另一個問題可能是錯誤地定義全局變量:
- var a = 'example 1'; // scoped to the place where var was createdb = 'example 2'; // added to the Window object
為防止這種問題,始終以嚴格模式執(zhí)行JavaScript:
- "use strict"
通過使用嚴格模式,您將暗示您想要保護自己免受這些類型的行為保護的JavaScript編譯器。當您需要時,您仍然可以使用窗口。但是,您必須以明確的方式使用它。
如何影響我們之前的示例的嚴格模式:
- 在Addelement函數(shù)上,從全局范圍內(nèi)調(diào)用時,這將是未定義的。
- 如果您未指定const |左撇子var在變量上,您將收到以下錯誤:
- Uncaught ReferenceError: b is not defined
5. 持有DOM參考
DOM節(jié)點也沒有內(nèi)存泄漏。你需要小心不要抓住他們的參考。否則,垃圾收集器將無法清除它們,因為它們?nèi)匀豢梢缘竭_。
讓我們看一個小的代碼示例來說明這個:
- 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并將新元素添加為子項。此新創(chuàng)建的元素將添加到元素數(shù)組中。
下次執(zhí)行AddElement,將從列表Div中刪除該元素。但是,它不會有資格獲得垃圾收集,因為它存儲在元素數(shù)組中。這使得它可以到達。這將使您在每個addelement執(zhí)行上的節(jié)點。
讓我們在幾個執(zhí)行之后監(jiān)視函數(shù):
我們可以在上面的屏幕截圖中看到節(jié)點如何泄露。我們怎樣才能解決這個問題?清除元素數(shù)組將使它們有資格獲得垃圾收集。
結論
在本文中,我們已經(jīng)看到了最常見的方法可以泄露。很明顯,JavaScript不會泄漏內(nèi)存本身。相反,它是由從開發(fā)人員側(cè)的無意的記憶保留引起的。只要代碼整潔,我們就不會忘記在自己之后清理,不會發(fā)生泄漏。
了解JavaScript中的內(nèi)存和垃圾收集工作是必須的。一些開發(fā)人員獲得虛假印象,因為它是自動的,他們不需要擔心它。
建議在Web應用程序上定期運行瀏覽器分析器工具。這是唯一能夠肯定沒有泄漏并留下的方法。Chrome開發(fā)人員性能選項卡是開始檢測某些異常的地點。瀏覽問題后,您可以通過拍攝快照并進行比較,使用Profiler選項卡深入挖掘它。
有時,我們花費時間優(yōu)化方法,忘記內(nèi)存在我們的Web應用程序的性能中播放了一個很大的部分。
干杯!
原文鏈接:https://betterprogramming.pub/5-common-javascript-memory-mistakes-c8553972e4c2
網(wǎng)站欄目:五個常見的JavaScript內(nèi)存錯誤
轉(zhuǎn)載來源:http://www.dlmjj.cn/article/codphcj.html


咨詢
建站咨詢
