GPT4o给了三种方法, 在后面给出. 第三种方法是改赋值=为调用函数, 意味着所有的赋值都要改为函数调用,不实用. 分析下方法一Proxy和方法二getter/setter.
Proxy的好处是不会改变原对象,而是建立了一个新的proxy对象, 对新对象的赋值会触发set函数. 问题有三个: 一是任何键值变化都会触发set,需要自己判断键值; 二是需要将原对象的名称指向新proxy对象, 才能实现无感. 三是无法通过instanceof判断pr一个实例是不是Proxy的实例, 只能自己在创建实例时添加一个自定义key如_isProxy来判断.(后面段落说明)

getter/setter是最早一代vue.js用的方法, 所有在app.data中的键都会用Object.defineProperty()的get/set重定义一遍, 做到自动更新UI. 好处是可以逐个key定义,不影响其他key, 问题是原key值就会消失,除非定义前先保存一下. 并且需要保存到另一个键下面(一般前面加个下划线_)而不能保存在本key下面, 否则会导致反复触发直到调用栈溢出Maximum call stack size exceeded.
这个话题在vue的官方文档中也有讨论: https://cn.vuejs.org/guide/extras/reactivity-in-depth, 里面也提到在vue3中,从getter/setter已经转向了proxy机制.
另外一个问题, 是如果键值是一个对象(数组也是),也就是下面有子键, 对子键键值的修改, 只会调用get而不是set!并且对Proxy和getter/setter机制都一样. 如:

proxyObject.k = {k1:1} // 调用set
proxyObject.k.k1 = 2 //调用get

怎么解决这个问题,GPT4o的回答是递归代理. 见后面一节.

方法1: 使用Proxy

在 JavaScript 中,如果希望在对象的某个键变化时自动调用一个函数,可以使用 Proxy 对象来实现。Proxy 允许你创建一个代理对象,以便对其属性的访问和修改进行拦截。

下面是一个使用 Proxy 的示例代码,当对象的 someKey 属性变化时,会自动调用函数 a

