@@ -164,7 +164,11 @@ export function shallowCopy(base: any, strict: StrictMode) {
164164
165165 const isPlain = isPlainObject ( base )
166166
167- if ( strict === true || ( strict === "class_only" && ! isPlain ) ) {
167+ if (
168+ strict === true ||
169+ strict === "full" ||
170+ ( strict === "class_only" && ! isPlain )
171+ ) {
168172 // Perform a strict copy
169173 const descriptors = Object . getOwnPropertyDescriptors ( base )
170174 delete descriptors [ DRAFT_STATE as any ]
@@ -192,6 +196,25 @@ export function shallowCopy(base: any, strict: StrictMode) {
192196 // perform a sloppy copy
193197 const proto = getPrototypeOf ( base )
194198 if ( proto !== null && isPlain ) {
199+ // v8 has a perf cliff at 1020 properties where it
200+ // switches from "fast properties" to "dictionary mode" at 1020 keys:
201+ // - https://github.com/v8/v8/blob/754e7ba956b06231c487e09178aab9baba1f46fe/src/objects/property-details.h#L242-L247
202+ // - https://github.com/v8/v8/blob/754e7ba956b06231c487e09178aab9baba1f46fe/test/mjsunit/dictionary-prototypes.js
203+ // At that size, object spread gets drastically slower,
204+ // and an `Object.keys()` loop becomes _faster_.
205+ // Immer currently expects that we also copy symbols. That would require either a `Reflect.ownKeys()`,
206+ // or `.keys()` + `.getOwnPropertySymbols()`.
207+ // For v10.x, we can keep object spread the default,
208+ // and offer an option to switch to just strings to enable better perf
209+ // with larger objects. For v11, we can flip those defaults.
210+ if ( strict === "strings_only" ) {
211+ const copy : Record < string | symbol , any > = { }
212+ Object . keys ( base ) . forEach ( key => {
213+ copy [ key ] = base [ key ]
214+ } )
215+ return copy
216+ }
217+
195218 return { ...base } // assumption: better inner class optimization than the assign below
196219 }
197220 const obj = Object . create ( proto )
0 commit comments