Design for a Watch tool in Javascript - javascript

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;
};

Related

Run a function when deep property is set

I have an object like
const obj = { field1: obj1, field2: obj2 }
and now I'd like to run a function when anything in obj was changed:
function objChanged() { ... }
// decorate obj somehow ...
obj.field3 = data; // objChanged should be called (Proxy can see it)
obj.field1.val = data; //objChanged should be called (Proxy can't see it?)
AFAIK there is a MutationObserver which works only for DOM and Proxy which intercepts only own properties, right?
I do not own obj1 so I can not change it. Is there a way to achieve this functionality?
Following the piece of code will listen to object property you can iterate over object properties to listen all. I am curious, what are you trying to achieve?
const dog = { bark: true };
function Observer(o, property) {
var _this = this;
this.observers = [];
this.Observe = function (notifyCallback) {
_this.observers.push(notifyCallback);
};
Object.defineProperty(o, property, {
set: function (val) {
_this.value = val;
for (var i = 0; i < _this.observers.length; i++) {
_this.observers[i](val);
}
},
get: function () {
return _this.value;
},
});
}
const observer = new Observer(dog, "bark");
observer.Observe(function (value) {
l("Barked");
});
dog.bark = true;
dog.bark = true;
dog.bark = true;
dog.bark = true;
Orgil's answer works only with a single property that needs to be known and encoded. I wanted a solution which works for all properties, including later added. Inspired by his idea to create an observing object, I created a dynamic Proxy that adds another Proxies when needed.
In the following code dog1 serves as proxy: setting its properties modifies the original dog object and logs the assigned value to console.
function AssignProxy(o, fn, path) {
var tree = {};
if(!path) path = "obj";
return new Proxy(o, {
get: (_, prop) => {
if(typeof o[prop] != "object") return o[prop];
if(tree[prop] === undefined) tree[prop] = AssignProxy(o[prop], fn, `${path}.${prop}`);
return tree[prop];
},
set: (_, prop, val) => fn(o[prop] = val, prop, o, path) || 1
});
}
/****** TEST *******/
const dog = {
sounds: {},
name: "Spike"
};
let callback = (val, prop, o, path) => console.log(`assigning ${path}.${prop} to ${val}`)
const dog1 = AssignProxy(dog, callback, "dog1");
dog1.name = "Tyke"; // overwrite property
dog1.age = 4; // create a property
dog1.sounds.howl = "hoooooowl"; // create a deep property
dog1.sounds.howl = {text: "hoowl", pitch: 5000}; // overwrite the deep property
var howl = dog1.sounds.howl; // access by reference
howl.pitch = 6000; // overwrite later added property
console.log(dog); // verify the original object

Sync Objects to changed parameters

I want to bind the parameters of an Object to another Object, so they update whenever the other one updates aswell.
Object1 =
x: 1
Object2 =
x: Object1.x
y: 0
so that Object1.x = 2; updates Object2 aswell. (And the other way round)
How do I do that efficiently? I could use .watch and update the other one on each change, but I doubt that is a smart solution. Am I missing something?
Depending on the environment this has to run, a getter might be a suitable solution:
Object.defineProperty(Object2, 'x', {
get: function() {
return Object1.x;
},
enumerable: true
});
You might also want to define a setter to sync changes back.
You need to implement the observer/observable pattern.
I could use .watch and update the other one on each change, but I
doubt that is a smart solution
How you notify the other part if something changes? Why isn't a smart solution? It's just the solution!
Check this code listing I did to show you a possible implementation of objects capable of listening changes of other objects creating a base ObservableObject prototype (also available in JSFiddle!):
var ObservableObject = function () {
this._handlers = [];
this._disablePropertyChangeNotification = false;
};
ObservableObject.prototype = {
get disablePropertyChangeNotification() {
return this._disablePropertyChangeNotification;
},
set disablePropertyChangeNotification(value) {
this._disablePropertyChangeNotification = value;
},
listenPropertyChange: function (handler) {
this._handlers.push(handler);
},
notifyPropertyChange: function (propertyName) {
if (!this.disablePropertyChangeNotification) {
this._handlers.forEach(function (handler) {
handler(propertyName);
});
}
},
};
var A = function () {};
A.prototype = new ObservableObject();
Object.defineProperty(A.prototype, "name", {
get: function () {
return this._name;
},
set: function (value) {
this._name = value;
this.notifyPropertyChange("name");
}
});
var B = function () {};
B.prototype = new ObservableObject();
Object.defineProperty(B.prototype, "name", {
get: function () {
return this._name;
},
set: function (value) {
this._name = value;
this.notifyPropertyChange("name");
}
});
var someObjectA = new A();
var someObjectB = new B();
someObjectA.listenPropertyChange(function (propertyName) {
// This will prevent an infinite loop where
// property from A is set by B and viceversa
someObjectA.disablePropertyChangeNotification = true;
someObjectB[propertyName] = someObjectA[propertyName];
someObjectA.disablePropertyChangeNotification = false;
});
someObjectB.listenPropertyChange(function (propertyName) {
// This will prevent an infinite loop where
// property from A is set by B and viceversa
someObjectB.disablePropertyChangeNotification = true;
someObjectA[propertyName] = someObjectB[propertyName];
someObjectB.disablePropertyChangeNotification = false;
});
// We set name on A instance, and we print B instance name value
someObjectA.name = "hello world";
$(document.body).append("<p>someObjectB.name: " + someObjectB.name + "</p>");
// We set name on B instance, and we print A instance name value
someObjectB.name = "hello world changed";
$(document.body).append("<p>someObjectA.name: " + someObjectA.name + "</p>");

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 to check if an object has been modified

