为了账号安全,请及时绑定邮箱和手机立即绑定

【备战春招】第10天 破解JavaScript高级玩法 第十七讲

标签:
JavaScript

课程章节: 综合案例-事件分析库

主讲老师: Cloud

课程内容:

今天学习的内容包括:

事件分析

课程收获:

17.1 心得:

BaseEvm.ts

import EventEmitter from "./EventEmitter";
import EvmEventsMap from "./EventsMap";
import { BaseEvmOptions, EventsMapItem, EventType, StatisticsOptions, TypeListenerOptions } from "./types";
import { booleanFalse, isSameStringifyObject, checkAndProxy, createPureObject, delay, getFunctionContent, isBuiltinFunctionContent, isFunction, isObject, restoreProperties, getStack, executeGC } from "./util";
import * as bindUtil from "./bindUtil"

const DEFAULT_OPTIONS: BaseEvmOptions = {
  /**
   * 选项相同判断函数
  */
  isSameOptions: isSameStringifyObject,
  /**
   * 白名单判断函数
   */
  isInWhiteList: booleanFalse,
  maxContentLength: 200,
  overrideBind: false,
}


const toString = Object.prototype.toString

export default class EVM<O = any>{
  protected watched: boolean = false;
  private emitter = new EventEmitter();
  private eventsMap: EvmEventsMap<O>;

  private options: BaseEvmOptions;

  constructor(options: BaseEvmOptions<O> = {}) {
    this.options = {
      ...DEFAULT_OPTIONS,
      ...options
    };

    this.eventsMap = new EvmEventsMap({
      isSameOptions: this.options.isSameOptions!
    });

    this.innerAddCallback = this.innerAddCallback.bind(this);
    this.innerRemoveCallback = this.innerRemoveCallback.bind(this);
  }

