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

⚡️ remove object spread operator for faster performance in big array iterator #2812

Merged
merged 2 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/sandbox/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,15 @@ export function rebindTarget2Fn(target: any, fn: any): any {
'toString',
);

Object.defineProperty(boundValue, 'toString', {
...originToStringDescriptor,
...(originToStringDescriptor?.get ? null : { value: () => fn.toString() }),
});
Object.defineProperty(
boundValue,
'toString',
Object.assign(
{},
originToStringDescriptor,
originToStringDescriptor?.get ? null : { value: () => fn.toString() },
),
);
}
}

Expand Down
38 changes: 24 additions & 14 deletions src/sandbox/proxySandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,27 @@ function uniq(array: Array<string | symbol>) {
}, Object.create(null));
}

const cachedGlobalsInBrowser = globalsInBrowser
.concat(process.env.NODE_ENV === 'test' ? ['mockNativeWindowFunction'] : [])
.reduce<Record<string, true>>((acc, key) => ({ ...acc, [key]: true }), Object.create(null));
/**
* transform array to object to enable faster element check with in operator
* @param array
*/
function array2TruthyObject(array: string[]): Record<string, true> {
return array.reduce(
(acc, key) => {
acc[key] = true;
return acc;
},
// Notes that babel will transpile spread operator to Object.assign({}, ...args), which will keep the prototype of Object in merged object,
// while this result used as Symbol.unscopables, it will make properties in Object.prototype always be escaped from proxy sandbox as unscopables check will look up prototype chain as well,
// such as hasOwnProperty, toString, valueOf, etc.
// so we should use Object.create(null) to create a pure object without prototype chain here.
Object.create(null),
);
}

const cachedGlobalsInBrowser = array2TruthyObject(
globalsInBrowser.concat(process.env.NODE_ENV === 'test' ? ['mockNativeWindowFunction'] : []),
);
function isNativeGlobalProp(prop: string): boolean {
return prop in cachedGlobalsInBrowser;
}
Expand Down Expand Up @@ -70,30 +88,22 @@ export const cachedGlobals = Array.from(
),
);

// transform cachedGlobals to object for faster element check
const cachedGlobalObjects = cachedGlobals.reduce((acc, globalProp) => ({ ...acc, [globalProp]: true }), {});
const cachedGlobalObjects = array2TruthyObject(cachedGlobals);

/*
Variables who are impossible to be overwritten need to be escaped from proxy sandbox for performance reasons.
But overwritten globals must not be escaped, otherwise they will be leaked to the global scope.
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/unscopables
*/
const unscopables = without(cachedGlobals, ...accessingSpiedGlobals.concat(overwrittenGlobals)).reduce(
(acc, key) => ({ ...acc, [key]: true }),
// Notes that babel will transpile spread operator to Object.assign({}, ...args), which will keep the prototype of Object in merged object,
// while this result used as Symbol.unscopables, it will make properties in Object.prototype always be escaped from proxy sandbox as unscopables check will look up prototype chain as well,
// such as hasOwnProperty, toString, valueOf, etc.
// so we should use Object.create(null) to create a pure object without prototype chain here.
Object.create(null),
);
const unscopables = array2TruthyObject(without(cachedGlobals, ...accessingSpiedGlobals.concat(overwrittenGlobals)));

const useNativeWindowForBindingsProps = new Map<PropertyKey, boolean>([
['fetch', true],
['mockDomAPIInBlackList', process.env.NODE_ENV === 'test'],
]);

function createFakeWindow(globalContext: Window, speedy: boolean) {
// map always has the fastest performance in has check scenario
// map always has the fastest performance in has checked scenario
// see https://jsperf.com/array-indexof-vs-set-has/23
const propertiesWithGetter = new Map<PropertyKey, boolean>();
const fakeWindow = {} as FakeWindow;
Expand Down
Loading