// 定义要监控的对象
const targetObject = {
    someKey: '初始值',
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 创建 Proxy 代理对象
const proxyObject = new Proxy(targetObject, {
    set(target, property, value) {
        // 当设置 someKey 时,调用函数 a
        if (property === 'someKey') {
            a(value);
        }
        // 修改属性的值
        target[property] = value;
        // 返回 true 表示设置成功
        return true;
    }
});

// 测试:修改 someKey
proxyObject.someKey = '新的值'; // 这将输出: someKey 的新值是: 新的值

// 可以继续修改其他属性也不会触发 a()
proxyObject.anotherKey = '另一个值';
console.log(proxyObject.anotherKey); // 输出: 另一个值

说明:

  • 在上面的示例中,创建了一个对象 targetObject 和一个函数 a
  • 使用 Proxy 创建了代理对象 proxyObject,并在其 set 拦截器中定义了逻辑。当 someKey 被修改时,会自动调用函数 a(newValue)
  • 修改其他属性不会触发这个函数,只会在指定的键发生变化时执行。

通过这种方式,你可以灵活地监控对象属性的变化,并根据需要触发相应的逻辑。

方法2 使用getter/setter

你可以使用 JavaScript 的 Object.defineProperty() 方法来定义 getter 和 setter,使得每次修改对象属性时都能执行相应的函数。

const targetObject = {
    _someKey: '初始值',  // 私有变量
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 使用 Object.defineProperty 定义 getter 和 setter
Object.defineProperty(targetObject, 'someKey', {
    get() {
        return this._someKey;
    },
    set(value) {
        this._someKey = value;
        a(value);  // 调用函数 a
    },
});

// 测试:修改 someKey
targetObject.someKey = '新的值'; // 这将输出: someKey 的新值是: 新的值

// 读取 someKey
console.log(targetObject.someKey); // 输出: 新的值

// 可以继续修改其他属性也不会触发 a()
targetObject.anotherKey = '另一个值';
console.log(targetObject.anotherKey); // 输出: undefined (因为没有定义)

方法3:使用自定义方法

另一种简单的方法是创建一个用于更新对象属性的方法,以便所有更改都通过此方法发生,这样就可以在里面调用相关逻辑。

const targetObject = {
    someKey: '初始值',
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 自定义方法用于更新键并执行相应逻辑
function setSomeKey(value) {
    targetObject.someKey = value;   // 更新属性
    a(value);                       // 调用函数 a
}

// 测试:修改 someKey
setSomeKey('新的值'); // 这将输出: someKey 的新值是: 新的值

// 读取 someKey
console.log(targetObject.someKey); // 输出: 新的值

// 修改其他属性的方法不变,不会触发函数 a()
targetObject.anotherKey = '另一个值';
console.log(targetObject.anotherKey); // 输出: 另一个值

如何知道实例是Proxy的实例

在 JavaScript 中,可以使用 Proxy 对象来创建代理,从而控制对某个对象的基本操作,如属性查找、赋值、枚举、函数调用等。如果想要判断一个对象是否是一个 Proxy 实例,可以通过以下方法:

  1. 利用 WeakMap: 在将对象包装为 Proxy 时,可以使用 WeakMap 来存储原始对象和其代理之间的关系。通过这种方式,你可以检查某个代理是否存在于这个 WeakMap 中。

     const originalObject = {};
     const proxyMap = new WeakMap();
    
     const proxy = new Proxy(originalObject, {
         set(target, prop, value) {
             return Reflect.set(target, prop, value);
         }
     });
    
     // 存入 WeakMap
     proxyMap.set(proxy, originalObject);
    
     function isProxy(obj) {
         return proxyMap.has(obj);
     }
    
     console.log(isProxy(proxy)); // true
     console.log(isProxy(originalObject)); // false
  2. 标记法: 你可以在创建 Proxy 时在其内部设置一个标记属性,以便后续判断。

     const originalObject = {};
    
     const proxy = new Proxy(originalObject, {
         get(target, prop) {
             if (prop === '_isProxy') return true;
             return Reflect.get(target, prop);
         }
     });
    
     function isProxy(obj) {
         return obj && obj._isProxy === true;
     }
    
     console.log(isProxy(proxy)); // true
     console.log(isProxy(originalObject)); // false
  3. 使用 instanceof 关键字: 不幸的是,直接通过 instanceof 来判断是否为 Proxy 是不可能的,因为我们无法直接访问 Proxy 的构造函数。

总的来说,上述方法是较常见的判断一个对象是否为 Proxy 的方案。选择适合自己需求的方法来实现即可。

递归代理

要解决object下的key是对象或者数组, 其子键或者子元素被修改时调用的是get而不是set的问题, GPTo答复是递归代理. 下面给出的是对象的递归代理, 实际还需要考虑数组.
递归代理是对了,但是GPT4o在第一次给出的代码中并没有实现递归代理,指出后,在第二次实现了递归代理, 但设置深层子元素的时候调用了2次set(重复代理了),指出问题后, GPT4o再修改了6次, 也没能回答正确. 感觉是黔驴技穷啦. 这儿贴出其中一次的代码.

function createReactiveObject(obj) {
    return new Proxy(obj, {
        set(target, property, value) {
            // 检查设置的值是否为对象
            if (typeof value === 'object' && value !== null) {
                // 用 Proxy 处理该对象
                value = createReactiveObject(value);
            }
            // 设置实际值
            target[property] = value;
            console.log(`属性 ${property} 被设置为: ${value}`); // 输出变更信息
            return true; // 返回成功标识
        },
        get(target, property) {
            const value = target[property];
            // 仅当获取的是对象时才返回新的代理,避免递归代理产生重复
            if (typeof value === 'object' && value !== null) {
                return createReactiveObject(value); // 利用递归返回新的代理而不是多次创建同一对象的代理
            }
            return value;
        }
    });
}

// 创建动态对象并使用代理函数包裹它
const dynamicObject = createReactiveObject({
    k1: { k2: { k3: {} } } // 初始化嵌套结构 
});

// 测试:设置嵌套属性
dynamicObject.k1.k2.k3.k4 = 20; // 应只输出一次: 属性 k4 被设置为: 20

// 打印查看当前状态
console.log(dynamicObject); 

标签: none 阅读量: 332

添加新评论