【问题标题】:ES6 Proxy: set() trap not triggering, when setting inside target object's methodES6 代理:set() 陷阱未触发,在目标对象的方法内设置时
【发布时间】:2018-09-14 04:45:23
【问题描述】:

例子:

let foo = {bar: 'baz', method() { this.bar = 'baz2' }}
let fooProxy = new Proxy(foo, {set(target, key, val) { console.log('set trap triggered!') }})

fooProxy.bar = 'any value' // as expected: set trap triggered!
foo.method() // trap not triggered

为什么会这样?如何从目标对象内部强制触发陷阱?

编辑,主要是为了向@Bergi解释这个:

我的主要目标是拦截对 foo 对象的任何更改,因此我可以设置属性,例如foo.changed 为 true。另外,我想拦截对具有数组/对象类型的 foo 属性的更改。你知道,如果我设置 foo 的属性,一切都很好,但是当我例如推送到一个数组,然后代理无法拦截它。所以我也需要将数组/对象属性转换为代理(我称它们为 ArrayProxy 和 ObjectProxy)。

这是我的代码(打字稿):

// Category.ts
class Category extends Model {
    title: string = ''
    products: Product[] = []
}

// Model.ts
abstract class Model extends BaseModel {
    constructor() {
        return new Proxy(this, {
            set (target, key, val) { 

                if (Array.isArray(val) {  
                    target[key] = new ArrayProxy(val) // I WANT all array properties to be ArrayProxy but the problem (see below) not let me do that
                }     
            } 
        })
    }
}

// BaseModel.ts
abstract class BaseModel {
    constructor(attributes) {
        this.setAttributes(attributes)
    }

    setAttributes(attributes) {
        Object.keys(attributes).forEach((key) => {
            this[key] = attributes[key] // THE PROBLEM
        })
    }
}

我已经删除了代码,这无关紧要(例如对象属性和 ObjectProxy 的类似情况)。

如果有更优雅的方式来做我所做的事情,我将非常感激。

【问题讨论】:

  • 你当然需要使用fooProxy.method()。你也不会指望fooProxy.method.call(something)(设置something.bar)来触发代理?
  • 好吧,普通对象的构造函数对代理一无所知(也不应该知道)。您尝试使用代理解决的actual problem 是什么,您的真实代码是什么?
  • @Phil:你不应该编辑其他人的代码。我不知道该编辑是如何获得批准的。添加分号和使用const 会将您的个人风格强加给其他人。
  • @NurbolAlpysbayev 好吧,您只能在将代理包裹在对象周围之后才能拦截对对象的更改,这将在对象构造之后发生。不,拦截对嵌套数组/对象的更改是一个完全不同的主题(参见例如here 和许多其他帖子),请不要偏离这个讨论。
  • @NurbolAlpysbayev 您应该简单地在Model 和BaseModel 之间交换代理。你可能注意到Category,它在扩展Model(构造函数返回一个代理)的东西上工作得很好。

标签: javascript ecmascript-6 es6-proxy


【解决方案1】:

在foo.method 中,您没有通过代理对象设置属性,因此显然不会触发陷阱。代理不会修改原始对象。

【讨论】:

    【解决方案2】:

    原始对象上不存在陷阱。如果可以,您将不需要单独的代理对象。这个想法是使用代理而不是原始的。

    一种可能性是让您的原始对象继承自代理对象。这可能会奏效,具体取决于您实际需要完成的任务。

    let fooProxy = new Proxy({}, {
        set(target, key, val) { console.log('set trap triggered!') }
    })
    
    let foo = Object.create(fooProxy, {
      method: {value: function() { this.bar = 'baz2' }}
    })
    
    fooProxy.bar = 'any value'
    foo.method()

    【讨论】:

    • 谢谢!至少在我个人情况下,您的解决方案似乎比接受的 .call() 解决方案(对我有用)更难。不管怎样,我现在就试试。
    • @NurbolAlpysbayev:这实际上只是旧的标准原型继承,但原型是您的Proxy 实例。我使用Object.create 来设置继承,但您可以使用较旧的方式与构造函数的.prototype,或较新的class 语法。
    • 很久以前,我来自 PHP 世界,一直很难处理原型继承。对我来说,类语法在思想上更简单,但现在经过多年,当然只是简单一点,但仍然更简单。所以这只是我,我猜:)
    • @NurbolAlpysbayev:原型继承的简单性被 JS 提供的各种笨拙的设置方式所掩盖。真的只是"...查看对象A的这个属性,如果没有找到,但是A继承自B,那么在B上查找属性" .因此,原型链只是一个对象的链表,当找不到您要查找的属性时,它会自动遍历。
    • “被 JS 提供的各种笨拙的设置方式所隐藏”——是的,在 es6 之前确实如此:)
    【解决方案3】:

    设置陷阱未触发,因为您在method() 中访问的this 不是代理,而是原始对象foo。代理不会更改原始对象。您可以通过检查 this 在 method() 中的内容来看到这一点:

    let foo = {
      method () {
        return this === fooProxy
      }
    }
    let fooProxy = new Proxy(foo, {})
    document.write(foo.method()) //=> false

    您可以改为使用Function#call 在调用时显式设置method() 的上下文:

    let foo = {bar: 'baz', method() { this.bar = 'baz2' }}
    let fooProxy = new Proxy(foo, {set(target, key, val) { console.log('set trap triggered!') }})
    
    fooProxy.bar = 'any value' //=> set trap triggered!
    foo.method.call(fooProxy) //=> set trap triggered!

    【讨论】:

    • 我需要的是 .call() 东西。谢谢你的想法!并将我的问题分解为简单的部分。
    【解决方案4】:

    您还可以将代理添加到对象原型中。

    SomeObject.prototype = new Proxy(
        SomeObject.prototype,
        SomeObjectHandlerProxy
    );
    
    instance = new SomeObject();
    

    类似的东西应该可以工作

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-28
      • 1970-01-01
      • 2016-12-12
      • 1970-01-01
      • 2013-12-02
      • 1970-01-01
      • 1970-01-01
      • 2011-12-02
      相关资源
      最近更新 更多