]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): fix iOS 12 JSON.stringify error on reactive objects
authorEvan You <yyx990803@gmail.com>
Mon, 24 Aug 2020 19:26:53 +0000 (15:26 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 24 Aug 2020 19:34:04 +0000 (15:34 -0400)
- Use WeakMap for raw -> reactive/readonly storage. This is slightly
  more expensive than using a field on the taget object but avoids
  polluting the original.

- also fix Collection.forEach callback value

fix #1916

packages/reactivity/__tests__/readonly.spec.ts
packages/reactivity/src/baseHandlers.ts
packages/reactivity/src/collectionHandlers.ts
packages/reactivity/src/reactive.ts

index 3c22ce2905ec80d7ede9a4ff2588a41d0984c431..ff73e43bcf79d500ff465b59ca37cbe0d4738953 100644 (file)
@@ -228,7 +228,7 @@ describe('reactivity/readonly', () => {
         test('should retrieve readonly values on iteration', () => {
           const key1 = {}
           const key2 = {}
-          const original = new Collection([[key1, {}], [key2, {}]])
+          const original = new Map([[key1, {}], [key2, {}]])
           const wrapped: any = readonly(original)
           expect(wrapped.size).toBe(2)
           for (const [key, value] of wrapped) {
@@ -246,7 +246,7 @@ describe('reactivity/readonly', () => {
         test('should retrieve reactive + readonly values on iteration', () => {
           const key1 = {}
           const key2 = {}
-          const original = reactive(new Collection([[key1, {}], [key2, {}]]))
+          const original = reactive(new Map([[key1, {}], [key2, {}]]))
           const wrapped: any = readonly(original)
           expect(wrapped.size).toBe(2)
           for (const [key, value] of wrapped) {
index 8d8112e926ad4410be5cbf81009e40a035c5666e..ef944a968a67342ea587cb18435f2d0429489ff7 100644 (file)
@@ -1,4 +1,12 @@
-import { reactive, readonly, toRaw, ReactiveFlags, Target } from './reactive'
+import {
+  reactive,
+  readonly,
+  toRaw,
+  ReactiveFlags,
+  Target,
+  readonlyMap,
+  reactiveMap
+} from './reactive'
 import { TrackOpTypes, TriggerOpTypes } from './operations'
 import { track, trigger, ITERATE_KEY } from './effect'
 import {
@@ -48,10 +56,7 @@ function createGetter(isReadonly = false, shallow = false) {
       return isReadonly
     } else if (
       key === ReactiveFlags.RAW &&
-      receiver ===
-        (isReadonly
-          ? target[ReactiveFlags.READONLY]
-          : target[ReactiveFlags.REACTIVE])
+      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
     ) {
       return target
     }
index 126c94460d1e23b344c540dda4f3d904327144e0..4f4b97261405c52d8e2358c564008d956cafc51d 100644 (file)
@@ -145,17 +145,17 @@ function createForEach(isReadonly: boolean, isShallow: boolean) {
     callback: Function,
     thisArg?: unknown
   ) {
-    const observed = this
-    const target = toRaw(observed)
+    const observed = this as any
+    const target = observed[ReactiveFlags.RAW]
+    const rawTarget = toRaw(target)
     const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
-    !isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
-    // important: create sure the callback is
-    // 1. invoked with the reactive map as `this` and 3rd arg
-    // 2. the value received should be a corresponding reactive/readonly.
-    function wrappedCallback(value: unknown, key: unknown) {
+    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
+    return target.forEach((value: unknown, key: unknown) => {
+      // important: make sure the callback is
+      // 1. invoked with the reactive map as `this` and 3rd arg
+      // 2. the value received should be a corresponding reactive/readonly.
       return callback.call(thisArg, wrap(value), wrap(key), observed)
-    }
-    return getProto(target).forEach.call(target, wrappedCallback)
+    })
   }
 }
 
index a6602db2c5c5e74feef3dc2bac7443eb998556eb..3f6da8364a0ee2c32814e58c1b48f47b8469848c 100644 (file)
@@ -1,4 +1,4 @@
-import { isObject, toRawType, def, hasOwn } from '@vue/shared'
+import { isObject, toRawType, def } from '@vue/shared'
 import {
   mutableHandlers,
   readonlyHandlers,
@@ -16,9 +16,7 @@ export const enum ReactiveFlags {
   SKIP = '__v_skip',
   IS_REACTIVE = '__v_isReactive',
   IS_READONLY = '__v_isReadonly',
-  RAW = '__v_raw',
-  REACTIVE = '__v_reactive',
-  READONLY = '__v_readonly'
+  RAW = '__v_raw'
 }
 
 export interface Target {
@@ -26,10 +24,11 @@ export interface Target {
   [ReactiveFlags.IS_REACTIVE]?: boolean
   [ReactiveFlags.IS_READONLY]?: boolean
   [ReactiveFlags.RAW]?: any
-  [ReactiveFlags.REACTIVE]?: any
-  [ReactiveFlags.READONLY]?: any
 }
 
+export const reactiveMap = new WeakMap<Target, any>()
+export const readonlyMap = new WeakMap<Target, any>()
+
 const enum TargetType {
   INVALID = 0,
   COMMON = 1,
@@ -155,23 +154,22 @@ function createReactiveObject(
     return target
   }
   // target already has corresponding Proxy
-  const reactiveFlag = isReadonly
-    ? ReactiveFlags.READONLY
-    : ReactiveFlags.REACTIVE
-  if (hasOwn(target, reactiveFlag)) {
-    return target[reactiveFlag]
+  const proxyMap = isReadonly ? readonlyMap : reactiveMap
+  const existingProxy = proxyMap.get(target)
+  if (existingProxy) {
+    return existingProxy
   }
   // only a whitelist of value types can be observed.
   const targetType = getTargetType(target)
   if (targetType === TargetType.INVALID) {
     return target
   }
-  const observed = new Proxy(
+  const proxy = new Proxy(
     target,
     targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
   )
-  def(target, reactiveFlag, observed)
-  return observed
+  proxyMap.set(target, proxy)
+  return proxy
 }
 
 export function isReactive(value: unknown): boolean {