78 votes

Comment utiliser le proxy javascript pour les objets imbriqués

J'ai ce code dans 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'
 

si je fais proxy.inner.salary = 555; cela ne fonctionne pas.

Cependant, si je fais proxy.firstName = "Anne" , alors cela fonctionne très bien.

Je ne comprends pas pourquoi cela ne fonctionne pas récursivement.

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

102voto

Gothdo Points 4842

Vous pouvez ajouter une interruption get et renvoyer un nouveau proxy avec validator comme gestionnaire:

 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' 

29voto

James Coyle Points 5187

Une légère modification sur l'exemple par Michał Perłakowski avec l'avantage de cette approche est que le imbriquée procuration n'est créé qu'une seule fois plutôt que chaque fois qu'une valeur est accessible.

Si la propriété du proxy en cours d'accès est un objet ou un tableau, la valeur de la propriété est remplacé par un autre proxy. L' isProxy propriété dans le getter est utilisé pour détecter si l'accessibles actuellement l'objet est un proxy ou non. Vous pouvez modifier le nom de l' isProxy afin d'éviter les conflits de noms avec les propriétés des objets stockés.

Remarque: le imbriquée proxy est défini dans la lecture plutôt que de le setter de sorte qu'il n'est créé que si les données sont effectivement utilisés quelque part. Cela peut ou peut ne pas convenir à votre cas d'utilisation.

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"

17voto

Elliot B. Points 4236

J'ai publié une bibliothèque sur GitHub qui fait cela également. Il signalera également à une fonction de rappel quelles modifications ont eu lieu ainsi que leur chemin complet.

La réponse de Michal est bonne, mais elle crée un nouveau Proxy chaque fois qu'un objet imbriqué est accédé. Selon votre utilisation, cela peut entraîner une surcharge de mémoire très importante.

4voto

jonny Points 2312

J'ai également créé une bibliothèque de type de fonction pour l'observation des mises à jour sur profondément imbriqués objets proxy (que j'ai créé pour l'utiliser comme un moyen lié modèle de données). Par rapport à Elliot de la bibliothèque, c'est légèrement plus facile à comprendre à < 100 lignes. En outre, je pense Elliot de vous soucier de nouveaux objets Proxy est un prématuré de l'optimisation, j'ai donc gardé cette fonction pour la rendre plus simple de raisonner sur la fonction du 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
    *   <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};
})();

Il est alors trivial de créer un observables modèle et inscrire des observateurs:

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

Espérons que cela est utile!

1voto

Mr. Nielsen Points 15

J'ai aussi publié une petite bibliothèque sur GitHub capable d'intercepter et de valider les modifications sur les objets imbriqués en utilisant des Proxys. Comme Elliot de la bibliothèque, il permet aussi d'économiser de l'généré Procurations de sorte qu'ils n'ont pas à être généré à chaque fois.

Elliot de la bibliothèque est grande, mais la façon dont il signale les changements lorsque les fonctions sont impliqués n'était pas à mon goût. La plus grande différence entre le mien et le sien est que les changements sont signalés de manière atomique avec ma bibliothèque. C'est, d'une action déclenche exactement un rapport de changement, et pas plus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X