Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proxy class or object #56

Open
wangning0 opened this issue May 22, 2020 · 0 comments
Open

proxy class or object #56

wangning0 opened this issue May 22, 2020 · 0 comments

Comments

@wangning0
Copy link
Owner

const callerMap = {};

function getCaller(error) {
    if (error && error.stack) {
        const lines = error.stack.split('\n');
        if (lines.length > 2) {
            let match = lines[2].match(/at ([a-zA-Z\-_$.]+) (.*)/);
            if (match) {
                return {
                    name: match[1].replace(/^Proxy\./, ''),
                    file: match[2],
                };
            } else {
                match = lines[2].match(/at (.*)/);
                if (match) {
                    return {
                        name: 'unknown',
                        file: match[1],
                    };
                }
            }
        }
    }
    return {
        name: 'unknown',
        file: '',
    };
}

function getFunctionName(fn, context) {
    let contextName = '';
    if (typeof context === 'function') {
        contextName = `{context.name}.`;
    } else if (context && context.constructor && context.constructor.name !== 'Object') {
        contextName = `${context.constructor.name}.`;
    }
    return `${contextName}${fn.name}`;
}

function trackFunctionCall(options = {}) {
    return function(target, thisArg, argumentsList) {
        const { trackTime, trackCaller, trackCount, stdout, filter } = options;
        const error = trackCaller && new Error();
        const caller = getCaller(error);
        const name = getFunctionName(target, thisArg);
        if (trackCount) {
            if (!callerMap[name]) {
                callerMap[name] = 1;
            } else {
                callerMap[name]++;
            }
        }
        let start, end;
        if (trackTime) {
            start = Date.now();
        }
        const retVal = target.apply(thisArg, argumentsList);
        if (trackTime) {
            end = Date.now();
        }
        let output = `${name} was called`;
        if (trackCaller) {
            output += ` by ${caller.name}`;
        }
        if (trackCount) {
            output += ` for the ${callerMap[name]} time`;
        }
        if (trackTime) {
            output += ` and took ${end-start} mils.`;
        }
        let canReport = true;
        if (filter) {
            canReport = filter({
                type: 'function',
                name,
                caller,
                count: callerMap[name],
                time: end - start,
            });
        }
        if (canReport) {
            if (stdout) {
                stdout(output);
            } else {
                console.log(output);
            }
        }
        return retVal;
    };
}

function trackPropertySet(options = {}) {
    return function set(target, prop, value, receiver) {
        const { trackCaller, trackCount, stdout, filter } = options;
        const error = trackCaller && new Error();
        const caller = getCaller(error);
        const contextName = target.constructor.name === 'Object' ? '' : `${target.constructor.name}.`;
        const name = `${contextName}${prop}`;
        const hashKey = `set_${name}`;
        if (trackCount) {
            if (!callerMap[hashKey]) {
                callerMap[hashKey] = 1;
            } else {
                callerMap[hashKey]++;
            }
        }
        let output = `${name} is being set`;
        if (trackCaller) {
            output += ` by ${caller.name}`;
        }
        if (trackCount) {
            output += ` for the ${callerMap[hashKey]} time`;
        }
        let canReport = true;
        if (filter) {
            canReport = filter({
                type: 'get',
                prop,
                name,
                caller,
                count: callerMap[hashKey],
                value,
            });
        }
        if (canReport) {
            if (stdout) {
                stdout(output);
            } else {
                console.log(output);
            }
        }
        return Reflect.set(target, prop, value, receiver);
    };
}

function trackPropertyGet(options = {}) {
    return function get(target, prop, receiver) {
        const { trackCaller, trackCount, stdout, filter } = options;
        if (typeof target[prop] === 'function' || prop === 'prototype') {
            return target[prop];
        }
        const error = trackCaller && new Error();
        const caller = getCaller(error);
        const contextName = target.constructor.name === 'Object' ? '' : `${target.constructor.name}.`;
        const name = `${contextName}${prop}`;
        const hashKey = `get_${name}`;

        if (trackCount) {
            if (!callerMap[hashKey]) {
                callerMap[hashKey] = 1;
            } else {
                callerMap[hashKey]++;
            }
        }
        let output = `${name} is being get`;
        if (trackCaller) {
            output += ` by ${caller.name}`;
        }
        if (trackCount) {
            output += ` for the ${callerMap[hashKey]} time`;
        }
        let canReport = true;
        if (filter) {
            canReport = filter({
                type: 'get',
                prop,
                name,
                caller,
                count: callerMap[hashKey],
            });
        }
        if (canReport) {
            if (stdout) {
                stdout(output);
            } else {
                console.log(output);
            }
        }
        return target[prop];
    };
}

function proxyFunctions(trackedEntity, options) {
    if (typeof trackedEntity === 'function') return;
    Object.getOwnPropertyNames(trackedEntity).forEach((name) => {
        if (typeof trackedEntity[name] === 'function') {
            trackedEntity[name] = new Proxy(trackedEntity[name], {
                apply: trackFunctionCall(options),
            });
        }
    });
}

function trackObject(obj, options = {}) {
    const { trackFunctions, trackProps } = options;

    let resultObj = obj;
    if (trackFunctions) {
        proxyFunctions(resultObj, options);
    }
    if (trackProps) {
        resultObj = new Proxy(resultObj, {
            get: trackPropertyGet(options),
            set: trackPropertySet(options),
        });
    }
    return resultObj;
}

const defaultOptions = {
    trackFunctions: true,
    trackProps: true,
    trackTime: true,
    trackCaller: true,
    trackCount: true,
    filter: null,
};


function trackClass(cls, options = {}) {
    cls.prototype = trackObject(cls.prototype, options);
    cls.prototype.constructor = cls;

    return new Proxy(cls, {
        construct(target, args) {
            const obj = new target(...args);
            return new Proxy(obj, {
                get: trackPropertyGet(options),
                set: trackPropertySet(options),
            });
        },
        apply: trackFunctionCall(options),
    });
}

function proxyTrack(entity, options = defaultOptions) {
    if (typeof entity === 'function') return trackClass(entity, options);
    return trackObject(entity, options);
}

module.exports = proxyTrack;


class A {
  constructor(name) {
    this.name = name;
  }

  getMyName() {
    return this.name;
  }

  setMyName(newName) {
    this.name = newName;
  }
}

const ProxyedA = proxyTrack(A);

const aInstance = new ProxyedA('wangning.frontend');

const name = aInstance.getMyName();

console.log(name);

aInstance.setMyName('wangning');

const newName = aInstance.getMyName();

console.log(newName);


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant