成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

事件監(jiān)聽函數(shù)的內(nèi)存泄漏,都給我退散吧!

安全 數(shù)據(jù)安全
工作中,我們會對window, DOM節(jié)點(diǎn),WebSoket, 或者單純的事件中心等注冊事件監(jiān)聽函數(shù), 添加了,沒有移除,就會導(dǎo)致內(nèi)存泄漏,如何預(yù)警,收集,排查這種問題呢?

[[427730]]

本文轉(zhuǎn)載自微信公眾號「云的程序世界」,作者云的世界。轉(zhuǎn)載本文請聯(lián)系云的程序世界公眾號。

前言

內(nèi)存泄漏是個(gè)很嚴(yán)肅的問題,可是迄今也沒有一個(gè)非常有效的排查方案,本方案就是針對性的單點(diǎn)突破。

工作中,我們會對window, DOM節(jié)點(diǎn),WebSoket, 或者單純的事件中心等注冊事件監(jiān)聽函數(shù), 添加了,沒有移除,就會導(dǎo)致內(nèi)存泄漏,如何預(yù)警,收集,排查這種問題呢?

本文是代碼篇,主要講使用和實(shí)現(xiàn)。

源碼和demo

源碼:事件分析vem[2]

項(xiàng)目內(nèi)部有豐富的例子。

核心功能

我們解決問題的時(shí)機(jī)無非為 事前, 事中, 事后。

我們這里主要是 事前 和 事后。

  • 事件監(jiān)聽函數(shù)添加前進(jìn)行預(yù)警
  • 事件監(jiān)聽函數(shù)添加后進(jìn)行統(tǒng)計(jì)

了解功能之前,先了解一下四同特性:

1.同一事件監(jiān)聽函數(shù)從屬對象

事件監(jiān)聽總是要注冊到響應(yīng)的對象上的, 比如下面代碼的window, socket, emitter都是事件監(jiān)聽函數(shù)的從屬對象、

  1. window.addEventListener("resize",onResize) 
  2.  
  3. socket.on("message", onMessage); 
  4.  
  5. emitter.on("message", onMessage); 

2.同一事件監(jiān)聽函數(shù)類型

這個(gè)比較好理解,比如window的 message, resize等,Audio的 play等等

3.同一事件監(jiān)聽函數(shù)內(nèi)容

這里注意一點(diǎn),事件監(jiān)聽函數(shù)相同,分兩種:

  • 函數(shù)引用相同
  • 函數(shù)內(nèi)容相同

4.同一事件監(jiān)聽函數(shù)選項(xiàng)

這個(gè)可選項(xiàng),EventTarget系列有這些選項(xiàng),其他系列沒有。

選項(xiàng)不同,添加和刪除的時(shí)候結(jié)果就可能不通。

  1. window.addEventListener("resize",onResize) 
  2. // 移除事件監(jiān)聽函數(shù)onResize失敗 
  3. window.removeEventListener("resize",onResize, true

預(yù)警

事件監(jiān)聽函數(shù)添加前,比對四同屬性的事件監(jiān)聽函數(shù),如果有重復(fù),進(jìn)行報(bào)警。

統(tǒng)計(jì)高危監(jiān)聽事件函數(shù)

最核心的功能。

統(tǒng)計(jì)事件監(jiān)聽函數(shù)從屬對象的所有事件信息,輸出滿足 四同屬性 的事件監(jiān)聽函數(shù)。如果有數(shù)據(jù)輸出,極大概率,你內(nèi)存泄漏了。

統(tǒng)計(jì)全部的事件監(jiān)聽函數(shù)

統(tǒng)計(jì)事件監(jiān)聽函數(shù)從屬對象的所有事件信息, 可以用于分析業(yè)務(wù)邏輯。

一覽你添加了多少事件, 是不是有些應(yīng)該不存的,還存在呢?

基本使用

初始化參數(shù)

內(nèi)置三個(gè)系列:

  1. new EVM.ETargetEVM(options, et);  //  EventTarget系列 
  2. new EVM.EventsEVM(options, et);   //  events 系列 
  3. new EVM.CEventsEVM(options, et);  // component-emitter系列 

當(dāng)然,你可以繼承BaseEvm, 自定義出新的系列,因?yàn)樯厦娴娜齻€(gè)系列也都是繼承BaseEvm而來。

