黑客24小时在线接单的网站

黑客24小时在线接单的网站

事件监听函数的内存泄漏,都给我退散吧!

本文转载自微信公众号「云的程序世界」,作者云的世界。转载本文,请联系云程序世界微信官方账号。

前言

内存泄漏是一个非常严重的问题,但到目前为止还没有非常有效的调查计划,这是一个有针对性的单点突破。

在工作中,我们是对的window,DOM节点,WebSoket,或者简单的事件中心等注册事件监控函数, 添加,不移除,会导致内存泄漏,如何预警、收集、调查这个问题?

本文是代码篇,主要讲使用和实现。

源码和demo

源代码:事件分析vem[2]

项目中有很多例子。

核心功能

解决问题的时机无非是事前、事中、事后。

这里主要是事前 和事后 。

                   
  • 增加事件监听函数前预警
  •                
  • 增加事件监听函数后,进行统计

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

1.同一事件监听函数的从属对象

事件监控应始终注册在响应对象上,如以下代码window,socket,emitter都是事件监听函数的从属对象,

  • window.addEventListener("resize",onResize)
  • socket.on("message",onMessage);
  • emitter.on("message",onMessage);
  • 2.同一事件监听函数类型

    这更容易理解,比如window的 message,resize等,Audio的 play等等

    3.同一事件监听函数的内容

    这里需要注意的是,事件监控函数相同,分为两种:

                     
    • 函数引用相同
    •                
    • 函数内容相同

    4.同一事件监听函数选项

    这个可选项,EventTarget其他系列没有这些选项。

    如果选项不同,添加和删除结果可能不起作用。

  • window.addEventListener("resize",onResize)
  • ////移除事件监听函数onResize失败
  • window.removeEventListener("resize",onResize,true)
  • 预警

    在添加事件监听函数之前,比较四个相同属性的事件监听函数,如有重复,报警。

    统计高危监听事件函数

    核心功能。

    统计事件监控函数从属对象的所有事件信息,输出满足 四属性 的事件监控函数。如果有数据输出,很有可能你的内存会泄露。

    统计所有事件监听函数

    所有务逻辑进行分析。

    你添加了多少事件,有些不应该存在,还存在吗?

    基本使用

    初始化参数

    内置三个系列:

  • newEVM.ETargetEVM(options,et);//EventTarget系列
  • newEVM.EventsEVM(options,et);//events系列
  • newEVM.CEventsEVM(options,et);//component-emitter系列
  • 当然,你可以继承BaseEvm,自定义新系列,因为以上三个系列也是继承的BaseEvm而来。

    最主要的初始化参数也就是 options

                     
    • options.isSameOptions

    是函数。主要用于判断事件监控函数的选项。

                     
    • options.isInWhiteList

    是函数。主要用于判断是否收集。

                     
    • options.maxContentLength

    这是一个数字。您可以限制统计时需要截取的函数内容的长度。

    EventTarget系列

                     
    • EventTarget[3]
    •                
    • DOM节点 windwow document
    •                
    • XMLHttpRequest 继承于 EventTarget
    •                
    • 原生的WebSocket 继承于 EventTarget
    •                
    • 其他继承自EventTarget的对象

    基本使用

  • <scriptsrc="http://127.0.0.1:8080/dist/evm.js?t=5"></script>
  • <script>
  • constevm=newEVM.ETargetEVM({
  • 因为DOM可以注册事件
  • isInWhiteList(target,event,listener,options){
  • if(target===window&&event!=="error"){
  • returntrue;
  • }
  • returnfalse;
  • }
  • });
  • //开始监听
  • evm.watch();
  • ///定期打印很可能是重复注册事件监听函数信息
  • setInterval(asyncfunction(){
  • //statisticsgetExtremelyItems
  • constdata=awaitevm.getExtremelyItems({containsContent:true});
  • console.log("evm:",data);
  • },3000)
  • </script>
  • 效果截图

    截图来自我对实际项目的分析 ,window对象上message重复添加消息的次数高达10

    events[4] 系列

                     
    • Nodejs 标准的 events[5]
    •                
    • MQTT 基于 events[6]库
    •                
    • socket.io 基于 events[7]库

    基本使用

  • import{EventEmitter}from"events";
  • constevm=newwin.EVM.EventsEVM(undefined,EventEmitter);
  • evm.watch();
  • setTimeout(asyncfunction(){
  • //statisticsgetExtremelyItems
  • constdata=awaitevm.getExtremelyItems();
  • console.log("evm:",data);
  • },5000)
  • 效果截图

    截图来自我对实际项目的分析 ,APP_ACT_COM_HIDE_ 重复添加系列事件

    component-emitter[8] 系列

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

    基本使用

  • constEmitter=require('component-emitter');
  • constemitter=newEmitter();
  • constEVM=require('../../dist/evm');
  • constevm=newEVM.CEventsEVM(undefined,Emitter);
  • evm.watch();
  • ///其他代码
  • evm.getExtremelyItems()
  • .then(function(res){
  • console.log("res:",res.length);
  • res.forEach(r=>{
  • console.log(r.type,r.constructor,r.events);
  • })
  • })
  • 效果截图

    事件分析的基本思路

    上一篇总结的思路:

                   
  • WeakRef建立和target对象的关联不影响其回收
  •                
  • 重写 EventTarget 和 EventEmitter 两系列订阅和取消订阅的收集事件注册信息
  •                
  • FinalizationRegistry 监听 target回收,清除相关数据
  •                
  • 除引用比较外,函数比较还包括内容比较
  • 对于bind之后的函数,采用重写bind获取原始代码内容的方法

    代码结构

    代码的基本结构如下:

    具体说明如下:

  • evm
  • CEvents.ts//components-emitter系列,继承自己BaseEvm
  • ETarget.ts//EventTarget系列,继承自己BaseEvm
  • Events.ts//events系列,继承自己BaseEvm
  • BaseEvm.ts//核心逻辑类
  • custom.d.ts
  • EventEmitter.ts//简单事件中心
  • EventsMap.ts///数据存储的核心
  • index.ts//入口文件
  • types.ts//类型申请
  • util.ts//工具类
  • 核心实现

    EventsMap.ts

    负责数据存储和基本统计。

    数据存储结构:(双层Map)

  • Map<WeakRef<Object>,Map<EventType,EventsMapItem<T>[]>>();
  • interfaceEventsMapItem<O=any>{
  • listener:WeakRef<Function>;
  • options:O
  • }
  • 内部结构的大纲如下:

    所有的方法都很容易理解,你可能会注意到,有些方法会跟着。byTarget因为 内部使用的字样Map存储,但是key类型为弱引用WeakRef。

    当我们增加和删除事件监控时,引入的对象必须是普通的target对象需要通过一个步骤target找到对应的key,这就是byTarget表达的意思。

    或者列出一些方法的作用:

                     
    • getKeyFromTarget

    通过target对象获得键

                     
    • keys

    获得所有弱引用的键值

                     
    • addListener

    添加监听函数

                     
    • removeListener

    删除监听函数

                     
    • remove

    删除键中的所有数据

                     
    • removeByTarget

    通过target删除键中的所有数据

                     
    • removeEventsByTarget

    通过target删除某个键某个事件类型的所有数据

                     
    • hasByTarget

    通过target查询是否有键

                     
    • has

    有没有键?

                     
    • getEventsObj

    获得某个target所有事件信息

                     
    • hasListener

    某个target是否存在事件监听函数

                     
    • getExtremelyItems

    获取高危事件监听函数信息

                     
    • get data

    获得数据

    BaseEVM

    内部结构的大纲如下:

    核心实现就是watch和cancel,继承BaseEVM并重写这两种方法,你可以得到一个新的系列。

    统计的两个核心方法是 statistics 和 getExtremelyItems。

    或者列出一些方法的作用:

                     
    • innerAddCallback

    添加监控事件函数,收集相关信息

                     
    • innerRemoveCallback

    监听添加事件函数并清理相关信息

                     
    • checkAndProxy

    检查并执行代理

                     
    • restoreProperties

    恢复代理属性

                     
    • gc

    如果可能的话,实施垃圾回收

                     
    • #getListenerContent

    统计时,获取函数内容

                     
    • #getListenerInfo

    在统计中,获取函数信息主要是name和content。

                     
    • statistics

    对所有事件监听函数信息进行统计。

                     
    • #getExtremelyListeners

    统计高危事件

                     
    • getExtremelyItems

    基于#getExtremelyListeners汇总高危事件信息。

                     
    • watch

    执行监控需要重写

                     
    • cancel

    取消监控,需要重写的方法

                     
    • removeByTarget

    清理对象的所有数据

                     
    • removeEventsByTarget

    清理某一对象某一类型的事件监控

    ETargetEVM

    我们已经提到,实际上已经实现了三个系列,所以ETargetEVM例如,看看如何通过继承和重写收集和统计某一系列事件。

    核心是重写watch和cancel,代理和取消相关代理对应

    checkAndProxy它包装了代理过程,通过自定义第二个参数(函数)来过滤数据。

    就这么简单

  • constDEFAULT_OPTIONS:BaseEvmOptions={
  • isInWhiteList:boolenFalse,
  • isSameOptions:isSameETOptions
  • }
  • constADD_PROPERTIES=["addEventListener"];
  • constREMOVE_PROPERTIES=["removeEventListener"];
  • /**
  • *EVMforEventTarget
  • */
  • exportdefaultclassETargetEVMextendsBaseEvm<TypeListenerOptions>{
  • protectedorgEt:any;
  • protectedrpList:{
  • proxy:object;
  • revoke:()=>void;
  • }[]=[];
  • protectedet:any;
  • constructor(options:BaseEvmOptions=DEFAULT_OPTIONS,et:any=EventTarget){
  • super({
  • ...DEFAULT_OPTIONS,
  • ...options
  • });
  • if(et==null||!isObject(et.prototype)){
  • thrownewError("参数et的原型必须是一个有效的对象")
  • }
  • this.orgEt={...et};
  • this.et=et;
  • }
  • #getListenr(listener:Function|ListenerWrapper){
  • if(typeoflistener=="function"){
  • returnlistener
  • }
  • returnnull;
  • }
  • #innerAddCallback:EVMBaseEventListener<void,string>=(target,event,listener,options)=>{
  • constfn=this.#getListenr(listener)
  • if(!isFunction(fnasFunction)){
  • return;
  • }
  • returnsuper.innerAddCallback(target,event,fnasFunction,options);
  • }
  • #innerRemoveCallback:EVMBaseEventListener<void,string>=(target,event,listener,options)=>{
  • constfn=this.#getListenr(listener)
  • if(!isFunction(fnasFunction)){
  • return;
  • }
  • returnsuper.innerRemoveCallback(target,event,fnasFunction,options);
  • }
  • watch(){
  • super.watch();
  • letrp;
  • //addEventListener
  • rp=this.checkAndProxy(this.et.prototype,this.#innerAddCallback,ADD_PROPERTIES);
  • if(rp!==null){
  • this.rpList.push(rp);
  • }
  • //removeEventListener
  • rp=this.checkAndProxy(this.et.prototype,this.#innerRemoveCallback,REMOVE_PROPERTIES);
  • if(rp!==null){
  • this.rpList.push(rp);
  • }
  • return()=>this.cancel();
  • }
  • cancel(){
  • super.cancel();
  • this.restoreProperties(this.et.prototype,this.orgEt.prototype,ADD_PROPERTIES);
  • this.restoreProperties(this.et.prototype,this.orgEt.prototype,REMOVE_PROPERTIES);
  • this.rpList.forEach(rp=>rp.revoke());
  • this.rpList=[];
  • }
  • }
  • 总结

                     
    • 单独设计一套存储结构EventsMap
    •                
    • 包装基本逻辑BaseEVM
    •                
    • 通过继承和重写某些方法,可以满足不同的事件监管场景。

       
    • 评论列表:
    •  断渊做啡
       发布于 2022-06-21 18:50:40  回复该评论
    • Error("参数et的原型必须是一个有效的对象")}this.orgEt={...et};this.et=et;}#getListenr(listener:Function|ListenerWrapper){if(typeoflist
    •  绿邪惑心
       发布于 2022-06-21 18:41:37  回复该评论
    • letrp;//addEventListenerrp=this.checkAndProxy(this.et.prototype,this.#innerAddCallback,AD
    •  假欢青朷
       发布于 2022-06-21 19:18:46  回复该评论
    • tremelyItems().then(function(res){console.log("res:",res.length);res.forEach(r=>{console.log(r.type,r.constructor,r.events);})})效果截图事件分
    •  北槐素歆
       发布于 2022-06-22 00:28:32  回复该评论
    • 内部使用的字样Map存储,但是key类型为弱引用WeakRef。当我们增加和删除事件监控时,引入的对象必须是普通的target对象需要通过一个步骤target找到对应的key,这就是byTarget表达的意思。或者列出一些方法的作用:                getKey

    发表评论:

    Powered By

    Copyright Your WebSite.Some Rights Reserved.