web-dev-qa-db-ja.com

ネストされたオブジェクトにJavaScriptプロキシを使用する方法

私は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

35
Aflred

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'
33

GitHubのライブラリ を公開しましたが、これも同様です。また、コールバック関数に、フルパスとともにどのような変更が行われたかを報告します。

Michalの答えは良いですが、新しいProxyevery timeが作成され、ネストされたオブジェクトにアクセスします。使用状況によっては、メモリオーバーヘッドが非常に大きくなる可能性があります。

8
Elliot B.

MichałPerłakowski による例のわずかな変更。このアプローチの利点は、値にアクセスするたびにではなく、ネストされたプロキシが1回だけ作成されることです。

アクセスされるプロキシのプロパティがオブジェクトまたは配列の場合、プロパティの値は別のプロキシに置き換えられます。ゲッターのisProxyプロパティは、現在アクセスされているオブジェクトがプロキシかどうかを検出するために使用されます。 isProxyの名前を変更して、保存されたオブジェクトのプロパティと名前の衝突を避けることができます。

注:ネストされたプロキシは、セッターではなくゲッターで定義されるため、データが実際にどこかで使用される場合にのみ作成されます。これは、ユースケースに適している場合とそうでない場合があります。

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.isBindingProxy && 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"
3
James Coyle

また、深くネストされたプロキシオブジェクトの更新を監視するためのライブラリタイプ関数を作成しました(一方向バインドデータモデルとして使用するために作成しました)。 Elliotのライブラリと比較すると、100行未満で理解するのが少し簡単です。さらに、新しいProxyオブジェクトが作成されることについてのElliotの心配は時期尚早な最適化だと思うので、コードの機能についての推論をより簡単にするために、その機能を維持しました。

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":"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: []};

これが役立つことを願っています!

0
jonny