@@ -35,22 +35,26 @@ export function isDraftable(value: any): boolean {
3535}
3636
3737const objectCtorString = Object . prototype . constructor . toString ( )
38+ const cachedCtorStrings = new WeakMap ( )
3839/*#__PURE__*/
3940export function isPlainObject ( value : any ) : boolean {
4041 if ( ! value || typeof value !== "object" ) return false
41- const proto = getPrototypeOf ( value )
42- if ( proto === null ) {
43- return true
44- }
42+ const proto = Object . getPrototypeOf ( value )
43+ if ( proto === null || proto === Object . prototype ) return true
44+
4545 const Ctor =
4646 Object . hasOwnProperty . call ( proto , "constructor" ) && proto . constructor
47-
4847 if ( Ctor === Object ) return true
4948
50- return (
51- typeof Ctor == "function" &&
52- Function . toString . call ( Ctor ) === objectCtorString
53- )
49+ if ( typeof Ctor !== "function" ) return false
50+
51+ let ctorString = cachedCtorStrings . get ( Ctor )
52+ if ( ctorString === undefined ) {
53+ ctorString = Function . toString . call ( Ctor )
54+ cachedCtorStrings . set ( Ctor , ctorString )
55+ }
56+
57+ return ctorString === objectCtorString
5458}
5559
5660/** Get the underlying object that is represented by the given draft */
@@ -64,15 +68,23 @@ export function original(value: Drafted<any>): any {
6468/**
6569 * Each iterates a map, set or array.
6670 * Or, if any other kind of object, all of its own properties.
67- * Regardless whether they are enumerable or symbols
71+ *
72+ * @param obj The object to iterate over
73+ * @param iter The iterator function
74+ * @param strict When true (default), includes symbols and non-enumerable properties.
75+ * When false, uses looseiteration over only enumerable string properties.
6876 */
6977export function each < T extends Objectish > (
7078 obj : T ,
71- iter : ( key : string | number , value : any , source : T ) => void
79+ iter : ( key : string | number , value : any , source : T ) => void ,
80+ strict ?: boolean
7281) : void
73- export function each ( obj : any , iter : any ) {
82+ export function each ( obj : any , iter : any , strict : boolean = true ) {
7483 if ( getArchtype ( obj ) === ArchType . Object ) {
75- Reflect . ownKeys ( obj ) . forEach ( key => {
84+ // If strict, we do a full iteration including symbols and non-enumerable properties
85+ // Otherwise, we only iterate enumerable string properties for performance
86+ const keys = strict ? Reflect . ownKeys ( obj ) : Object . keys ( obj )
87+ keys . forEach ( key => {
7688 iter ( key , obj [ key ] , obj )
7789 } )
7890 } else {
@@ -198,12 +210,12 @@ export function freeze<T>(obj: T, deep?: boolean): T
198210export function freeze < T > ( obj : any , deep : boolean = false ) : T {
199211 if ( isFrozen ( obj ) || isDraft ( obj ) || ! isDraftable ( obj ) ) return obj
200212 if ( getArchtype ( obj ) > 1 /* Map or Set */ ) {
201- Object . defineProperties ( obj , {
202- set : { value : dontMutateFrozenCollections as any } ,
203- add : { value : dontMutateFrozenCollections as any } ,
204- clear : { value : dontMutateFrozenCollections as any } ,
205- delete : { value : dontMutateFrozenCollections as any }
206- } )
213+ Object . defineProperties ( obj , {
214+ set : dontMutateMethodOverride ,
215+ add : dontMutateMethodOverride ,
216+ clear : dontMutateMethodOverride ,
217+ delete : dontMutateMethodOverride
218+ } )
207219 }
208220 Object . freeze ( obj )
209221 if ( deep )
@@ -217,6 +229,12 @@ function dontMutateFrozenCollections() {
217229 die ( 2 )
218230}
219231
232+ const dontMutateMethodOverride = {
233+ value : dontMutateFrozenCollections
234+ }
235+
220236export function isFrozen ( obj : any ) : boolean {
237+ // Fast path: primitives and null/undefined are always "frozen"
238+ if ( obj === null || typeof obj !== "object" ) return true
221239 return Object . isFrozen ( obj )
222240}
0 commit comments