/* * proxy.ts * Copyright (C) 2024 veypi * 2024-10-23 17:20 * Distributed under terms of the GPL license. */ type voidFn = () => void const callbackCache: (voidFn | undefined)[] = [] const cacheUpdateList: number[] = [] // 界面更新响应频率40hz setInterval(() => { let list = new Set(cacheUpdateList.splice(0)) for (let l of list) { if (callbackCache[l]) { callbackCache[l]() } } }, 25) // 绑定事件删除 setInterval(() => { let exists = new Set() let check = (dom: Element) => { let ids = dom.getAttribute('vbind-fns')?.split(',') if (ids?.length) { for (let i of ids) { exists.add(i) } } for (let child of dom.children) { check(child) } } check(document.body) for (let i in callbackCache) { if (!exists.has(i)) { // 只删除元素,保留位置 delete callbackCache[i] // console.log('remove ' + i) } } }, 1000) function generateUniqueId() { const timestamp = performance.now().toString(36); const random = Math.random().toString(36).substring(2, 5); return `${timestamp}-${random}`; } function ForceUpdate() { for (let c of callbackCache) { if (c) { c() } } } var listen_tags: number[] = [] function Listen(callback: voidFn): number { let idx = callbackCache.length listen_tags.push(idx) callbackCache.push(callback) callback() listen_tags.pop() return idx } const isProxy = Symbol("isProxy") const DataID = Symbol("DataID") function Watch(data: T) { const did = generateUniqueId() let isArray = false if (Object.prototype.toString.call(data) === '[object Array]') { isArray = true } // console.log(`watch ${did} ${isArray}`, data) const listeners: { [key: string | symbol]: number[] } = {} const handler = { get(target: Object, key: string | symbol, receiver: any) { if (key === isProxy) { return true } if (key === DataID) { return did } const value = Reflect.get(target, key, receiver) if (typeof value === 'object' && value !== null) { if (value[isProxy]) { return value } else { let newValue = Watch(value) Reflect.set(target, key, newValue, receiver) return newValue } } let idx = -1 if (listen_tags.length > 0) { let lkey = key idx = listen_tags[listen_tags.length - 1] if (isArray) { lkey = '' } if (!listeners[lkey]) { listeners[lkey] = [idx] } else if (listeners[lkey].indexOf(idx) == -1) { listeners[lkey].push(idx) } } // console.log(`${did} get ${key.toString()}:${value} ${idx}`) return value; }, set(target: Object, key: string | symbol, newValue: any, receiver: any) { // console.log(`${did} set ${key.toString()} ${newValue}`) const result = Reflect.set(target, key, newValue, receiver); if (result) { let lkey = key if (isArray) { lkey = '' } if (listeners[lkey]) { for (let cb of listeners[lkey]) { cacheUpdateList.push(cb) } } } return result; }, deleteProperty(target: Object, key: string) { // console.log(`del ${key}`) const result = Reflect.deleteProperty(target, key); if (result) { } return result } }; let res = new Proxy(data, handler); // Symbol(Symbol.toStringTag) // res[Symbol.toStringTag] = 'Proxy' return res } export default { Watch, Listen, ForceUpdate, DataID, generateUniqueId }