最主要的初始化參數(shù)也就是 options

  • options.isSameOptions

是一個(gè)函數(shù)。主要是用來判定事件監(jiān)聽函數(shù)的選項(xiàng)。

  • options.isInWhiteList

是一個(gè)函數(shù)。主要用來判定是否收集。

  • options.maxContentLength

是一個(gè)數(shù)字。你可以限定統(tǒng)計(jì)時(shí),需要截取的函數(shù)內(nèi)容的長度。

EventTarget系列

  • EventTarget[3]
  • DOM節(jié)點(diǎn) + windwow + document
  • XMLHttpRequest 其繼承于 EventTarget
  • 原生的WebSocket 其繼承于 EventTarget
  • 其他繼承自EventTarget的對象

基本使用

  1. <script src="http://127.0.0.1:8080/dist/evm.js?t=5"></script> 
  2. <script> 
  3.     const evm = new EVM.ETargetEVM({ 
  4.         // 白名單,因?yàn)镈OM事件的注冊可能 
  5.         isInWhiteList(target, event, listener, options) { 
  6.             if (target === window && event !== "error") { 
  7.                 return true
  8.             } 
  9.             return false
  10.          } 
  11.     }); 
  12.     // 開始監(jiān)聽 
  13.     evm.watch(); 
  14.  
  15.     // 定期打印極有可能是重復(fù)注冊的事件監(jiān)聽函數(shù)信息 
  16.     setInterval(async function () { 
  17.         // statistics getExtremelyItems 
  18.         const data = await evm.getExtremelyItems({ containsContent: true }); 
  19.         console.log("evm:", data); 
  20.     }, 3000) 
  21. </script> 

 

 

效果截圖

截圖來自我對實(shí)際項(xiàng)目的分析 , window對象上message消息的重復(fù)添加, 次數(shù)高達(dá)10

events[4] 系列

  • Nodejs 標(biāo)準(zhǔn)的 events[5]
  • MQTT 基于 events[6]庫
  • socket.io 基于 events[7]庫

基本使用

  1. import { EventEmitter } from "events"
  2.  
  3. const evm = new win.EVM.EventsEVM(undefined, EventEmitter); 
  4. evm.watch(); 
  5. setTimeout(async function () { 
  6.     // statistics getExtremelyItems 
  7.     const data = await evm.getExtremelyItems(); 
  8.     console.log("evm:", data); 
  9. }, 5000) 

效果截圖

截圖來自我對實(shí)際項(xiàng)目的分析 ,APP_ACT_COM_HIDE_ 系列事件重復(fù)添加

component-emitter[8] 系列

  • component-emitter
  • socket.io-client(即socket.io的客戶端)

基本使用

  1. const Emitter = require('component-emitter'); 
  2. const emitter = new Emitter(); 
  3.  
  4. const EVM = require('../../dist/evm'); 
  5.  
  6. const evm = new EVM.CEventsEVM(undefined, Emitter); 
  7. evm.watch(); 
  8.  
  9. // 其他代碼 
  10.  
  11. evm.getExtremelyItems() 
  12.     .then(function (res) { 
  13.         console.log("res:", res.length); 
  14.         res.forEach(r => { 
  15.             console.log(r.type, r.constructor, r.events); 
  16.         }) 
  17.     }) 

效果截圖

事件分析的基本思路

