perf(core): cache property access types on renderProxy

This commit is contained in:
Evan You 2019-10-16 22:13:52 -04:00
parent cdee65aa1b
commit 4771319a15
2 changed files with 30 additions and 3 deletions

View File

@ -82,6 +82,7 @@ export interface ComponentInternalInstance {
render: RenderFunction | null render: RenderFunction | null
effects: ReactiveEffect[] | null effects: ReactiveEffect[] | null
provides: Data provides: Data
accessCache: Data
components: Record<string, Component> components: Record<string, Component>
directives: Record<string, Directive> directives: Record<string, Directive>
@ -146,6 +147,7 @@ export function createComponentInstance(
setupContext: null, setupContext: null,
effects: null, effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides), provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!,
// setup context properties // setup context properties
renderContext: EMPTY_OBJ, renderContext: EMPTY_OBJ,
@ -254,7 +256,8 @@ export function setupStatefulComponent(
} }
} }
} }
// 0. create render proxy property access cache
instance.accessCache = Object.create(null)
// 1. create render proxy // 1. create render proxy
instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers) instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers)
// 2. create props proxy // 2. create props proxy

View File

@ -48,14 +48,38 @@ const publicPropertiesMap = {
$options: 'type' $options: 'type'
} }
const enum AccessTypes {
DATA,
CONTEXT,
PROPS
}
export const PublicInstanceProxyHandlers: ProxyHandler<any> = { export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get(target: ComponentInternalInstance, key: string) { get(target: ComponentInternalInstance, key: string) {
const { renderContext, data, props, propsProxy } = target const { renderContext, data, props, propsProxy, accessCache } = target
if (data !== EMPTY_OBJ && hasOwn(data, key)) { // This getter gets called for every property access on the render context
// during render and is a major hotspot. The most expensive part of this
// is the multiple hasOwn() calls. It's much faster to do a simple property
// access on a plain object, so we use an accessCache object (with null
// prototype) to memoize what access type a key corresponds to.
const n = accessCache[key]
if (n !== undefined) {
switch (n) {
case AccessTypes.DATA:
return data[key]
case AccessTypes.CONTEXT:
return renderContext[key]
case AccessTypes.PROPS:
return propsProxy![key]
}
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache[key] = AccessTypes.DATA
return data[key] return data[key]
} else if (hasOwn(renderContext, key)) { } else if (hasOwn(renderContext, key)) {
accessCache[key] = AccessTypes.CONTEXT
return renderContext[key] return renderContext[key]
} else if (hasOwn(props, key)) { } else if (hasOwn(props, key)) {
accessCache[key] = AccessTypes.PROPS
// return the value from propsProxy for ref unwrapping and readonly // return the value from propsProxy for ref unwrapping and readonly
return propsProxy![key] return propsProxy![key]
} else if (key === '$el') { } else if (key === '$el') {