I have an object, for example:
var o = {
a: 1
};
The user does:
o.a = 2;
How can I know if the o object has been modified? I cannot touch the o object so I cannot use Object.defineProperties().
Since you are in the node.js environment and thus don't have to care about crappy old JavaScript engines (i.e. old browsers) you can use Object.defineProperty() to define properties with accessor functions. This allows you to execute a custom function whenever a certain property is read/written - so you can simply log the write and e.g. store it in a separate property.
var o = {};
Object.defineProperty(o, 'a', {
get: function() {
return this.__a;
},
set: function(value) {
this.__a = value;
this.__a_changed = true;
}
});
o.__a = 1;
Whenever a value is assigned to o.a the __a_changed property will be set. Of course it would be even cleaner to execute whatever you want to do on change right in the set function - but it obviously depends on your code if you can do so in a useful way.
The easiest thing would obviously be to just check if the value is different than what you initialized it as. Another option would be to use Object.defineProperty.
var o = {};
var aValue = 2;
Object.defineProperty(o, "a", {
get: function() { return aValue; },
set: function(newValue) {
aValue = newValue;
// trigger event or set flag that it was changed
}
});
You could always wrap o with your own setter getter if the defineProperty doesn't do the job for you.
var myO = {
o: o, // original o
changed: false,
set: function (prop, val) {
this.o[prop] = val;
this[prop + "IsChanged"] = true;
},
get: function (prop) {
return this.o[prop];
}
};
myO.set("a", 2);
console.log(myO.aIsChanged); // true
A better solution would be to execute an event using something like Backbone.Events to exeute someting myO.on("change", function () { ... })
var myO = {
o: o, // original o
changed: false,
set: function (prop, val) {
this.o[prop] = val;
this.trigger("change", prop, val);
},
get: function (prop) {
return this.o[prop];
}
};
_.extend(myO, Backbone.events);
myO.on("change", function (prop, val) {
// Do stuff with the change
});
myO.set("a", 1);
I'm using the Underscore library's extend method here, fyi.
var o_backup = o.a;
if(o.a != o_backup){
// function to execute
}
Basically, you are just creating a variable which will save the a from the object o and then you're checking if it has been modified or not.
Also, you can do a setInterval() function if you want to check if it has been modified more than once.

object.watch(), getting new value

In Firefox, I've got several objects that I need to trigger an event when a particular property of each is changed. I'm using object.watch(), however when I return the value of the property that was changed using "this", it returns the old value the first time, and "undefined" the second and subsequent times:
var myObject = {
"aProperty": 1
};
function propChanged(prop) {
alert(prop);
}
myObject.watch("aProperty", function () {
propChanged(this.aProperty);
});
myObject.aProperty = 2;//alerts "1"
myObject.aProperty = 3;//alerts "undefined"
The reason I can't just say alert(myObject.aProperty) is because this is meant to be a dynamic code that will apply the event handler to several, possibly unknown objects.
I'm just unsure exactly how to dynamically get the new value of the property using the watch method. I'm setting up a prototype for IE for this, so I'm not worried about it not working there. I just need to understand "this" and how it applies to the watch method's owner.
Edit>>
Here's the new code I'm using for cross browser, including the IE et al prototype:
var myObject = {};
if (!Object.prototype.watch) {
Object.prototype.watch = function (prop, handler) {
var oldval = this[prop], newval = oldval,
getter = function () {
return newval;
},
setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
};
if (delete this[prop]) { // can't watch constants
if (Object.defineProperty) // ECMAScript 5
Object.defineProperty(this, prop, {
get: getter,
set: setter
});
else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
Object.prototype.__defineGetter__.call(this, prop, getter);
Object.prototype.__defineSetter__.call(this, prop, setter);
}
}
};
}
if (!Object.prototype.unwatch) {
Object.prototype.unwatch = function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
};
}
function propChanged(t, p, o, n) {
alert(o);
}
Object.defineProperty(myObject, "aProperty", {value: 2,
writable: true,
enumerable: true,
configurable: true});
myObject.watch("aProperty", propChanged);
myObject.aProperty = 3; //alerts 3
myObject.aProperty = 4; //alerts 4 (n is undefined in propChanged?
You need to return the value you want the property to have from the function you pass to watch.
myObject.watch("aProperty", function (prop, oldval, newval) {
propChanged(newVal);
return newVal;
});
should do it.
See the MDN docs for a full detail of the function but the relevant bit is
Watches for assignment to a property named prop in this object, calling handler(prop, oldval, newval) whenever prop is set and storing the return value in that property. A watchpoint can filter (or nullify) the value assignment, by returning a modified newval (or by returning oldval).
EDIT
Your edited code might work better this way
Object.prototype.watch = function (prop, handler) {
var fromPrototype = !Object.hasOwnProperty.call(this, prop),
val = this[prop],
getter = function () {
return fromPrototype ? Object.getPrototypeOf(this)[prop] : val;
},
setter = function (newval) {
fromPrototype = false;
return val = handler.call(this, prop, val, newval);
};
if (delete this[prop]) { // can't watch constants
if (Object.defineProperty) { // ECMAScript 5
Object.defineProperty(this, prop, {
get: getter,
set: setter,
configurable: true,
enumerable: true
});
} else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
Object.prototype.__defineGetter__.call(this, prop, getter);
Object.prototype.__defineSetter__.call(this, prop, setter);
}
}
};

Categories

Resources