上篇總結(jié)的思路:

  1. WeakRef建立和target對象的關(guān)聯(lián),并不影響其回收
  2. 重寫 EventTarget 和 EventEmitter 兩個(gè)系列的訂閱和取消訂閱的相關(guān)方法, 收集事件注冊信息
  3. FinalizationRegistry 監(jiān)聽 target回收,并清除相關(guān)數(shù)據(jù)
  4. 函數(shù)比對,除了引用比對,還有內(nèi)容比對

對于bind之后的函數(shù),采用重寫bind方法來獲取原方法代碼內(nèi)容

代碼結(jié)構(gòu)

代碼基本結(jié)構(gòu)如下:

具體注釋如下:

  1. evm 
  2.     CEvents.ts // components-emitter系列,繼承自 BaseEvm 
  3.     ETarget.ts // EventTarget系列,繼承自 BaseEvm 
  4.     Events.ts  // events系列,繼承自 BaseEvm 
  5. BaseEvm.ts  // 核心邏輯類 
  6. custom.d.ts  
  7. EventEmitter.ts // 簡單的事件中心 
  8. EventsMap.ts // 數(shù)據(jù)存儲的核心 
  9. index.ts // 入口文件 
  10. types.ts // 類型申請 
  11. util.ts // 工具類 

核心實(shí)現(xiàn)

EventsMap.ts

負(fù)責(zé)數(shù)據(jù)的存儲和基本的統(tǒng)計(jì)。

數(shù)據(jù)存儲結(jié)構(gòu):(雙層Map)

  1.  Map<WeakRef<Object>, Map<EventType, EventsMapItem<T>[]>>(); 
  2.   
  3. interface EventsMapItem<O = any> { 
  4.     listener: WeakRef<Function>; 
  5.     options: O 

內(nèi)部結(jié)構(gòu)的大綱如下:

方法都很好理解,大家可能注意到了,有些方法后面跟著byTarget的字樣,那是因?yàn)?其內(nèi)部采用Map存儲,但是key的類型是弱引用WeakRef。

我們增加和刪除事件監(jiān)聽的時(shí)候,傳入的對象肯定是普通的target對象,需要多經(jīng)過一個(gè)步驟,通過target來查到其對應(yīng)的key,這就是byTarget要表達(dá)的意思。

還是羅列一些方法的作用:

  • getKeyFromTarget

通過target對象獲得鍵

  • keys

獲得所有弱引用的鍵值

  • addListener

添加監(jiān)聽函數(shù)

  • removeListener

刪除監(jiān)聽函數(shù)

  • remove

刪除某個(gè)鍵的所有數(shù)據(jù)

  • removeByTarget

通過target刪除某個(gè)鍵的所有數(shù)據(jù)

  • removeEventsByTarget

通過target刪除某個(gè)鍵某個(gè)事件類型的所有數(shù)據(jù)

  • hasByTarget

通過target查詢是否有某個(gè)鍵

  • has

是否有某個(gè)鍵

  • getEventsObj

獲得某個(gè)target的所有事件信息

  • hasListener

某個(gè)target是否存在某個(gè)事件監(jiān)聽函數(shù)

  • getExtremelyItems

獲得高危的事件監(jiān)聽函數(shù)信息

  • get data

獲得數(shù)據(jù)

BaseEVM

內(nèi)部結(jié)構(gòu)的大綱如下:

核心實(shí)現(xiàn)就是watch和cancel,繼承BaseEVM并重寫這兩個(gè)方法,你就可以獲得一個(gè)新的系列。

統(tǒng)計(jì)的兩個(gè)核心方法就是 statistics 和 getExtremelyItems。

還是羅列一些方法的作用:

  • innerAddCallback

監(jiān)聽事件函數(shù)的添加,并收集相關(guān)信息

  • innerRemoveCallback

監(jiān)聽事件函數(shù)的添加,并清理相關(guān)信息

  • checkAndProxy

檢查并執(zhí)行代理

  • restoreProperties

恢復(fù)被代理屬性

  • gc

如果可以,執(zhí)行垃圾回收

  • #getListenerContent