  #listenerRegistry = new FinalizationRegistry<{ weakRefTarget: WeakRef<object> }>(
    ({ weakRefTarget }) => {
      console.log("evm::clean up ------------------");
      if (!weakRefTarget) {
        return;
      }
      this.eventsMap.remove(weakRefTarget);
      console.log("length", [...this.eventsMap.data.keys()].length);
    }
  )

  innerAddCallback(target: Object, event: EventType, listener: Function, options: O) {

    const { isInWhiteList } = this.options;

    if (!isInWhiteList!(target, event, listener, options)) {
      return;
    }

    if (!isFunction(listener)) {
      return console.warn("EVM::innerAddCallback listener must be a function");
    }

    // EventTarget  https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener#multiple_identical_event_listeners
    // 多次添加,覆盖
    if (isObject(target) && target instanceof EventTarget && this.eventsMap.hasListener(target, event, listener, options)) {
      return console.log(`EventTarget 注册了多个相同的 EventListener, 多余的丢弃!${toString.call(target)} ${event} ${listener.name} 多余的丢弃`);
    }

    const eItems = this.eventsMap.getExtremelyItems(target, event, listener, options);
    if (Array.isArray(eItems) && eItems.length > 0) {
      console.warn(`${toString.call(target)}-${target.constructor.name}`, " ExtremelyItems: type:", event, " name:" + (listener.name || "unknown"), " options: " + options, " content:" + listener.toString().slice(0, 100));
    }

    // console.log("add:", Object.prototype.toString.call(target), event);

    let weakRefTarget;
    if (!this.eventsMap.hasByTarget(target)) {
      weakRefTarget = new WeakRef(target);
      this.#listenerRegistry.register(target, { weakRefTarget });
    }

    this.eventsMap.addListener(weakRefTarget ? weakRefTarget : target, event, listener, options);
    // this.#emitter.emit("on-add", ...argList);

  }

  innerRemoveCallback(target: Object, event: EventType, listener: Function, options: O) {

    const { isInWhiteList } = this.options;
    if (!isInWhiteList!(target, event, listener, options)) {
      return;
    }

    if (!isFunction(listener)) {
      return console.warn("EVM::innerAddCallback listener must be a function");
    }

    if (!this.eventsMap.hasByTarget(target)) {
      return;
    }
    // console.log("remove:", Object.prototype.toString.call(target), event);

    this.eventsMap.removeListener(target, event, listener, options);
    // this.#emitter.emit("on-remove", ...argList)
  }


  /**
   * 检查属性,并产生代理
   * @param prototype 
   * @param callback 
   * @param ckProperties 
   * @param proxyProperties 
   * @returns 
   */
  protected checkAndProxy = checkAndProxy;

  /**
   * 还原属性方法
   */
  protected restoreProperties = restoreProperties;



  #getListenerContent(listener: Function) {
    // const { maxContentLength } = this.options;
    return listener.toString(); //.slice(0, maxContentLength)
  }

  #getListenerInfo(listener: Function, containsContent: boolean = false) {
    const name = listener.name || "unkown";
    if (!containsContent) {
      return name;
    }
    return createPureObject({
      name,
      content: this.#getListenerContent(listener),
      stack: getStack(listener)
    }) as Record<string, any>;
  }

  async statistics({
    containsContent = false,
    forceGC = true,
  }: StatisticsOptions = {}) {

    if (forceGC) {
      await executeGC()
    }

    const data = this.data;
    const keys = [...data.keys()];
    const d = keys.map(wr => {
      const el = wr.deref();
      if (!el) return null;

      const events = data.get(wr);
      if (!events) {
        return createPureObject();
      }
      return {
        constructor: el?.constructor?.name,
        type: toString.call(el),
        // id: el.id,
        // class: el.className,
        events: [...events.keys()].reduce((obj, cur) => {
          const items = events.get(cur)?.map(e => {
            const fn = e.listener.deref();
            if (!fn) return null;
            return this.#getListenerInfo(fn, containsContent);
          }).filter(Boolean)

          if (items && items.length > 0) {
            obj.set(cur, items);
          }

          return obj
        }, new Map())
      }
    })

    return d;
  }

  #getExtremelyListeners(eventsInfo: EventsMapItem[] = []) {
    const map = new Map();
    let listener, listenerStr, listenerKeyStr;
    let info;
    for (let i = 0; i < eventsInfo.length; i++) {
      info = 0;
      const eInfo = eventsInfo[i];
      listener = eInfo.listener.deref();
      // 被回收了
      if (!listener) {
        continue;
      }
      // 函数 + options
      listenerStr = getFunctionContent(listener)
      if (isBuiltinFunctionContent(listenerStr)) {
        continue;
      }
      // TODO::  improve
      listenerKeyStr = listenerStr + ` %s----%s ${JSON.stringify(eInfo.options)}`
      // console.log("listenerKeyStr:", listenerKeyStr);
      info = map.get(listenerKeyStr);
      if (!info) {
        map.set(listenerKeyStr, {
          ...(this.#getListenerInfo(listener, true) as Object),
          count: 1,
          options: eInfo.options
        })
      } else {
        info.count++
      }
    }

    return [...map.values()].filter(v => v.count > 1);
  }

  async getExtremelyItems(forceGC: boolean = true) {

    if (forceGC) {
      await executeGC();
    }

    const data = this.data;
    const keys = [...data.keys()];
    const d = keys.map(wr => {
      const el = wr.deref();
      if (!el) return null;

      const eventsObj = data.get(wr);

      if (!eventsObj) {
        return createPureObject();
      }

      let exItems: EventsMapItem[];
      const eventsMap = [...eventsObj.keys()].reduce((obj, cur: EventType) => {
        exItems = this.#getExtremelyListeners(eventsObj.get(cur));
        if (exItems.length > 0) {
          obj.set(cur, exItems);
        }
        return obj

        // 使用map而不适用Object,因为key可能是Symbol
      }, new Map());

      const events = [...eventsMap.keys()].reduce((evs, key) => {
        const arr = eventsMap.get(key) || [];
        evs.push(...arr.map((ev: any) => {
          ev.key = key;
          return ev;
        }));
        return evs;
      }, [])


      return events.length > 0 ? createPureObject({
        type: toString.call(el),
        constructor: el?.constructor?.name,
        key: events[0].key,
        // id: el.id,
        // class: el.className,
        events
      }) : null
    }).filter(Boolean)

    return d;
  }


  // onAdd(fn: Function): void {
  //   this.emitter.on("on-add", fn)
  // }

  // offAdd(fn: Function) {
  //   this.emitter.off("on-add", fn)
  // }

  // onRemove(fn: Function) {
  //   this.emitter.on("on-remove", fn)
  // }

  // offRemove(fn: Function) {
  //   this.emitter.off("on-remove", fn)
  // }

  // onAlarm(fn: Function) {
  //   this.emitter.on("on-alarm", fn)
  // }

  // offAlarm(fn: Function) {
  //   this.emitter.off("on-alarm", fn)
  // }

  watch() {
    if (this.watched) {
      return console.error("watched")
    }
    if (this.options.overrideBind) {
      bindUtil.doBind();
    }
    this.watched = true;
  }

  cancel() {
    this.watched = false;
    if (this.options.overrideBind) {
      bindUtil.undoBind();
    }
  }

  get data() {
    return this.eventsMap.data;
  }

  removeByTarget(target: Object) {
    this.eventsMap.removeByTarget(target);
  }

  removeEventsByTarget(target: Object, type: EventType) {
    this.eventsMap.removeEventsByTarget(target, type);
  }

}

