|
|
|
/*
|
|
|
|
* proxy.ts
|
|
|
|
* Copyright (C) 2024 veypi <i@veypi.com>
|
|
|
|
* 2024-10-23 17:20
|
|
|
|
* Distributed under terms of the GPL license.
|
|
|
|
*/
|
|
|
|
|
|
|
|
type voidFn = () => void
|
|
|
|
const callbackCache: ([boolean, voidFn])[] = []
|
|
|
|
const cacheUpdateList: number[] = []
|
|
|
|
// 界面更新响应频率40hz
|
|
|
|
setInterval(() => {
|
|
|
|
let list = new Set(cacheUpdateList.splice(0))
|
|
|
|
for (let l of list) {
|
|
|
|
if (callbackCache[l][0]) {
|
|
|
|
callbackCache[l][1]()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 25)
|
|
|
|
|
|
|
|
// TODO: 已删除的元素有可能会重新添加回来,
|
|
|
|
// // 绑定事件删除
|
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
// if (!callbackCache[Number(i)][0]) {
|
|
|
|
// callbackCache[Number(i)][0] = true
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// for (let child of dom.children) {
|
|
|
|
// check(child)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// check(document.body)
|
|
|
|
// for (let i in callbackCache) {
|
|
|
|
// if (!exists.has(i)) {
|
|
|
|
// // 只使能在页面上的更新
|
|
|
|
// callbackCache[i][0] = false
|
|
|
|
// // console.log('remove ' + i)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }, 1000)
|
|
|
|
|
|
|
|
function GenUniqueID() {
|
|
|
|
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[0]) {
|
|
|
|
c[1]()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var listen_tags: number[] = []
|
|
|
|
function Listen(callback: voidFn): number {
|
|
|
|
let idx = callbackCache.length
|
|
|
|
listen_tags.push(idx)
|
|
|
|
callbackCache.push([true, callback])
|
|
|
|
callback()
|
|
|
|
listen_tags.pop()
|
|
|
|
return idx
|
|
|
|
}
|
|
|
|
|
|
|
|
function DomListen(dom: HTMLElement, fn: () => void) {
|
|
|
|
let fns = dom.getAttribute('vbind-fns')
|
|
|
|
let fnid = Listen(fn)
|
|
|
|
if (fns) {
|
|
|
|
fns += ',' + fnid
|
|
|
|
} else {
|
|
|
|
fns = String(fnid)
|
|
|
|
}
|
|
|
|
dom.setAttribute('vbind-fns', fns)
|
|
|
|
}
|
|
|
|
|
|
|
|
const isProxy = Symbol("isProxy")
|
|
|
|
const DataID = Symbol("DataID")
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
window.$u = (id: number) => {
|
|
|
|
callbackCache[id][1]()
|
|
|
|
}
|
|
|
|
|
|
|
|
function Ref<T>(data: T): { value: T } {
|
|
|
|
return Watch({ value: data })
|
|
|
|
}
|
|
|
|
function Watch<T extends Object>(data: T, debug = false) {
|
|
|
|
const did = GenUniqueID()
|
|
|
|
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, debug)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (debug) {
|
|
|
|
console.log(`${did} get ${key.toString()}:${value} ${idx}`)
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
set(target: Object, key: string | symbol, newValue: any, receiver: any) {
|
|
|
|
const result = Reflect.set(target, key, newValue, receiver);
|
|
|
|
if (debug) {
|
|
|
|
console.log(`${did} set ${key.toString()} ${newValue}`, listeners, result)
|
|
|
|
}
|
|
|
|
if (result && listen_tags.length === 0) {
|
|
|
|
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<T>(data, handler);
|
|
|
|
// Symbol(Symbol.toStringTag)
|
|
|
|
// res[Symbol.toStringTag] = 'Proxy'
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
export default { Watch, Listen, Ref, DomListen, ForceUpdate, DataID, GenUniqueID, }
|