Javascript Proxy set() local property on inherited objects - javascript

According to MDN,
handler.set() can trap Inherited property assignment:
Object.create(proxy)[foo] = bar;
In which case, how does one both monitor and allow local assignments on inherited objects?
var base = {
foo: function(){
return "foo";
}
}
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
console.log("called: " + property + " = " + value, "on", receiver);
//receiver[property] = value; //Infinite loop!?!?!?!?!
//target[property] = value // This is incorrect -> it will set the property on base.
/*
Fill in code here.
*/
return true;
}
})
var inherited = {}
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
}
//Test cases
console.log(base.foo); //function foo
console.log(base.bar); //undefined
console.log(inherited.hasOwnProperty("bar")) //true

After some additional thought, i noticed that it intercepts 3 ops:
Property assignment: proxy[foo] = bar and proxy.foo = bar
Inherited property assignment: Object.create(proxy)[foo] = bar
Reflect.set()
but not Object.defineProperty() which appears to be even lower level than the = operator.
Thus the following works:
var base = {
foo: function(){
return "foo";
}
};
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
var p = Object.getPrototypeOf(receiver);
Object.defineProperty(receiver, property, { value: value }); // ***
return true;
}
});
var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
};
// Test cases
console.log(base.foo); // function foo
console.log(base.bar); // undefined
console.log(inherited.bar); // function bar
console.log(inherited.hasOwnProperty("bar")) // true

I see two options (maybe):
Store the property in a Map, keeping the Maps for various receivers in a WeakMap keyed by the receiver. Satisfy get by checking the Map and returning the mapping there instead of from the object. (Also has.) Slight problem is that you also need to proxy the receiver (not just base) in order to handle ownKeys. So this could be unworkable.
Temporarily get the proxy out of the inheritance chain while setting.
Here's that second one:
var base = {
foo: function(){
return "foo";
}
};
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
const p = Object.getPrototypeOf(receiver); // ***
Object.setPrototypeOf(receiver, null); // ***
receiver[property] = value; // ***
Object.setPrototypeOf(receiver, p); // ***
return true;
}
});
var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
};
// Test cases
console.log("base.foo:", base.foo); // function foo
console.log("base.bar:", base.bar); // undefined
console.log("inherited.bar:", inherited.bar); // function bar
console.log("inherited has own bar?", inherited.hasOwnProperty("bar")); // true

Related

Is there any point to use getters/setters for an object?

It seems to me that using getters and setters for an object inside a class has no point to it. As I understand it, get/set is useful because it prevents someone outside the class changing something that shouldn't be changed or changing it to something it shouldn't be. However it seems pointless for objects. For example, I have a person with an address, I want to prevent editing the address, you can only view it:
class Person{
constructor(name, address){
this._name = name;
this._address = address;
}
get address(){
return this._address;
}
}
let bob = new Person("bob", {
street: "123 Main Street",
city: "Los Angelos",
state: "California"
});
But then you can still edit it like this:
let address = bob.address;
address.state = "New York";
To prevent this, I would think that you have to return a copy of the object instead of the reference. However, as far as i know, there is no standard way to deep clone an object. So you either have to shallow clone it, which seems not ideal if you have lots of nested references, or just return the reference to the object, which can be edited.
Am I missing something here?
Consider this class.
class Test {
constructor(val) {
this._foo = val;
}
set foo(val) {
throw new Error("It's not possible to change the foo property!")
}
set boo(val) {
console.log("setting _boo")
this._boo = val;
}
get foo() {
console.log("getting _foo");
return this._foo;
}
}
try {
let test = new Test('foooooo');
test.boo = "booooo";
console.log(`foo: ${test.foo}`);
test.foo = "bar";
} catch (e) {
console.log(e);
}
With "Setter" it's possible to control initializing properties.
You can see that it's possible to change the value of "boo" property but any attempt to change the value of the "foo" will throw an exception.
With "Getter" it's possible to control retrieving the value of properties.
You can see that it's possible to retrieve the value of "foo" property but not "boo" and its value is private.
PS:
Here are some examples to better understand JS behavior with objects and arrays:
//This works fine:
//Object
const obj = {};
obj.foo = 'bar';
console.log(obj); // {foo : 'bar'}
obj.foo = 'bar2';
console.log(obj); // {foo : 'bar2'}
//---------------------------------------
//Array:
const arr = [];
arr.push('foo');
console.log(arr); // ['foo']
arr.unshift("foo2");
console.log(arr); // ['foo2', 'foo']
arr.pop();
console.log(arr); // ['foo2']
//===========================================
//but these won't work:
const obj = {};
obj = {foo: 'bar'}; // error - re-assigning
const arr = ['foo'];
const arr = ['bar']; // error - re-declaring
const foo = 'bar';
foo = 'bar2'; // error - can not re-assign
var foo = 'bar3'; // error - already declared
function foo() {}; // error - already declared
New Example:
class A {
constructor(val) {
this._foo = val;
}
set foo(val) {
throw new Error("It's not possible to change the foo property!")
}
get foo() {
return this._foo;
}
}
class B {
constructor(val) {
this._obj = new A(val);
}
get Obj() {
return this._obj;
}
}
let b = new B('Test');
b.Obj.foo = 'new value';
console.log(b.Obj.foo);
In this manner, it's not possible to change the values ​​of the internal object.