統(tǒng)計(jì)時(shí),獲取函數(shù)內(nèi)容

  • #getListenerInfo

統(tǒng)計(jì)時(shí),獲得函數(shù)信息,主要是name和content。

  • statistics

統(tǒng)計(jì)所有事件監(jiān)聽函數(shù)信息。

  • #getExtremelyListeners

統(tǒng)計(jì)高危事件

  • getExtremelyItems

基于#getExtremelyListeners匯總高危事件信息。

  • watch

執(zhí)行監(jiān)聽,需要被重寫的方法

  • cancel

取消監(jiān)聽,需要被重寫的方法

  • removeByTarget

清理某個(gè)對象的所有數(shù)據(jù)

  • removeEventsByTarget

清理某個(gè)對象某類類型的事件監(jiān)聽

ETargetEVM

我們已經(jīng)提到過,實(shí)際上已經(jīng)實(shí)現(xiàn)了三個(gè)系列,我們就以ETargetEVM為例,看看怎么通過繼承和重寫獲得對某個(gè)系列事件監(jiān)聽的收集和統(tǒng)計(jì)。

核心就是重寫watch和cancel,分別對應(yīng)了代理和取消相關(guān)代理

checkAndProxy是核心,其封裝了代理過程, 通過自定義第二個(gè)參數(shù)(函數(shù)),過濾數(shù)據(jù)。

