自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

五個(gè)常見(jiàn)的JavaScript內(nèi)存錯(cuò)誤

開(kāi)發(fā) 前端
JavaScript 不提供任何內(nèi)存管理操作。相反,內(nèi)存由 JavaScript VM 通過(guò)內(nèi)存回收過(guò)程管理,該過(guò)程稱為垃圾收集。

JavaScript 不提供任何內(nèi)存管理操作。相反,內(nèi)存由 JavaScript VM 通過(guò)內(nèi)存回收過(guò)程管理,該過(guò)程稱為垃圾收集。

[[443138]]

既然我們不能強(qiáng)制的垃圾回收,那我們?cè)趺粗浪苷9ぷ?我們對(duì)它又了解多少呢?

  • 腳本執(zhí)行在此過(guò)程中暫停
  • 它為不可訪問(wèn)的資源釋放內(nèi)存
  • 它是不確定的
  • 它不會(huì)一次檢查整個(gè)內(nèi)存,而是在多個(gè)周期中運(yùn)行
  • 它是不可預(yù)測(cè)的,但它會(huì)在必要時(shí)執(zhí)行

這是否意味著無(wú)需擔(dān)心資源和內(nèi)存分配問(wèn)題?當(dāng)然不是。如果我們一不小心,可能會(huì)產(chǎn)生一些內(nèi)存泄漏。

什么是內(nèi)存泄漏?

內(nèi)存泄漏是軟件無(wú)法回收的已分配的內(nèi)存塊。

Javascript 提供了一個(gè)垃圾收集程序,但這并不意味著我們就能避免內(nèi)存泄漏。為了符合垃圾收集的條件,該對(duì)象必須不被其他地方引用。如果持有對(duì)未使用的資源的引用,這將會(huì)阻止這些資源被回收。這就是所謂的無(wú)意識(shí)的內(nèi)存保持。

泄露內(nèi)存可能會(huì)導(dǎo)致垃圾收集器更頻繁地運(yùn)行。由于這個(gè)過(guò)程會(huì)阻止腳本的運(yùn)行,它可能會(huì)讓我們程序卡起來(lái),這么一卡,挑剔的用戶肯定會(huì)注意到,一用不爽了,那這個(gè)產(chǎn)品離下線的日子就不完了。更嚴(yán)重可能會(huì)讓整個(gè)應(yīng)用奔潰,那就gg了。

如何防止內(nèi)存泄漏? 主要還是我們應(yīng)該避免保留不必要的資源。來(lái)看看一些常見(jiàn)的場(chǎng)景。

1.計(jì)時(shí)器的監(jiān)聽(tīng)

setInterval() 方法重復(fù)調(diào)用函數(shù)或執(zhí)行代碼片段,每次調(diào)用之間有固定的時(shí)間延遲。它返回一個(gè)時(shí)間間隔ID,該ID唯一地標(biāo)識(shí)時(shí)間間隔,因此您可以稍后通過(guò)調(diào)用 clearInterval() 來(lái)刪除它。

