web-dev-qa-db-fra.com

Comment utiliser un 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

29
Aflred

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'

25

J'ai publié une bibliothèque sur GitHub qui le fait aussi. Il indiquera é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 une nouvelle Proxyà chaque accès à un objet imbriqué. En fonction de votre utilisation, cela peut entraîner une surcharge de mémoire très importante.

8
Elliot B.

Une légère modification de l’exemple de Michał Perłakowski , l’avantage de cette approche étant que le proxy imbriqué n’est créé qu’une fois, et non à chaque accès à une valeur.

Si la propriété du proxy auquel vous accédez est un objet ou un tableau, la valeur de la propriété est remplacée par un autre proxy. La propriété isProxy du getter est utilisée pour détecter si l’objet actuellement utilisé est un proxy ou non. Vous souhaiterez peut-être modifier le nom de isProxy pour éviter les collisions de noms avec les propriétés des objets stockés.

Remarque: le proxy imbriqué est défini dans le getter plutôt que dans le setter. Il est donc créé uniquement si les données sont réellement utilisées quelque part. Cela peut ou non 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"

3
James Coyle

J'ai également publié une petite bibliothèque sur GitHub qui peut intercepter et valider les modifications apportées aux objets imbriqués à l'aide de proxies. Comme la bibliothèque d'Elliot }, il enregistre également les procurations générées afin qu'elles ne soient pas nécessairement générées à chaque fois.

La bibliothèque d'Elliot est excellente, mais la façon dont elle rend compte des modifications lorsque des fonctions sont impliquées n'était pas à mon goût. La plus grande différence entre le mien et le sien est que les changements sont rapportés de manière atomique avec ma bibliothèque. C'est-à-dire qu'une action déclenche exactement un rapport de modification, et non plus.

1
Mr. Nielsen

J'ai également créé une fonction de type bibliothèque pour observer les mises à jour sur des objets proxy profondément imbriqués (je l'ai créée pour l'utiliser comme modèle de données lié à sens unique). Comparé à la bibliothèque d'Elliot, il est légèrement plus facile de comprendre moins de 100 lignes. De plus, je pense que l'inquiétude d'Elliot au sujet de la création de nouveaux objets proxy est une optimisation prématurée. J'ai donc conservé cette fonctionnalité pour simplifier la tâche 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 modèle observable et d’enregistrer 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: []};

J'espère que c'est utile!

0
jonny