课程名称: 破解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"
}