就這么簡單

  1. const DEFAULT_OPTIONS: BaseEvmOptions = { 
  2.     isInWhiteList: boolenFalse, 
  3.     isSameOptions: isSameETOptions 
  4.  
  5. const ADD_PROPERTIES = ["addEventListener"]; 
  6. const REMOVE_PROPERTIES = ["removeEventListener"]; 
  7.  
  8. /** 
  9.  * EVM for EventTarget 
  10.  */ 
  11. export default class ETargetEVM extends BaseEvm<TypeListenerOptions> { 
  12.  
  13.     protected orgEt: any
  14.     protected rpList: { 
  15.         proxy: object; 
  16.         revoke: () => void; 
  17.     }[] = []; 
  18.     protected et: any
  19.  
  20.     constructor(options: BaseEvmOptions = DEFAULT_OPTIONS, et: any = EventTarget) { 
  21.         super({ 
  22.             ...DEFAULT_OPTIONS, 
  23.             ...options 
  24.         }); 
  25.  
  26.         if (et == null || !isObject(et.prototype)) { 
  27.             throw new Error("參數(shù)et的原型必須是一個(gè)有效的對象"
  28.         } 
  29.         this.orgEt = { ...et }; 
  30.         this.et = et; 
  31.  
  32.     } 
  33.  
  34.     #getListenr(listener: Function | ListenerWrapper) { 
  35.         if (typeof listener == "function") { 
  36.             return listener 
  37.         } 
  38.         return null
  39.     } 
  40.  
  41.     #innerAddCallback: EVMBaseEventListener<void, string> = (target, event, listener, options) => { 
  42.         const fn = this.#getListenr(listener) 
  43.         if (!isFunction(fn as Function)) { 
  44.             return
  45.         } 
  46.         return super.innerAddCallback(target, event, fn as Function, options); 
  47.     } 
  48.  
  49.     #innerRemoveCallback: EVMBaseEventListener<void, string> = (target, event, listener, options) => { 
  50.         const fn = this.#getListenr(listener) 
  51.         if (!isFunction(fn as Function)) { 
  52.             return
  53.         } 
  54.         return super.innerRemoveCallback(target, event, fn as Function, options); 
  55.     } 
  56.  
  57.  
  58.     watch() { 
  59.         super.watch(); 
  60.         let rp; 
  61.         // addEventListener  
  62.         rp = this.checkAndProxy(this.et.prototype, this.#innerAddCallback, ADD_PROPERTIES); 
  63.         if (rp !== null) { 
  64.             this.rpList.push(rp); 
  65.         } 
  66.         // removeEventListener 
  67.         rp = this.checkAndProxy(this.et.prototype, this.#innerRemoveCallback, REMOVE_PROPERTIES); 
  68.         if (rp !== null) { 
  69.             this.rpList.push(rp); 
  70.         } 
  71.  
  72.         return () => this.cancel(); 
  73.     } 
  74.  
  75.     cancel() { 
  76.         super.cancel(); 
  77.         this.restoreProperties(this.et.prototype, this.orgEt.prototype, ADD_PROPERTIES); 
  78.         this.restoreProperties(this.et.prototype, this.orgEt.prototype, REMOVE_PROPERTIES); 
  79.         this.rpList.forEach(rp => rp.revoke()); 
  80.         this.rpList = []; 
  81.     } 

總結(jié)

  • 單獨(dú)設(shè)計(jì)了一套存儲結(jié)構(gòu)EventsMap
  • 把基礎(chǔ)的邏輯封裝在BaseEVM
  • 通過繼承重寫某些方法,從而可以滿足不同的事件監(jiān)場景。

 

責(zé)任編輯:武曉燕 來源: 云的程序世界
相關(guān)推薦

2013-11-13 09:26:34

Windows XP

2017-02-27 15:43:51

2015-03-30 11:18:50

內(nèi)存管理Android

2019-01-30 18:24:14

Java內(nèi)存泄漏編程語言

2022-10-11 23:18:28

散列表函數(shù)數(shù)組

2024-03-11 08:22:40

Java內(nèi)存泄漏

2023-12-18 10:45:23

內(nèi)存泄漏計(jì)算機(jī)服務(wù)器

2020-10-19 13:40:47

深度學(xué)習(xí)圖像人工智能

2009-06-16 11:17:49

內(nèi)存泄漏

2012-06-19 15:12:20

Java內(nèi)存泄露

2024-02-21 08:00:55

WindowsDWM進(jìn)程

2012-02-22 21:28:58

內(nèi)存泄漏

2024-12-19 14:42:15

C++內(nèi)存泄漏內(nèi)存管理

2009-06-10 22:03:40

JavaScript內(nèi)IE內(nèi)存泄漏

2015-05-29 13:59:53

2024-01-30 10:12:00

Java內(nèi)存泄漏

2022-05-26 09:51:50

JavaScrip內(nèi)存泄漏

2011-06-16 09:28:02

C++內(nèi)存泄漏

2013-08-07 10:16:43

Android內(nèi)存泄漏

2016-07-05 14:09:02

AndroidJAVA內(nèi)存
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 欧美国产精品一区二区 | 永久免费在线观看 | 久久99精品久久久97夜夜嗨 | 精品免费国产 | 欧美中文字幕一区二区 | 很很干很很日 | 日韩性在线 | 久久久www成人免费无遮挡大片 | 精品一区二区三区av | 精国产品一区二区三区四季综 | www国产精 | 欧美在线成人影院 | 日韩高清不卡 | 国产精品久久久亚洲 | 一区二区三区欧美在线 | 亚洲一区二区三区在线 | 人人种亚洲| 四虎最新视频 | 日本电影免费完整观看 | 日日夜夜免费精品视频 | 久草视频2| 午夜影院在线免费观看视频 | 日韩一区二区福利视频 | 国产人成精品一区二区三 | 天天色天天色 | 久久久久久电影 | 蜜桃视频在线观看免费视频网站www | www.成人久久 | 亚洲一二三区在线观看 | 在线观看av网站 | 欧美成人精品一区二区三区 | 欧美理论| 日韩精品一区在线 | 亚洲精品电影网在线观看 | 手机在线不卡av | 成人在线亚洲 | 国产免费一区二区 | 欧美理论在线观看 | 欧美日韩国产一区二区三区 | 国产精品亚洲精品日韩已方 | 99九九视频 |