diff --git a/src/sandbox/common.ts b/src/sandbox/common.ts index e05700c..909198c 100644 --- a/src/sandbox/common.ts +++ b/src/sandbox/common.ts @@ -27,7 +27,12 @@ export function getTargetValue(target: any, value: any): any { const boundValue = value.bind(target); // some callable function has custom fields, we need to copy the enumerable props to boundValue. such as moment function. - Object.keys(value).forEach(key => (boundValue[key] = value[key])); + // use for..in rather than Object.keys.forEach for performance reason + // eslint-disable-next-line guard-for-in,no-restricted-syntax + for (const key in value) { + boundValue[key] = value[key]; + } + // copy prototype, for performance reason, we use in operator to check rather than hasOwnProperty if ('prototype' in value) boundValue.prototype = value.prototype; Object.defineProperty(value, boundValueSymbol, { enumerable: false, value: boundValue }); @@ -37,7 +42,7 @@ export function getTargetValue(target: any, value: any): any { return value; } -const getterInvocationResultMap = new Map(); +const getterInvocationResultMap = new WeakMap(); export function getProxyPropertyValue(getter: CallableFunction) { const getterResult = getterInvocationResultMap.get(getter); diff --git a/src/sandbox/patchers/dynamicAppend.ts b/src/sandbox/patchers/dynamicAppend.ts index c601359..7e28e63 100644 --- a/src/sandbox/patchers/dynamicAppend.ts +++ b/src/sandbox/patchers/dynamicAppend.ts @@ -33,7 +33,7 @@ const SCRIPT_TAG_NAME = 'SCRIPT'; const LINK_TAG_NAME = 'LINK'; const STYLE_TAG_NAME = 'STYLE'; -const proxyContainerInfoMapper = new Map>(); +const proxyContainerInfoMapper = new WeakMap>(); function isHijackingTag(tagName?: string) { return ( diff --git a/src/utils.ts b/src/utils.ts index 0e7867e..91603bc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,16 +22,22 @@ export function nextTick(cb: () => void): void { Promise.resolve().then(cb); } -export function isConstructable(fn: () => void | FunctionConstructor) { +const constructableMap = new WeakMap(); +export function isConstructable(fn: () => any | FunctionConstructor) { + if (constructableMap.has(fn)) { + return constructableMap.get(fn); + } + const constructableFunctionRegex = /^function\b\s[A-Z].*/; const classRegex = /^class\b/; // 有 prototype 并且 prototype 上有定义一系列非 constructor 属性,则可以认为是一个构造函数 - return ( + const constructable = (fn.prototype && fn.prototype.constructor === fn && Object.getOwnPropertyNames(fn.prototype).length > 1) || constructableFunctionRegex.test(fn.toString()) || - classRegex.test(fn.toString()) - ); + classRegex.test(fn.toString()); + constructableMap.set(fn, constructable); + return constructable; } /** @@ -45,12 +51,18 @@ export const isCallable = naughtySafari ? (fn: any) => typeof fn === 'function' && typeof fn !== 'undefined' : (fn: any) => typeof fn === 'function'; +const boundedMap = new WeakMap(); export function isBoundedFunction(fn: CallableFunction) { + if (boundedMap.has(fn)) { + return boundedMap.get(fn); + } /* indexOf is faster than startsWith see https://jsperf.com/string-startswith/72 */ - return fn.name.indexOf('bound ') === 0 && !fn.hasOwnProperty('prototype'); + const bounded = fn.name.indexOf('bound ') === 0 && !fn.hasOwnProperty('prototype'); + boundedMap.set(fn, bounded); + return bounded; } /**