如何创建Deep Proxy?

时间:2017-04-03 06:29:19

标签: javascript node.js proxy ecmascript-6

如何创建深度/递归Proxy

具体来说,我想知道在对象树中的任何地方设置或修改属性。

这是我到目前为止所得到的:

function deepProxy(obj) {
    return new Proxy(obj, {
        set(target, property, value, receiver) {
            console.log('set', property,'=', value);
            if(typeof value === 'object') {
                for(let k of Object.keys(value)) {
                    if(typeof value[k] === 'object') {
                        value[k] = deepProxy(value[k]);
                    }
                }
                value = deepProxy(value);
            }
            target[property] = value;
            return true;
        },
        deleteProperty(target, property) {
            if(Reflect.has(target, property)) {
                let deleted = Reflect.deleteProperty(target, property);
                if(deleted) {
                    console.log('delete', property);
                }
                return deleted;
            }
            return false;
        }
    });
}

这是我的测试:

const proxy = deepProxy({});
const baz = {baz: 9, quux: {duck: 6}};

proxy.foo = 5;
proxy.bar = baz;
proxy.bar.baz = 10;
proxy.bar.quux.duck = 999;

baz.quux.duck = 777;
delete proxy.bar;
delete proxy.bar; // should not trigger notifcation -- property was already deleted
baz.quux.duck = 666;  // should not trigger notification -- 'bar' was detached

console.log(proxy);

输出:

set foo = 5
set bar = { baz: 9, quux: { duck: 6 } }
set baz = 10
set duck = 999
set duck = 777
delete bar
set duck = 666
{ foo: 5 }

正如你所看到的,我只是让它工作,除了baz.quux.duck = 666触发了setter,即使我已经从proxy的对象树中删除了它。在删除属性后,有没有办法取消baz

4 个答案:

答案 0 :(得分:6)

修复了原始问题中的一堆错误。我认为现在有效:

    Dim dr As DataRow = dtOrderingItem.NewRow

        dr("ItemCode") = clckdItemCode
        dr("ItemName") = clickdItemName
        dr("ServiceCode") = srvc
        dr("RatePerItem") = txtItemRate.Text

        dtOrderingItem.Rows.Add(dr)
        dgvCart.datasource=dtOrderingItem

用法:

function createDeepProxy(target, handler) {
    const preproxy = new WeakMap();

    function makeHandler(path) {
        return {
            set(target, key, value, receiver) {
                if(typeof value === 'object') {
                    value = proxify(value, [...path, key]);
                }
                target[key] = value;

                if(handler.set) {
                    handler.set(target, [...path, key], value, receiver);
                }
                return true;
            },

            deleteProperty(target, key) {
                if(Reflect.has(target, key)) {
                    unproxy(target, key);
                    let deleted = Reflect.deleteProperty(target, key);
                    if(deleted && handler.deleteProperty) {
                        handler.deleteProperty(target, [...path, key]);
                    }
                    return deleted;
                }
                return false;
            }
        }
    }

    function unproxy(obj, key) {
        if(preproxy.has(obj[key])) {
            // console.log('unproxy',key);
            obj[key] = preproxy.get(obj[key]);
            preproxy.delete(obj[key]);
        }

        for(let k of Object.keys(obj[key])) {
            if(typeof obj[key][k] === 'object') {
                unproxy(obj[key], k);
            }
        }

    }

    function proxify(obj, path) {
        for(let key of Object.keys(obj)) {
            if(typeof obj[key] === 'object') {
                obj[key] = proxify(obj[key], [...path, key]);
            }
        }
        let p = new Proxy(obj, makeHandler(path));
        preproxy.set(p, obj);
        return p;
    }

    return proxify(target, []);
}


module.exports = createDeepProxy;

您可以将完整对象分配给属性,它们将以递归方式进行代理,然后当您从代理对象中删除它们时,它们将被取消,这样您就不会收到不再属于其中的对象的通知对象图。

我不知道如果你创建一个循环链接会发生什么。我不推荐它。

答案 1 :(得分:3)