我們創(chuàng)建一個(gè)組件,它調(diào)用一個(gè)回調(diào)函數(shù)來(lái)表示它在x個(gè)循環(huán)之后完成了。我在這個(gè)例子中使用React,但這適用于任何FE框架。

 

  1. import React, { useRef } from 'react'
  2.  
  3. const Timer = ({ cicles, onFinish }) => { 
  4.     const currentCicles = useRef(0); 
  5.  
  6.     setInterval(() => { 
  7.         if (currentCicles.current >= cicles) { 
  8.             onFinish(); 
  9.             return
  10.         } 
  11.         currentCicles.current++; 
  12.     }, 500); 
  13.  
  14.     return ( 
  15.         <div>Loading ...</div> 
  16.     ); 
  17.  
  18. export default Timer; 

 

一看,好像沒(méi)啥問(wèn)題。不急,我們?cè)賱?chuàng)建一個(gè)觸發(fā)這個(gè)定時(shí)器的組件,并分析其內(nèi)存性能。

  1. import React, { useState } from 'react'
  2. import styles from '../styles/Home.module.css' 
  3. import Timer from '../components/Timer'
  4.  
  5. export default function Home() { 
  6.     const [showTimer, setShowTimer] = useState(); 
  7.     const onFinish = () => setShowTimer(false); 
  8.  
  9.     return ( 
  10.       <div className={styles.container}> 
  11.           {showTimer ? ( 
  12.               <Timer cicles={10} onFinish={onFinish} /> 
  13.           ): ( 
  14.               <button onClick={() => setShowTimer(true)}> 
  15.                 Retry 
  16.               </button> 
  17.           )} 
  18.       </div> 
  19.     ) 

在 Retry 按鈕上單擊幾次后,這是使用Chrome Dev Tools獲取內(nèi)存使用的結(jié)果:

 

5個(gè)常見(jiàn)的JavaScript內(nèi)存錯(cuò)誤

 

當(dāng)我們點(diǎn)擊重試按鈕時(shí),可以看到分配的內(nèi)存越來(lái)越多。這說(shuō)明之前分配的內(nèi)存沒(méi)有被釋放。計(jì)時(shí)器仍然在運(yùn)行而不是被替換。

怎么解決這個(gè)問(wèn)題?setInterval 的返回值是一個(gè)間隔 ID,我們可以用它來(lái)取消這個(gè)間隔。在這種特殊情況下,我們可以在組件卸載后調(diào)用 clearInterval。

 

  1. useEffect(() => { 
  2.     const intervalId = setInterval(() => { 
  3.         if (currentCicles.current >= cicles) { 
  4.             onFinish(); 
  5.             return
  6.         } 
  7.         currentCicles.current++; 
  8.     }, 500); 
  9.  
  10.     return () => clearInterval(intervalId); 
  11. }, []) 

 

有時(shí),在編寫(xiě)代碼時(shí),很難發(fā)現(xiàn)這個(gè)問(wèn)題,最好的方式,還是要把組件抽象化。

這里使用的是React,我們可以把所有這些邏輯都包裝在一個(gè)自定義的 Hook 中。

 

  1. import { useEffect } from 'react'
  2.  
  3. export const useTimeout = (refreshCycle = 100, callback) => { 
  4.     useEffect(() => { 
  5.         if (refreshCycle <= 0) { 
  6.             setTimeout(callback, 0); 
  7.             return
  8.         } 
  9.  
  10.         const intervalId = setInterval(() => { 
  11.             callback(); 
  12.         }, refreshCycle); 
  13.  
  14.         return () => clearInterval(intervalId); 
  15.     }, [refreshCycle, setInterval, clearInterval]); 
  16. }; 
  17.  
  18. export default useTimeout; 

 

現(xiàn)在需要使用setInterval時(shí),都可以這樣做:

 

  1. const handleTimeout = () => ...; 
  2.  
  3. useTimeout(100, handleTimeout); 

 

現(xiàn)在你可以使用這個(gè)useTimeout Hook,而不必?fù)?dān)心內(nèi)存被泄露,這也是抽象化的好處。

2.事件監(jiān)聽(tīng)

Web API提供了大量的事件監(jiān)聽(tīng)器。在前面,我們討論了setTimeout?,F(xiàn)在來(lái)看看 addEventListener。