EventEmitter

import { EventEmitterItem } from "./types";

function isListener(listener: unknown): boolean {
    if (typeof listener === 'function') {
        return true
    }
    return false;
}


const pureObject = Object.create(null);

export default class EventEmitter {

    #events: Record<string, EventEmitterItem[]> = pureObject;

    #addListener(event: string, listener: Function, once: boolean) {
        if (!event || !listener) return false;

        if (!isListener(listener)) {
            throw new TypeError('listener must be a function');
        }
        const listeners = (this.#events[event] = this.#events[event] || []);
        listeners.push({
            listener,
            once
        });

        return true;
    }

    #removeListener(event: string, listener: Function) {
        const listeners = this.#events[event];
        if (!listeners) return false;

        const index = listeners.findIndex(l => l.listener === listener);

        // 如果不是 -1, ~-1 =  -(-1 + 1) = 0
        if (~index) {
            listeners.splice(index, 1);
            return true;
        }
        return false;
    }

    on(event: string, listener: Function) {
        this.#addListener(event, listener, false);
        return this;
    };

    once(event: string, listener: Function) {
        this.#addListener(event, listener, true);
        return this;
    };

    off(event: string, listener: Function) {
        this.#removeListener(event, listener);
        return this;
    };

    offAll(event: string) {
        if (event && this.#events[event]) {
            this.#events[event] = []
        } else {
            this.#events = pureObject
        }
        return this;
    };

    emit(event: string, ...args: any[]) {
        const listeners = this.#events[event];
        if (!listeners) return;

        // 倒叙遍历,不,我就不
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i];
            if (!listener) {
                continue;
            }
            listener.listener.apply(this, args);
            // TODO??
            if (listener.once && this.#removeListener(event, listener.listener)) {
                i--;
            }
        }
        return this;
    };
}

EventsMap

import { EventsMapItem, EventType, EvmEventsMapOptions, ISameFunction, ISameOptions } from "./types";
import { copyListenerOption, isSameFunction, isSameStringifyObject } from "./util";

const DEFAULT_OPTIONS: EvmEventsMapOptions = {
    isSameOptions: isSameStringifyObject,
    isSameFunction,
}

export default class EvmEventsMap<T = any> {

    private isSameOptions: ISameOptions<T>;
    private isSameFunction: ISameFunction;
    constructor(options: EvmEventsMapOptions = DEFAULT_OPTIONS) {
        const opt = { ...DEFAULT_OPTIONS, ...options };
        this.isSameOptions = opt.isSameOptions!;
        this.isSameFunction = opt.isSameFunction!;
    }

    #map = new Map<WeakRef<Object>, Map<EventType, EventsMapItem<T>[]>>();

    /**
     * 
     * @param target 被弱引用的对象
     * @returns 
     */
    getKeyFromTarget(target: object) {
        const keys = this.keys();

        const index = keys.findIndex(wrKey => {
            const key = wrKey.deref();
            if (!key) return false;
            return key === target;
        });

        return keys[index];
    }