What happens when setting object property without setter in javascript

var vehicle = function(){
var type;
var tyre;
this.tellTyres = function(){
console.log(type + " has " + tyre + " tyres");
};
this.__defineGetter__("type", function(){
return type;
});
this.__defineSetter__("type", function(val){
type = val;
});
this.__defineGetter__("tyre", function(){
return tyre;
});
// this.__defineSetter__("tyre", function(val){
// tyre = val;
// });
};
var car = new vehicle();
car.type = "Car";
car.tyre = 4;
console.log(car.tyre);
car.tellTyres();
I was learning about the getter and setter. Then I realized Javascript is not throwing any error while setting the value of car.tyre without having its setter method.
What happens to the car.tyre property outside the constructor. Where does the value 4 store? Does it override?
JavaScript objects are more like dictionaries than like Java objects. This means that you can set and get an object's properties just by using the property accessor operators . and []:
var obj = { foo: 'bar' };
obj.baz = 17;
console.log(obj.foo, obj.baz); // logs '"bar" 17'
And that is absolutely fine.
But sometimes, you want to do something whenever someone modifies a property of your object. In these cases, you define a getter or setter function for that property (use Object.defineProperty instead of defineGetter and defineSetter):
var obj = { foo: 'bar' };
Object.defineProperty(obj, 'baz', {
get: function () {
console.log('Someone wants to read the property "baz"!');
return 34;
},
set: function (value) {
console.log('You are not allowed to modify the property "baz"!');
}
});
obj.baz = 17; // doesn't work
console.log(obj.foo, obj.baz); // logs '"bar" 34'
When you create a new vehicle(), you create a new object, on which you can set or read properties. You don't need the getters and setters.

Override default get in javascript class such as __get in php