我在GitHub上发布了一个图书馆( Observable Slim )。它还有一些额外的功能:

  • 每当发生更改时,都会向指定的回调报告。
  • 将阻止用户尝试代理代理。
  • 保留已代理对象的存储,并将重用现有代理而不是创建新代理(非常重要的性能影响)。
  • 允许用户从子对象遍历并检索父对象。
  • 使用ES5编写并与Proxy Polyfill配合使用,因此可以非常轻松地在旧版浏览器中进行部署。

请随意看看,希望也有所贡献!

mpen的答案是可靠的,但是如果您尝试代理代理并且如果您尝试代理已经通过父代理代理的嵌套子代,则它会解决内存使用效率低下问题。 Reflect的使用也使得支持旧版浏览器变得更麻烦。

答案 2 :(得分:1)

这是一个更简单的工具,可以满足我的要求。

此示例使您能够深入获取或设置任何属性,并对任何属性(无论是否为深度)调用更改处理程序以表明其有效:

function createOnChangeProxy(onChange, target) {
return new Proxy(target, {
    get(target, property) {
        const item = target[property]
        if (item && typeof item === 'object') return createOnChangeProxy(onChange, item)
        return item
    },
    set(target, property, newValue) {
        target[property] = newValue
        onChange()
        return true
    },
})
}

let changeCount = 0
const o = createOnChangeProxy(() => changeCount++, {})

o.foo = 1
o.bar = 2
o.baz = {}
o.baz.lorem = true
o.baz.yeee = {}
o.baz.yeee.wooo = 12

console.log(changeCount === 6)

const proxy = createOnChangeProxy(() => console.log('change'), {})
const baz = {baz: 9, quux: {duck: 6}};

proxy.foo = 5;
proxy.bar = baz;
proxy.bar.baz = 10;
proxy.bar.quux.duck = 999;

baz.quux.duck = 777;
delete proxy.bar;
delete proxy.bar; // should not trigger notifcation -- property was already deleted
baz.quux.duck = 666;  // should not trigger notification -- 'bar' was detached

console.log(proxy);

在使用代码示例的部分中,没有多余的通知,如您想要的注释。

答案 3 :(得分:1)

@mpen答案很棒。我将他的示例移到了可以轻松扩展的DeepProxy类中。

class DeepProxy {
    constructor(target, handler) {
        this._preproxy = new WeakMap();
        this._handler = handler;
        return this.proxify(target, []);
    }

    makeHandler(path) {
        let dp = this;
        return {
            set(target, key, value, receiver) {
                if (typeof value === 'object') {
                    value = dp.proxify(value, [...path, key]);
                }
                target[key] = value;

                if (dp._handler.set) {
                    dp._handler.set(target, [...path, key], value, receiver);
                }
                return true;
            },

            deleteProperty(target, key) {
                if (Reflect.has(target, key)) {
                    dp.unproxy(target, key);
                    let deleted = Reflect.deleteProperty(target, key);
                    if (deleted && dp._handler.deleteProperty) {
                        dp._handler.deleteProperty(target, [...path, key]);
                    }
                    return deleted;
                }
                return false;
            }
        }
    }

    unproxy(obj, key) {
        if (this._preproxy.has(obj[key])) {
            // console.log('unproxy',key);
            obj[key] = this._preproxy.get(obj[key]);
            this._preproxy.delete(obj[key]);
        }

        for (let k of Object.keys(obj[key])) {
            if (typeof obj[key][k] === 'object') {
                this.unproxy(obj[key], k);
            }
        }

    }

    proxify(obj, path) {
        for (let key of Object.keys(obj)) {
            if (typeof obj[key] === 'object') {
                obj[key] = this.proxify(obj[key], [...path, key]);
            }
        }
        let p = new Proxy(obj, this.makeHandler(path));
        this._preproxy.set(p, obj);
        return p;
    }
}

// TEST DeepProxy


let obj = {
    foo: 'baz',
}


let proxied = new DeepProxy(obj, {
    set(target, path, value, receiver) {
        console.log('set', path.join('.'), '=', JSON.stringify(value));
    },

    deleteProperty(target, path) {
        console.log('delete', path.join('.'));
    }
});


proxied.foo = 'bar';
proxied.deep = {}
proxied.deep.blue = 'sea';
delete proxied.foo;
delete proxied.deep; // triggers delete on 'deep' but not 'deep.blue'