    keys() {
        return [...this.#map.keys()];
    }

    /**
     * 添加
     * @param target object或者 WeakRef(object)
     * @param event 事件类型,比如message,click等
     * @param listener 事件处理程序
     */
    addListener(target: Object, event: EventType, listener: Function, options: T) {

        const map = this.#map;

        let t: Map<EventType, EventsMapItem<T>[]> | undefined;
        // target 如果是 WeakRef, 直接使用
        let wrTarget = target instanceof WeakRef ? target : this.getKeyFromTarget(target);

        if (!wrTarget) {
            wrTarget = new WeakRef(target);
        }
        t = this.#map.get(wrTarget);
        if (!t) {
            t = new Map<EventType, EventsMapItem<T>[]>();
            map.set(wrTarget, t);
        }

        if (!t.has(event)) {
            t.set(event, []);
        }
        const eventsInfo = t.get(event);
        if (!eventsInfo) {
            return this;
        }
        eventsInfo.push({
            listener: new WeakRef(listener),
            options: copyListenerOption(options) as T
        });
        return this;
    }

    /**
     * 添加
     * @param target object或者 WeakRef(object)
     * @param event 事件类型,比如message,click等
     * @param listener 事件处理程序
     */
    removeListener(target: Object, event: EventType, listener: Function, options: T) {
        const map = this.#map;

        let wrTarget = target instanceof WeakRef ? target : this.getKeyFromTarget(target);
        if (!wrTarget) {
            return console.error('EvmEventsMap:: remove failed, target is not found');
        }

        const t = map.get(wrTarget);

        if (!t) {
            return
        }
        if (!t.has(event)) {
            return console.error(`EvmEventsMap:: remove failed, event (${event}) is not found`);
        }

        // options 不能比同一个对象,比字符串的值
        const eventsInfo = t.get(event);
        if (!eventsInfo) {
            return this;
        }
        const index = eventsInfo.findIndex(l => {
            const fun = l.listener.deref();
            if (!fun) {
                return false;
            }
            return fun === listener && this.isSameOptions(l.options, options)
        });

        if (index >= 0) {
            eventsInfo.splice(index, 1);
        }

        const hasItem = eventsInfo.some(l => l.listener.deref());
        if (!hasItem) {
            t.delete(event);
        }
        if (Object.keys(t).length === 0) {
            map.delete(wrTarget);
        }
        return this;
    }

    /**
     * 
     * @param wrTarget WeakRef(object)
     * @returns 
     */
    remove(wrTarget: WeakRef<object>) {
        return this.#map.delete(wrTarget);
    }

    /**
     * 删除某个实例全部信息
     * @param target  object
     * @returns 
     */
    removeByTarget(target: object) {
        const wrTarget = this.getKeyFromTarget(target);
        if (!wrTarget) {
            return;
        }
        return this.#map.delete(wrTarget);
    }


    /**
     * 删除某个实例的某个类别的全部信息
     * @param target 
     * @param event 
     */
    removeEventsByTarget(target: object, event: EventType) {
        const wrTarget = this.getKeyFromTarget(target);
        if (!wrTarget) {
            return;
        }

        const infos = this.#map.get(wrTarget);
        if (!infos) {
            return;
        }

        return infos.delete(event);
    }

    /**
     * 
     * @param target  object
     * @returns 
     */
    hasByTarget(target: object) {
        return !!this.getKeyFromTarget(target)
    }

    /**
     * 
     * @param wrTarget WeakRef(object)
     * @returns 
     */
    has(wrTarget: WeakRef<object>) {
        return this.#map.has(wrTarget);
    }

    /**
     * 获取关联的事件信息信息
     * @param target 
     * @returns 
     */
    getEventsObj(target: object) {
        let wrTarget = this.getKeyFromTarget(target);
        if (!wrTarget) {
            return null;
        }
        const eventsObj = this.#map.get(wrTarget);
        return eventsObj;
    }


    /**
     * 是有已经有listener
     * @param target 
     * @param event 
     * @param listener 
     * @param options 
     * @returns 
     */
    hasListener(target: Object, event: EventType, listener: Function, options: T) {
        let wrTarget = this.getKeyFromTarget(target);
        if (!wrTarget) {
            return false;
        }
        const t = this.#map.get(wrTarget);
        if (!t) return false;
        const wrListeners = t.get(event);

        if (!Array.isArray(wrListeners)) {
            return false;
        }

        return wrListeners.findIndex(lObj => {
            const l = lObj.listener.deref();
            if (!l) {
                return false;
            }
            return l === listener && this.isSameOptions(options, lObj.options)
        }) > -1

    }

    /**
     * 获取极可能是有问题的事件监听信息
     * @param target 
     * @param event 
     * @param listener 
     * @param options 
     * @returns 
     */
    getExtremelyItems(target: Object, event: EventType, listener: Function, options: T) {

        const eventsObj = this.getEventsObj(target);
        if (!eventsObj) {
            return null;
        }
        const listenerObjs = eventsObj.get(event);
        if (!listenerObjs) {
            return null;
        }
        const items = listenerObjs.filter(l => this.isSameFunction(l.listener.deref(), listener, true) && this.isSameOptions(l.options, options));
        return items;
    }

    get data() {
        return this.#map
    }
}

bindUtil

let oriBind: any, isOverride = false, oriToString: any;

const symbolKey = `__xyz_symbol_key_zyx__(~!@#$%^&*()_+)__`;

export const SymbolForBind = Symbol.for(`${symbolKey}`);

export function undoBind() {
    if (!isOverride) {
        return;
    }
    delete oriBind[SymbolForBind];
    Function.prototype.bind = oriBind;
    Function.prototype.toString = oriToString;
}

const { hasOwnProperty } = Object.prototype;
export function doBind() {
    oriBind = Function.prototype.bind;
    if (hasOwnProperty.call(oriBind, SymbolForBind) || isOverride) {
        return undoBind;
    }
    oriToString = Function.prototype.toString;

    const overrideBind: (thisArg: any, ...args: any[]) => Function = (
        function (oriBind) {
            return function overrideBind(this: any) {
                if (typeof this !== "function") {
                    throw new Error("必须是一个函数")
                }

                let fun: any;
                fun = oriBind.apply(this as any, arguments as any);
                if (hasOwnProperty.call(this, SymbolForBind)) {
                    fun[SymbolForBind] = this[SymbolForBind];
                } else {
                    fun[SymbolForBind] = this;
                }
                return fun;
            }

        }
    )(oriBind);

    (overrideBind as any)[SymbolForBind] = true;
    Function.prototype.bind = overrideBind;
    Function.prototype.toString = function (this:any) {
        if(hasOwnProperty.call(this, SymbolForBind)){
            return this[SymbolForBind].toString();
        }
        return oriToString.call(this);
    }
    isOverride = true;
    return undoBind;
}




index

import { CreateOptions, EnumEVMType } from "./types";
import { isObject } from "./util";

import Base from "./BaseEvm";
import ETarget from "./evm/ETarget";
import Events from "./evm/Events";
import CEvents from "./evm/CEvents";

import UIRender from "./ui/UIRender";

export const ETargetEVM = ETarget;
export const EventsEVM = Events;
export const CEventsEVM = CEvents
export const BaseEvm = Base;

export interface IUIRender {
    new (evm: Record<EnumEVMType, Base>): any;
}


export function createAllEVM(options: CreateOptions = {}): Record<EnumEVMType, Base> {
    const obj = Object.create(null);
    if (isObject(options.cEvents)) {
        obj["cEvents"] = new CEventsEVM(options.cEvents, options.cEvents?.et)
    }
    if (isObject(options.eTarget)) {
        obj["eTarget"] = new ETargetEVM(options.eTarget, options.eTarget?.et)
    }
    if (isObject(options.events)) {
        obj["events"] = new EventsEVM(options.events, options.events?.et)
    }
    return obj;
}

interface InstallOptions {
    evmOptions?: CreateOptions,
    render?: IUIRender
}

export default function install(options: InstallOptions = {}) {
    const evm = createAllEVM(options.evmOptions);

    function start(){
        console.log("evm started");
        evm?.cEvents?.watch();
        evm?.eTarget?.watch();
        evm?.events?.watch();
    }

    if (options.render) {
        return {
            render: new options.render(evm),
            evm,
            start
        }
    }
    return {
        render: new UIRender(evm),
        evm,
        start
    }
}

types

export interface ListenerOptions {
    once?: boolean;
    capture?: boolean;
    passive?: boolean;
    signal?: AbortSignal
}


export type TypeListenerOptions = boolean | ListenerOptions | undefined;


export interface EventsMapItem<O = any> {
    listener: WeakRef<Function>;
    options: O
}

export interface EventEmitterItem {
    listener: Function;
    once?: boolean;
}

export interface ISameOptions<O = any> {
    (options1: O, options2: O): boolean;
}

export interface ISameFunction {
    (fn1: any, fn2: any, ...args: any[]): boolean;
}

export interface BaseEvmOptions<S = any> {
    /**
     * 是否是相同选项
     */
    isSameOptions?: ISameOptions<S>;
    /**
     * 白名单
     */
    isInWhiteList?: EVMBaseEventListener<boolean>;
    /**
     * 最大的函数内容截取长度
     */
    maxContentLength?: number;
    /**
     * 是否重写bind函数
     */
    overrideBind?: boolean;
}


export interface EVMBaseEventListener<R = void, ET = EventType> {
    (target: Object, event: ET, listener: Function, options: TypeListenerOptions): R
}

export interface ListenerWrapper {
    listener: Function
}

export interface StatisticsOptions {
    containsContent?: boolean;
    forceGC?: boolean;
}

export interface EvmEventsMapOptions {
    isSameOptions?: ISameOptions;
    isSameFunction?(fun1: Function, fun2: Function): boolean;
}


export type EventType = string | Symbol | number;

type EVMOptions = BaseEvmOptions & {
    et?: Object
}

export interface CreateOptions {
    events?: EVMOptions,
    cEvents?: EVMOptions,
    eTarget?: EVMOptions
}

export enum EnumEVMType {
    events = "events",
    cEvents = "cEvents",
    eTarget = "eTarget"
}

图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消