render: RenderFunction | null
effects: ReactiveEffect[] | null
provides: Data
+ accessCache: Data
components: Record<string, Component>
directives: Record<string, Directive>
setupContext: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
+ accessCache: null!,
// setup context properties
renderContext: EMPTY_OBJ,
}
}
}
-
+ // 0. create render proxy property access cache
+ instance.accessCache = Object.create(null)
// 1. create render proxy
instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers)
// 2. create props proxy
$options: 'type'
}
+const enum AccessTypes {
+ DATA,
+ CONTEXT,
+ PROPS
+}
+
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get(target: ComponentInternalInstance, key: string) {
- const { renderContext, data, props, propsProxy } = target
- if (data !== EMPTY_OBJ && hasOwn(data, key)) {
+ const { renderContext, data, props, propsProxy, accessCache } = target
+ // 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]
} else if (hasOwn(renderContext, key)) {
+ accessCache[key] = AccessTypes.CONTEXT
return renderContext[key]
} else if (hasOwn(props, key)) {
+ accessCache[key] = AccessTypes.PROPS
// return the value from propsProxy for ref unwrapping and readonly
return propsProxy![key]
} else if (key === '$el') {