59

我在 js bin 中有这段代码:

var validator = {
  set (target, key, value) {
    console.log(target);
    console.log(key);
    console.log(value);
    if(isObject(target[key])){

    }
    return true
  }
}


var person = {
      firstName: "alfred",
      lastName: "john",
      inner: {
        salary: 8250,
        Proffesion: ".NET Developer"
      }
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'

如果我这样做proxy.inner.salary = 555;是行不通的。

但是,如果我这样做proxy.firstName = "Anne",那么效果很好。

我不明白为什么它不能递归工作。

http://jsbin.com/dinerotiwe/edit?html,js,console

4

5 回答 5

81

您可以添加一个get陷阱并返回一个新代理validator作为处理程序:

var validator = {
  get(target, key) {
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], validator)
    } else {
      return target[key];
    }
  },
  set (target, key, value) {
    console.log(target);
    console.log(key);
    console.log(value);
    return true
  }
}


var person = {
      firstName: "alfred",
      lastName: "john",
      inner: {
        salary: 8250,
        Proffesion: ".NET Developer"
      }
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'

于 2016-12-23T10:59:35.170 回答
21

Michał Perłakowski对示例稍作修改,这种方法的好处是嵌套代理只创建一次,而不是每次访问值时创建。

如果正在访问的代理的属性是对象或数组,则该属性的值将被另一个代理替换。getter 中的isProxy属性用于检测当前访问的对象是否为代理。您可能需要更改名称isProxy以避免与存储对象的属性发生命名冲突。

注意:嵌套代理是在 getter 而不是 setter 中定义的,因此只有在数据实际在某处使用时才会创建它。这可能适合也可能不适合您的用例。

const handler = {
  get(target, key) {
    if (key == 'isProxy')
      return true;

    const prop = target[key];

    // return if property not found
    if (typeof prop == 'undefined')
      return;

    // set value as proxy if object
    if (!prop.isProxy && typeof prop === 'object')
      target[key] = new Proxy(prop, handler);

    return target[key];
  },
  set(target, key, value) {
    console.log('Setting', target, `.${key} to equal`, value);

    // todo : call callback

    target[key] = value;
    return true;
  }
};

const test = {
  string: "data",
  number: 231321,
  object: {
    string: "data",
    number: 32434
  },
  array: [
    1, 2, 3, 4, 5
  ],
};

const proxy = new Proxy(test, handler);

console.log(proxy);
console.log(proxy.string); // "data"

proxy.string = "Hello";

console.log(proxy.string); // "Hello"

console.log(proxy.object); // { "string": "data", "number": 32434 }

proxy.object.string = "World";

console.log(proxy.object.string); // "World"

于 2018-06-06T14:56:08.403 回答
14

在 GitHub 上发布了一个库,它也可以做到这一点。它还将向回调函数报告发生了哪些修改以及它们的完整路径。

Michal 的回答很好,但是Proxy 每次访问嵌套对象时都会创建一个新对象。根据您的使用情况,这可能会导致非常大的内存开销。

于 2018-01-16T18:38:30.120 回答
3

我还创建了一个库类型函数,用于观察深层嵌套代理对象的更新(我创建它是为了用作单向绑定数据模型)。与 Elliot 的库相比,它在 < 100 行时更容易理解。此外,我认为 Elliot 对生成新 Proxy 对象的担忧是一种过早的优化,因此我保留了该功能以简化对代码功能的推理。

observable-model.js

let ObservableModel = (function () {
    /*
    * observableValidation: This is a validation handler for the observable model construct.
    * It allows objects to be created with deeply nested object hierarchies, each of which
    * is a proxy implementing the observable validator. It uses markers to track the path an update to the object takes
    *   <path> is an array of values representing the breadcrumb trail of object properties up until the final get/set action
    *   <rootTarget> the earliest property in this <path> which contained an observers array    *
    */
    let observableValidation = {
        get(target, prop) {
            this.updateMarkers(target, prop);
            if (target[prop] && typeof target[prop] === 'object') {
                target[prop] = new Proxy(target[prop], observableValidation);
                return new Proxy(target[prop], observableValidation);
            } else {
                return target[prop];
            }
        },
        set(target, prop, value) {
            this.updateMarkers(target, prop);
            // user is attempting to update an entire observable field
            // so maintain the observers array
            target[prop] = this.path.length === 1 && prop !== 'length'
                ? Object.assign(value, { observers: target[prop].observers })
                : value;
            // don't send events on observer changes / magic length changes
            if(!this.path.includes('observers') && prop !== 'length') {
                this.rootTarget.observers.forEach(o => o.onEvent(this.path, value));
            }
            // reset the markers
            this.rootTarget = undefined;
            this.path.length = 0;
            return true;
        },
        updateMarkers(target, prop) {
            this.path.push(prop);
            this.rootTarget = this.path.length === 1 && prop !== 'length'
                ? target[prop]
                : target;
        },
        path: [],
        set rootTarget(target) {
            if(typeof target === 'undefined') {
                this._rootTarget = undefined;
            }
            else if(!this._rootTarget && target.hasOwnProperty('observers')) {
                this._rootTarget = Object.assign({}, target);
            }
        },
        get rootTarget() {
            return this._rootTarget;
        }
    };

    /*
    * create: Creates an object with keys governed by the fields array
    * The value at each key is an object with an observers array
    */
    function create(fields) {
        let observableModel = {};
        fields.forEach(f => observableModel[f] = { observers: [] });
        return new Proxy(observableModel, observableValidation);
    }

    return {create: create};
})();

然后创建一个可观察模型并注册观察者就很简单了:

app.js

// give the create function a list of fields to convert into observables
let model = ObservableModel.create([
    'profile',
    'availableGames'
]);

// define the observer handler. it must have an onEvent function
// to handle events sent by the model
let profileObserver = {
    onEvent(field, newValue) {
        console.log(
            'handling profile event: \n\tfield: %s\n\tnewValue: %s',
            JSON.stringify(field),
            JSON.stringify(newValue));
    }
};

// register the observer on the profile field of the model
model.profile.observers.push(profileObserver);

// make a change to profile - the observer prints:
// handling profile event:
//        field: ["profile"]
//        newValue: {"name":{"first":"foo","last":"bar"},"observers":[{}
// ]}
model.profile = {name: {first: 'foo', last: 'bar'}};

// make a change to available games - no listeners are registered, so all
// it does is change the model, nothing else
model.availableGames['1234'] = {players: []};

希望这是有用的!

于 2018-02-12T12:15:05.277 回答
0

我基于Michał Perłakowski代码编写了一个函数。我在 set/get 函数中添加了对属性路径的访问。另外,我添加了类型。

    const createHander = <T>(path: string[] = []) => ({
        get: (target: T, key: keyof T): any => {
            if (key == 'isProxy') return true;
            if (typeof target[key] === 'object' && target[key] != null)
                return new Proxy(
                    target[key],
                    createHander<any>([...path, key as string])
                );
            return target[key];
        },
        set: (target: T, key: keyof T, value: any) =>  {
            console.log(`Setting ${[...path, key]} to: `, value);
            target[key] = value;
            return true;
        }
    });
    
    const proxy = new Proxy(obj ,createHander<ObjectType>());
于 2021-10-11T21:23:18.247 回答