在這個(gè)事例中,我們創(chuàng)建一個(gè)鍵盤(pán)快捷鍵功能。由于我們?cè)诓煌捻?yè)面上有不同的功能,所以將創(chuàng)建不同的快捷鍵功能

 

  1. function homeShortcuts({ key}) { 
  2.     if (key === 'E') { 
  3.         console.log('edit widget'
  4.     } 
  5.  
  6. // 用戶在主頁(yè)上登陸,我們執(zhí)行 
  7. document.addEventListener('keyup', homeShortcuts);  
  8.  
  9.  
  10. // 用戶做一些事情,然后導(dǎo)航到設(shè)置 
  11.  
  12. function settingsShortcuts({ key}) { 
  13.     if (key === 'E') { 
  14.         console.log('edit setting'
  15.     } 
  16.  
  17. // 用戶在主頁(yè)上登陸,我們執(zhí)行 
  18. document.addEventListener('keyup', settingsShortcuts);  

 

看起來(lái)還是很好,除了在執(zhí)行第二個(gè) addEventListener 時(shí)沒(méi)有清理之前的 keyup。這段代碼不是替換我們的 keyup 監(jiān)聽(tīng)器,而是將添加另一個(gè) callback。這意味著,當(dāng)一個(gè)鍵被按下時(shí),它將觸發(fā)兩個(gè)函數(shù)。

要清除之前的回調(diào),我們需要使用 removeEventListener :

 

  1. document.removeEventListener(‘keyup’, homeShortcuts); 

重構(gòu)一下上面的代碼:

 

  1. function homeShortcuts({ key}) { 
  2.     if (key === 'E') { 
  3.         console.log('edit widget'
  4.     } 
  5.  
  6. // user lands on home and we execute 
  7. document.addEventListener('keyup', homeShortcuts);  
  8.  
  9.  
  10. // user does some stuff and navigates to settings 
  11.  
  12. function settingsShortcuts({ key}) { 
  13.     if (key === 'E') { 
  14.         console.log('edit setting'
  15.     } 
  16.  
  17. // user lands on home and we execute 
  18. document.removeEventListener('keyup', homeShortcuts);  
  19. document.addEventListener('keyup', settingsShortcuts); 

 

根據(jù)經(jīng)驗(yàn),當(dāng)使用來(lái)自全局對(duì)象的工具時(shí),需要灰常小心。

3.Observers

Observers 是一個(gè)瀏覽器的 Web API功能,很多開(kāi)發(fā)者都不知道。如果你想檢查HTML元素的可見(jiàn)性或大小的變化,這個(gè)就很強(qiáng)大了。

IntersectionObserver接口 (從屬于Intersection Observer API) 提供了一種異步觀察目標(biāo)元素與其祖先元素或頂級(jí)文檔視窗(viewport)交叉狀態(tài)的方法。祖先元素與視窗(viewport)被稱為根(root)。

盡管它很強(qiáng)大,但我們也要謹(jǐn)慎的使用它。一旦完成了對(duì)對(duì)象的觀察,就要記得在不用的時(shí)候取消它。

看看代碼:

 

  1. const ref = ... 
  2. const visible = (visible) => { 
  3.   console.log(`It is ${visible}`); 
  4.  
  5. useEffect(() => { 
  6.     if (!ref) { 
  7.         return
  8.     } 
  9.  
  10.     observer.current = new IntersectionObserver( 
  11.         (entries) => { 
  12.             if (!entries[0].isIntersecting) { 
  13.                 visible(true); 
  14.             } else { 
  15.                 visbile(false); 
  16.             } 
  17.         }, 
  18.         { rootMargin: `-${header.height}px` }, 
  19.     ); 
  20.  
  21.     observer.current.observe(ref); 
  22. }, [ref]); 

 

上面的代碼看起來(lái)不錯(cuò)。然而,一旦組件被卸載,觀察者會(huì)發(fā)生什么?它不會(huì)被清除,那內(nèi)存可就泄漏了。我們?cè)趺唇鉀Q這個(gè)問(wèn)題呢?只需要使用 disconnect 方法:

 

  1. const ref = ... 
  2. const visible = (visible) => { 
  3.   console.log(`It is ${visible}`); 
  4.  
  5. useEffect(() => { 
  6.     if (!ref) { 
  7.         return
  8.     } 
  9.  
  10.     observer.current = new IntersectionObserver( 
  11.         (entries) => { 
  12.             if (!entries[0].isIntersecting) { 
  13.                 visible(true); 
  14.             } else { 
  15.                 visbile(false); 
  16.             } 
  17.         }, 
  18.         { rootMargin: `-${header.height}px` }, 
  19.     ); 
  20.  
  21.     observer.current.observe(ref); 
  22.  
  23.     return () => observer.current?.disconnect(); 
  24. }, [ref]); 

 

4. Window Object

向 Window 添加對(duì)象是一個(gè)常見(jiàn)的錯(cuò)誤。在某些場(chǎng)景中,可能很難找到它,特別是在使用 Window Execution上下文中的this關(guān)鍵字??纯聪旅娴睦樱?/p>

 

  1. function addElement(element) { 
  2.     if (!this.stack) { 
  3.         this.stack = { 
  4.             elements: [] 
  5.         } 
  6.     } 
  7.  
  8.     this.stack.elements.push(element); 

 

它看起來(lái)無(wú)害,但這取決于你從哪個(gè)上下文調(diào)用addElement。如果你從Window Context調(diào)用addElement,那就會(huì)越堆越多。

另一個(gè)問(wèn)題可能是錯(cuò)誤地定義了一個(gè)全局變量:

 

  1. var a = 'example 1'; // 作用域限定在創(chuàng)建var的地方 
  2. b = 'example 2'; // 添加到Window對(duì)象中 

 

要防止這種問(wèn)題可以使用嚴(yán)格模式:

 

  1. "use strict" 

通過(guò)使用嚴(yán)格模式,向JavaScript編譯器暗示,你想保護(hù)自己免受這些行為的影響。當(dāng)你需要時(shí),你仍然可以使用Window。不過(guò),你必須以明確的方式使用它。

嚴(yán)格模式是如何影響我們前面的例子:

  • 對(duì)于 addElement 函數(shù),當(dāng)從全局作用域調(diào)用時(shí),this 是未定義的
  • 如果沒(méi)有在一個(gè)變量上指定const | let | var,你會(huì)得到以下錯(cuò)誤:
  1. Uncaught ReferenceError: b is not defined 

5. 持有DOM引用

DOM節(jié)點(diǎn)也不能避免內(nèi)存泄漏。我們需要注意不要保存它們的引用。否則,垃圾回收器將無(wú)法清理它們,因?yàn)樗鼈內(nèi)匀皇强稍L問(wèn)的。

用一小段代碼演示一下:

 

  1. const elements = []; 
  2. const list = document.getElementById('list'); 
  3.  
  4. function addElement() { 
  5.     // clean nodes 
  6.     list.innerHTML = ''
  7.  
  8.     const divElement= document.createElement('div'); 
  9.     const element = document.createTextNode(`adding element ${elements.length}`); 
  10.     divElement.appendChild(element); 
  11.  
  12.  
  13.     list.appendChild(divElement); 
  14.     elements.push(divElement); 
  15.  
  16. document.getElementById('addElement').onclick = addElement; 

 

注意,addElement 函數(shù)清除列表 div,并將一個(gè)新元素作為子元素添加到它中。這個(gè)新創(chuàng)建的元素被添加到 elements 數(shù)組中。

下一次執(zhí)行 addElement 時(shí),該元素將從列表 div 中刪除,但是它不適合進(jìn)行垃圾收集,因?yàn)樗鎯?chǔ)在 elements 數(shù)組中。

我們?cè)趫?zhí)行幾次之后監(jiān)視函數(shù):

 

5個(gè)常見(jiàn)的JavaScript內(nèi)存錯(cuò)誤

 

在上面的截圖中看到節(jié)點(diǎn)是如何被泄露的。那怎么解決這個(gè)問(wèn)題?清除 elements 數(shù)組將使它們有資格進(jìn)行垃圾收集。

總結(jié)

在這篇文章中,我們已經(jīng)看到了最常見(jiàn)的內(nèi)存泄露方式。很明顯,JavaScript本身并沒(méi)有泄漏內(nèi)存。相反,它是由開(kāi)發(fā)者方面無(wú)意的內(nèi)存保持造成的。只要代碼是整潔的,而且我們不忘自己清理,就不會(huì)發(fā)生泄漏。

了解內(nèi)存和垃圾回收在JavaScript中是如何工作的是必須的。一些開(kāi)發(fā)者得到了錯(cuò)誤的意識(shí),認(rèn)為由于它是自動(dòng)的,所以他們不需要擔(dān)心這個(gè)問(wèn)題。

 

 

責(zé)任編輯:華軒 來(lái)源: 今日頭條
相關(guān)推薦

2021-06-16 15:04:06

JavaScript內(nèi)存開(kāi)發(fā)

2019-10-14 16:39:50

云計(jì)算配置錯(cuò)誤企業(yè)

2015-07-29 10:46:20

Java錯(cuò)誤

2022-05-31 15:43:15

自動(dòng)化測(cè)試

2020-05-21 18:38:49

JavaScript前端技術(shù)

2022-06-07 12:38:44

云應(yīng)用安全勒索軟件網(wǎng)絡(luò)攻擊

2021-06-28 10:12:34

云計(jì)算云平臺(tái)云計(jì)算架構(gòu)

2021-11-26 05:50:50

Promise JS項(xiàng)目

2019-11-04 05:37:52

SD-WAN軟件定義的廣域網(wǎng)網(wǎng)絡(luò)

2019-06-21 10:13:26

JavaScript錯(cuò)誤開(kāi)發(fā)

2022-12-01 08:00:42

CICD部署

2020-10-19 09:34:04

C語(yǔ)言內(nèi)存錯(cuò)誤編程語(yǔ)言

2020-09-01 07:58:34

API漏洞黑客

2010-07-21 08:51:26

Perl錯(cuò)誤

2022-08-24 14:14:58

JavaScript函數(shù)

2014-03-11 11:33:21

2020-03-20 15:10:09

Python錯(cuò)誤分析代碼

2021-10-19 05:54:58

C語(yǔ)言編程

2013-07-09 15:54:10

VDI虛擬化

2023-09-12 09:47:38

云計(jì)算云管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)