I'm building a javascript library and I would like to be able to do exactly like the PHP's __get does.
My library has a attributes property which stores each model's attributes. Now, I am force to get an attribute using a .get method. But I would be able to do it with a getter. Let's says that User extends my model class.
let instance = new User({firstname: 'John', lastname: 'Doe'});
console.log(instance.get('firstname')); // gives me 'John'
I want to be able to do instance.firstname which will call the .get method passing 'firstname' as parameter. In PHP you can do it that way : http://php.net/manual/fr/language.oop5.overloading.php#object.get
Is this something possible?
Thank you all
This is easy using ES 2015 classes:
class Foo {
constructor () {
this._bar = null;
}
get bar () {
doStuff();
return this._bar;
}
set bar (val) {
doOtherStuff();
this._bar = val;
return this;
}
};
var foo = new Foo();
foo.bar = 3; // calls setter function
console.log(foo.bar); // calls getter function
here's the (simplified) output from babel:
var Foo = function () {
function Foo() {
this._bar = null;
}
_createClass(Foo, [{
key: "bar",
get: function get() {
doStuff();
return this._bar;
},
set: function set(val) {
doOtherStuff();
this._bar = val;
return this;
}
}]);
return Foo;
}();
Note that this isn't just for classes, any arbitrary object can have these:
var baz = {
get qux() {
// arbitrary code
},
set qux(val) {
// arbitrary code
}
};
Source.
EDIT
What you want is possible but only in native ES 6 environments, as Proxy cannot be polyfilled.
var getter = function(target, property, proxy) {
console.log(`Getting the ${property} property of the obj.`);
return target[property];
};
var setter = function(target, property, value, proxy) {
console.log(`Setting the ${property} property to ${value}.`);
target[property] = value;
};
var emptyObj = {};
var obj = new Proxy(emptyObj, {
get: getter,
set: setter
});
obj.a = 3; // logs 'Setting the a property to 3'
var foo = obj.a; // logs 'Getting the a property of the obj'
Quite simply assign the properties in a loop:
User = function (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
User.prototype = {
// further methods
}
Using the ES6 class syntax, - I have to admit I do not see the point of writing things this way:
class User {
constructor (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
// further methods
}
Remember: the second syntax is exactly what happens with the first one, only with some sugar on top.

Javascript - catch access to property of object [duplicate]

This question already has answers here:
Is it possible to implement dynamic getters/setters in JavaScript?
(5 answers)
Closed 3 years ago.
Is it possible to capture when a (any) property of an object is accessed, or attempting to be accessed?
Example:
I have created custom object Foo
var Foo = (function(){
var self = {};
//... set a few properties
return self;
})();
Then there is some action against Foo - someone tries to access property bar
Foo.bar
Is there way (prototype, perhaps) to capture this? bar may be undefined on Foo. I could suffice with capturing any attempted access to undefined properties.
For instance, if bar is undefined on Foo, and Foo.bar is attempted, something like:
Foo.prototype.undefined = function(){
var name = this.name; //name of property they attempted to access (bar)
Foo[name] = function(){
//...do something
};
return Foo[name];
}
But functional, unlike my example.
Concept
Foo.* = function(){
}
Background
If I have a custom function, I can listen for every time this function is called (see below). Just wondering if it's possible with property access.
Foo = function(){};
Foo.prototype.call = function(thisArg){
console.log(this, thisArg);
return this;
}
Yes, this is possible in ES2015+, using the Proxy. It's not possible in ES5 and earlier, not even with polyfills.
It took me a while, but I finally found my previous answer to this question. See that answer for all the details on proxies and such.
Here's the proxy example from that answer:
const obj = new Proxy({}, {
get: function(target, name, receiver) {
if (!(name in target)) {
console.log("Getting non-existant property '" + name + "'");
return undefined;
}
return Reflect.get(target, name, receiver);
},
set: function(target, name, value, receiver) {
if (!(name in target)) {
console.log("Setting non-existant property '" + name + "', initial value: " + value);
}
return Reflect.set(target, name, value, receiver);
}
});
console.log("[before] obj.foo = " + obj.foo);
obj.foo = "bar";
console.log("[after] obj.foo = " + obj.foo);
obj.foo = "baz";
console.log("[after] obj.foo = " + obj.foo);
Live Copy:
"use strict";
const obj = new Proxy({}, {
get: function(target, name, receiver) {
if (!(name in target)) {
console.log("Getting non-existant property '" + name + "'");
return undefined;
}
return Reflect.get(target, name, receiver);
},
set: function(target, name, value, receiver) {
if (!(name in target)) {
console.log("Setting non-existant property '" + name + "', initial value: " + value);
}
return Reflect.set(target, name, value, receiver);
}
});
console.log("[before] obj.foo = " + obj.foo);
obj.foo = "bar";
console.log("[after] obj.foo = " + obj.foo);
obj.foo = "baz";
console.log("[after] obj.foo = " + obj.foo);
When run, that outputs:
Getting non-existant property 'foo'
[before] obj.foo = undefined
Setting non-existant property 'foo', initial value: bar
[after] obj.foo = bar
[after] obj.foo = baz
I'll write this under the assumption you're trying to debug something. As Crowder said, this is only available on newer browsers; so it's very useful for testing code that does something you don't want it to. But, I remove it for production code.
Object.defineProperty(Foo, 'bar', {
set: function() {
debugger; // Here is where I'll take a look in the developer console, figure out what's
// gone wrong, and then remove this whole block.
}
});
Looks like megawac beat me to it. You can also find some Mozilla documentation on the features here.
Like answered already, it will only be possible using the Proxy object in ECMAScript6. Meanwhile, depending on your needs and overall design, you can still achieve this by implementing something similar.
E.g.
function WrappingProxy(object, noSuchMember) {
if (!this instanceof WrappingProxy) return new WrappingProxy(object);
this._object = object;
if (noSuchMember) this.noSuchMember = noSuchMember;
}
WrappingProxy.prototype = {
constructor: WrappingProxy,
get: function (propertyName) {
var obj = this._object;
if (propertyName in obj) return obj[propertyName];
if (this.noSuchMember) this.noSuchMember(propertyName, 'property');
},
set: function (propertyName, value) {
return this._object[propertyName] = value;
},
invoke: function (functionName) {
var obj = this._object,
args = Array.prototype.slice.call(arguments, 1);
if (functionName in obj) return obj[functionName].apply(obj, args);
if (this.noSuchMember) {
this.noSuchMember.apply(obj, [functionName, 'function'].concat(args));
}
},
object: function() { return this._object },
noSuchMember: null
};
var obj = new WrappingProxy({
testProp: 'test',
testFunc: function (v) {
return v;
}
},
//noSuchMember handler
function (name, type) {
console.log(name, type, arguments[2]);
}
);
obj.get('testProp'); //test
obj.get('nonExistingProperty'); //undefined, call noSuchMember
obj.invoke('testFunc', 'test'); //test
obj.invoke('nonExistingFunction', 'test'); //undefined, call noSuchMember
//accesing properties directly on the wrapped object is not monitored
obj.object().nonExistingProperty;
With the new defineProperties, defineGetter and defineSetter being added to javascript, you can do something somewhat similar. There is still no true way to hide the __properties__ of an object however. I suggest you see this article.
var obj = {
__properties__: {
a: 4
}
}
Object.defineProperties(obj, {
"b": { get: function () { return this.__properties__.a + 1; } },
"c": { get: function (x) { this.__properties__.a = x / 2; } }
});
obj.b // 2
obj.c // .5
This is the classic sort of model that should work in any environment
//lame example of a model
var Model = function(a) {
this.__properties__ = {a: a};
}
Model.prototype.get = function(item) {
//do processing
return this.__properties__[item];
}
Model.prototype.set = function(item, val) {
//do processing
this.__properties__[item] = val;
return this;
}
var model = new Model(5);
model.get("a") // => 5
As the other answers mentioned, at the moment there is no way to intercept undefined properties.
Would this be acceptable though?
var myObj = (function() {
var props = {
foo : 'foo'
}
return {
getProp : function(propName) { return (propName in props) ? props[propName] : 'Nuh-uh!' }
}
}());
console.log(myObj.getProp('foo')); // foo
console.log(myObj.getProp('bar')); // Nuh-uh

How can I establish if an object property has been directly assigned or inherited?

I have a constructor function with a default value:
var myObject = function(){
this.myValue = "default";
}
I then instantiate a new object:
var newInstance = new myObject();
How can I tell if myValue is the original default value, and has not been reassigned. I can't check the value, as it could be set to be the same, and that would produce a false positive.
Use Object.hasOwnProperty().
if (newInstance.hasOwnProperty('myValue')) {
// original default value
}
However, this won't be able to tell you whether or not this happened:
newInstance.myValue = 'default';
To detect that, you'll need to use something like getters/setters and track a private mutation flag:
function myObject() {
var _myValue = "default";
var _wasMutated = false;
return {
get myValue() {
return _myValue;
},
set myValue(x) {
_myValue = x;
_wasMutated = true;
},
get wasMutated() {
return _wasMutated;
}
}
}
// usage:
var newInstance = new myObject();
newInstance.myValue; // "default"
newInstance.wasMutated; // false
newInstance.myValue = "default";
newInstance.wasMutated; // true
newInstance.wasMutated = false; // writes have no effect since there's no setter
newInstance.wasMutated; // true
I can't check the value, as it could be set to be the same, and that would produce a false positive.
If you change your constructor to not set that value, and instead set it on the prototype:
var myObject = function(){};
myObject.prototype.myValue = "default";
...then you can use the .hasOwnProperty() method to test whether myValue has been overwritten even if it was set to the same value as the default.
if (!newInstance.hasOwnProperty("myValue")) {
// has not been assigned another value
}
This works because if you later say:
newInstance.myValue = "something";
...it will create a myValue property directly on the instance. And when you use newInstance .myValue it automatically retrieves the direct property if it exists or the inherited prototype property otherwise.
var newInstance = new myObject();
console.log( newInstance.myValue ); // "default"
console.log( newInstance.hasOwnProperty("myValue") ); // false
newInstance.myValue = "default";
console.log( newInstance.myValue ); // "default"
console.log( newInstance.hasOwnProperty("myValue") ); // true
There's no built-in way to accomplish what you're looking for, but it's easy enough to do with a "defaults" object:
var MyObject = function() {
this.myValue = this.defaults.myValue
}
MyObject.prototype.defaults = { myValue: 'default' };
MyObject.prototype.isDefault = function(prop) {
return this[prop]===this.defaults[prop];
}
var o = new MyObject();
o.isDefault('myValue'); // returns true
o.myValue = 'foo';
o.isDefault('myValue'); // returns false
o.myValue = 'default';
o.isDefault('myValue'); // returns true
On the other hand, if what you need to know is whether or not a property has ever been set (regardless of it's value), you'll have to use private properties and getter/setter functions:
var MyObject = function() {
var myValue = 'default'; // i'm private!
var myValueHasChanged = false;
this.myValue = function(newVal) {
if( arguments.length===0 ) {
return newVal;
} else {
myValue = newVal;
myValueHasChanged = true;
}
}
this.myValueHasChanged = function() {
return myValueHasChanged;
}
}
var o = new MyObject();
o.myValue(); // returns 'default'
o.myValueHasChanged(); // returns false
o.myValue('foo');
o.myValue(); // returns 'foo'
o.myValueHasChanged(); // returns true
o.myValue('default');
o.myValueHasChanged(); // returns true

Categories

Resources