How to use javascript proxy for nested objects

前端 未结 4 781
礼貌的吻别
礼貌的吻别 2020-12-02 13:22

I have this code in js bin:

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


        
4条回答
  •  夕颜
    夕颜 (楼主)
    2020-12-02 13:56

    I have also created a library type function for observing updates on deeply nested proxy objects (I created it for use as a one-way bound data model). Compared to Elliot's library it's slightly easier to understand at < 100 lines. Moreover, I think Elliot's worry about new Proxy objects being made is a premature optimisation, so I kept that feature to make it simpler to reason about the function of the code.

    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
        *    is an array of values representing the breadcrumb trail of object properties up until the final get/set action
        *    the earliest property in this  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};
    })();
    

    It's then trivial to create an observable model and register observers:

    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":"jonny","last":"brooks"},"observers":[{}
    // ]}
    model.profile = {name: {first: 'jonny', last: 'brooks'}};
    
    // make a change to available games - no listeners are registered, so all
    // it does is change the model, nothing else
    model.availableGames['1234'] = {players: []};
    

    Hope this is useful!

提交回复
热议问题