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
Related
Given an object obj, I would like to define a read-only property 'prop' and set its value to val. Is this the proper way to do that?
Object.defineProperty( obj, 'prop', {
get: function () {
return val;
}
});
The result should be (for val = 'test'):
obj.prop; // 'test'
obj.prop = 'changed';
obj.prop; // still 'test' since it's read-only
This method works btw: http://jsfiddle.net/GHMjN/
I'm just unsure if this is the easiest / smoothest / most proper way to do it...
You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:
var obj = {};
Object.defineProperty(obj, "prop", {
value: "test",
writable: false
});
As mentioned in the comments, the writable option defaults to false so you can omit it in this case:
Object.defineProperty(obj, "prop", {
value: "test"
});
This is ECMAScript 5 so won't work in older browsers.
In new browsers or node.js it is possible to use Proxy to create read-only object.
var obj = {
prop: 'test'
}
obj = new Proxy(obj ,{
setProperty: function(target, key, value){
if(target.hasOwnProperty(key))
return target[key];
return target[key] = value;
},
get: function(target, key){
return target[key];
},
set: function(target, key, value){
return this.setProperty(target, key, value);
},
defineProperty: function (target, key, desc) {
return this.setProperty(target, key, desc.value);
},
deleteProperty: function(target, key) {
return false;
}
});
You can still assign new properties to that object, and they would be read-only as well.
Example
obj.prop
// > 'test'
obj.prop = 'changed';
obj.prop
// > 'test'
// New value
obj.myValue = 'foo';
obj.myValue = 'bar';
obj.myValue
// > 'foo'
In my case I needed an object where we can set its properties only once.
So I made it throw an error when somebody tries to change already set value.
class SetOnlyOnce {
#innerObj = {}; // private field, not accessible from outside
getCurrentPropertyName(){
const stack = new Error().stack; // probably not really performant method
const name = stack.match(/\[as (\w+)\]/)[1];
return name;
}
getValue(){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] === undefined){
throw new Error('No global param value set for property: ' + key);
}
return this.#innerObj[key];
}
setValue(value){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] !== undefined){
throw new Error('Changing global parameters is prohibited, as it easily leads to errors: ' + key)
}
this.#innerObj[key] = value;
}
}
class GlobalParams extends SetOnlyOnce {
get couchbaseBucket() { return this.getValue()}
set couchbaseBucket(value){ this.setValue(value)}
get elasticIndex() { return this.getValue()}
set elasticIndex(value){ this.setValue(value)}
}
const _globalParams = new GlobalParams();
_globalParams.couchbaseBucket = 'some-bucket';
_globalParams.elasticIndex = 'some-index';
console.log(_globalParams.couchbaseBucket)
console.log(_globalParams.elasticIndex)
_globalParams.elasticIndex = 'another-index'; // ERROR is thrown here
console.log(_globalParams.elasticIndex)
Because of the old browsers (backwards compatibility) I had to come up with accessor functions for properties. I made it part of bob.js:
var obj = { };
//declare read-only property.
bob.prop.namedProp(obj, 'name', 'Bob', true);
//declare read-write property.
bob.prop.namedProp(obj, 'age', 1);
//get values of properties.
console.log(bob.string.formatString('{0} is {1} years old.', obj.get_name(), obj.get_age()));
//set value of read-write property.
obj.set_age(2);
console.log(bob.string.formatString('Now {0} is {1} years old.', obj.get_name(), obj.get_age()));
//cannot set read-only property of obj. Next line would throw an error.
// obj.set_name('Rob');
//Output:
//========
// Bob is 1 years old.
// Now Bob is 2 years old.
I hope it helps.
I tried and it Works ...
element.readOnly = "readOnly" (then .readonly-> true)
element.readOnly = "" (then .readonly-> false)
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.
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
I am looking at adding the ability to watch a variable to my code base, and I found this answer which does almost everything I need. The code that the answer provided is the following:
console = console || {}; // just in case
console.watch = function(oObj, sProp) {
sPrivateProp = "$_"+sProp+"_$"; // to minimize the name clash risk
oObj[sPrivateProp] = oObj[sProp];
// overwrite with accessor
Object.defineProperty(oObj, sProp, {
get: function () {
return oObj[sPrivateProp];
},
set: function (value) {
//console.log("setting " + sProp + " to " + value);
debugger; // sets breakpoint
oObj[sPrivateProp] = value;
}
});
}
To 'watch' a variable, you would use: console.watch(obj, "someProp");
However, I would like to know if it possible to add a 'unwatch' method that would undo the above? If so, how can that be done?
console = console || {}; // just in case
//additional method use in watch or unwatch
console._defineProperty=function(oObj, sProp, watch){
sPrivateProp = "$_"+sProp+"_$"; // to minimize the name clash risk
oObj[sPrivateProp] = oObj[sProp];
// overwrite property
Object.defineProperty(oObj, sProp, {
get: function () {
return oObj[sPrivateProp];
},
set: function (value) {
if (watch)//if true then watching if false then not watch
console.log("setting " + sProp + " to " + value);
oObj[sPrivateProp] = value;
}
});
};
console.watch = function(oObj, sProp) {
this._defineProperty(oObj,sProp,true);
};
console.unwatch = function(oObj, sProp) {
this._defineProperty(oObj,sProp,false);
};
//USE CASE
var user={ name:"Tom"};
console.watch(user,"name");
user.name="Mike";//watching
user.name="Rafael";//watching
console.unwatch(user,"name");
user.name="John";//not wathing
user.name="Donatello";//not wathing
console.watch(user,"name");
user.name="Greg";//wathing
user.name="Ron";//wathing
Doing undo defineProperty is not possible so I am overriding property again but without additional not wanted behavior.
First, some fixes to the original to make it simpler and also to make it work when the property doesn’t already exist on the object:
console = console || {};
console.watch = function (obj, prop) {
var value_ = obj[prop];
Object.defineProperty(obj, prop, {
configurable: true,
get: function () {
return value_;
},
set: function (value) {
debugger; // sets breakpoint
value_ = value;
}
});
};
Then implementing unwatch is easy:
console.unwatch = function (obj, prop) {
var value = obj[prop];
delete obj[prop];
obj[prop] = value;
};
Is there a way to assign properties of one object as references to the properties of another, and do so dynamically? Note that in the for loop, I've skipped any property that has the same name as the second object. I'm working on a framework that will cache JSON as objects with behaviors and allow ORM kind of behavior, where I can grab cached objects and collections as properties of other cached objects. I need to skip certain properties to avoid circular reference.
var obj1 = {
prop1: "hey",
obj2:"you",
prop2: "come over here"
}
var obj2 = {}
for(var prop in obj1){
if(prop != 'obj2'){
obj2[prop] = obj1[prop];
}
}
console.log(obj1);
console.log(obj2);
obj1.prop2 = "come on, man";
console.log(obj1);
console.log(obj2);
//obj1 is unchanged in output. I would like to be able to update it by mutating obj2's properties
fiddle: http://jsfiddle.net/6ncasLb0/1/
If this is not possible, is it possible to remove or mutate a property of a reference without mutating the original object? I know, probably not. Just a shot in the dark.
I guess the closest you can get to it, is to make sure the property you are changing it the same property you are getting on both objects, so you would need to do some work to make sure they "know" each other when they are instantiated (eg, clone from the original object)
As an example, you could use a simplified model like this, any properties marked in its creation would also update the original object, though new properties defined on the object should be fine). Note that enumrating and just referencing the properties wouldn't work, at least not with strings (objects would change when copied from 1 object to another)
;
(function(namespace) {
function addProperty(obj, property, valueholder) {
Object.defineProperty(obj, property, {
get: function() {
return valueholder[property];
},
set: function(val) {
valueholder[property] = val;
},
enumerable: true,
configurable: false
});
}
var model = namespace.model || function(options) {
if (typeof options === 'undefined') {
options = {};
}
var propHolder = options.container || {},
prop;
if (typeof options.props != null) {
for (prop in options.props) {
if (options.props.hasOwnProperty(prop)) {
addProperty(this, prop, propHolder);
propHolder[prop] = options.props[prop];
}
}
};
namespace.model.prototype.clone = function() {
var options = {
props: {},
container: propHolder
},
prop;
for (prop in this) {
if (this.hasOwnProperty(prop)) {
options.props[prop] = this[prop];
}
}
return new namespace.model(options);
};
namespace.model.prototype.toString = function() {
var prop, msg, props = [];
for (prop in propHolder) {
if (propHolder.hasOwnProperty(prop)) {
props.push(prop + ': "' + this[prop].toString() + '"');
}
}
return '[Model] {' + props.join(', ') + '}';
}
return this;
};
namespace.model = model;
}(window));
var obj1 = new model({
props: {
prop2: "come over here"
}
});
obj1.prop1 = 'Hey';
obj1.obj2 = 'You';
obj1.test = {
a: 10
};
var obj2 = obj1.clone();
console.log('-- before changes --');
console.log(obj1.toString());
console.log(obj2.toString());
obj2.prop2 = "come on, man";
obj2.prop1 = "won't change";
obj2.obj2 = "also not";
obj2.test.b = "both have this now";
console.log('-- after changes --');
console.log(obj1.toString());
console.log(obj2.toString());
console.log(obj1.test);
console.log(obj2.test);