Reputation: 8745
Going through Javascript documentation, I found the following two functions on a Javascript object looks interesting:
.watch
- Watches for a property to be assigned a value and runs a function when that occurs.
.unwatch
- Removes a watchpoint set with the watch method.
UPDATE: Deprecation warning
Do not usewatch()
andunwatch()
! These two methods were implemented only in Firefox prior to version58
, they're deprecated and removed in Firefox58+
Sample usage:
o = { p: 1 };
o.watch("p", function (id,oldval,newval) {
console.log("o." + id + " changed from " + oldval + " to " + newval)
return newval;
});
Whenever we change the property value of "p", this function gets triggered.
o.p = 2; //logs: "o.p changed from 1 to 2"
I am working on Javascript for the past few years and never used these functions.
Can someone please throw some good use cases where these functions will come in handy?
Upvotes: 46
Views: 63731
Reputation: 4647
As far as i can read, no answer relates to the OP's question:
Can someone please throw some good use cases where these functions will come in handy?
My use case: Imagine an object in an existing library that receives an options
-object on instantiation, which is also available after it was created. Some of these config values can be altered during lifetime of that object, and on calling specific methods the next time, those will use these new config values.
However, now i have found an option that only has an effect if supplied during instantiation, i.e. b/c it will register certain event listeners – changing it afterwards updates the value but doesn't change the behavior. But i really need to turn this behavior on or off during runtime. Lets call that option doSomethingOnKeyDown
for the sake of the argument.
How can i add a pull request to that library that doesn't fundamentally change the API?
I build a proxy:
// … we're somewhere in the constructor
this.options = new Proxy(this.options, {
set: function (target, key, value) {
if (key == 'doSomethingOnKeyDown' && value !== target[key]) {
value === true
? window.addEventListener('keydown', keydownHandler)
: window.removeEventListener('keydown', keydownHandler)
}
target[key] = value // default behavior: store the value
return true // Indicate success
}
})
Et voilá: now anyone can change this option on the object during runtime, i.e. myInstance.options.doSomethingOnKeyDown = false
and it will update its behavior accordingly – and the API of the object stays the same.
Upvotes: 0
Reputation: 1
Instead of using Proxy and therefore not "listening" directly on the original object you can use something like this:
const obj = {};
obj.state = {
isLoaded: false,
isOpen: false,
};
obj.setState = (newState) => {
// Before using obj.setState => result Object { isLoaded: false, isOpen: false }
console.log(obj.state);
obj.state = { ...obj.state, ...newState };
// After using obj.setState ex. obj.setState({new:''}) => result Object { isLoaded: false, isOpen: false, new: "" }
console.log(obj.state);
};
That approach is more or less how it would work in ReactJS, you have source object in this example obj.state and setter obj.setState
You don't have to put both in one object like I did but seems like a good way to organize things
Upvotes: 0
Reputation: 3721
Here's a simple alternative to watch/unwatch for an object literal using just a getter/setter. Whenever the p
property is changed, any function can be called.
var o = {
_p: 0,
get p() {
return this._p;
},
set p(p){
console.log(`Changing p from ${this._p} to ${p}`);
this._p = p;
return this._p;
}
}
o.p = 4;
o.p = 5;
Upvotes: 3
Reputation: 1763
remove Promise and keep callback only if Promise is not supported in your target browser
1) Be aware of async behaviour on using promise.
2) Object.defineProperty doesn't trigger the callback, only assign operator '=' does
Object.onPropertySet = function onPropertySet(obj, prop, ...callback_or_once){
let callback, once;
for(let arg of callback_or_once){
switch(typeof arg){
case "function": callback = arg; break;
case "boolean": once = arg; break;
}
}
let inner_value = obj[prop];
let p = new Promise(resolve => Object.defineProperty(obj, prop, {
configurable: true,
// enumerable: true,
get(){ return inner_value; },
set(v){
inner_value = v;
if(once){
Object.defineProperty(obj, prop, {
configurable: true,
// enumerable: true,
value: v,
writable: true,
});
}
(callback || resolve)(v);
}
}));
if(!callback) return p;
};
// usage
let a = {};
function sayHiValue(v){ console.log(`Hi "${v}"`); return v; }
// do
Object.onPropertySet(a, "b", sayHiValue);
a.b = 2; // Hi "2"
a.b = 5; // Hi "5"
// or
Object.onPropertySet(a, "c", true).then(sayHiValue).then(v => {
console.log(a.c); // 4 // because a.c is set immediatly after a.c = 3
console.log(v); // 3 // very important: v != a.c if a.c is reassigned immediatly
a.c = 2; // property "c" of object "a" is re-assignable by '=' operator
console.log(a.c === 2); // true
});
a.c = 3; // Hi "3"
a.c = 4; // (Nothing)
Upvotes: 2
Reputation: 17651
It's now 2018 and the answers to this question are a bit outdated:
Today, you can now use the Proxy object to monitor (and intercept) changes made to an object. It is purpose built for what the OP is trying to do. Here's a basic example:
var targetObj = {};
var targetProxy = new Proxy(targetObj, {
set: function (target, key, value) {
console.log(`${key} set to ${value}`);
target[key] = value;
return true;
}
});
targetProxy.hello_world = "test"; // console: 'hello_world set to test'
The only drawbacks of the Proxy
object are:
Proxy
object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy
functionality.Date
) -- the Proxy
object is best paired with plain Objects or Arrays.If you need to observe changes made to a nested object, then you need to use a specialized library such as Observable Slim (which I authored). It works like this:
var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
console.log(JSON.stringify(changes));
});
p.testing.blah = 42; // console: [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]
Upvotes: 59
Reputation: 157
You can use setInterval
Object.prototype.startWatch = function (onWatch) {
var self = this;
if (!self.watchTask) {
self.oldValues = [];
for (var propName in self) {
self.oldValues[propName] = self[propName];
}
self.watchTask = setInterval(function () {
for (var propName in self) {
var propValue = self[propName];
if (typeof (propValue) != 'function') {
var oldValue = self.oldValues[propName];
if (propValue != oldValue) {
self.oldValues[propName] = propValue;
onWatch({ obj: self, propName: propName, oldValue: oldValue, newValue: propValue });
}
}
}
}, 1);
}
}
var o = { a: 1, b: 2 };
o.startWatch(function (e) {
console.log("property changed: " + e.propName);
console.log("old value: " + e.oldValue);
console.log("new value: " + e.newValue);
});
Upvotes: -2
Reputation: 69
You could take a look at the Javascript Propery Events library. It's a small library extending Object.defineProperty
with some event callers, that I made recently. It adds a few on[event]
properties that can be used like the on[event]
properties of HTML-Objects. It also has a simple type check, which calls the onerror
event if it fails.
Taking your code it would result in something like this:
var o = {}
Object.defineProperty(o, "p", {
value:1,
writable:true,
onchange:function(e){
console.log("o." + e.target + " changed from " + e.previousValue + " to " + e.returnValue);
}
})
Upvotes: 2
Reputation: 2920
Check out Object.defineProperty
and
Object.prototype.\__defineGetter__
(or \__defineSetter__
) to see where this functionality is heading.
Object.defineProperty
should be available in all contemporary browsers real soon now.
Upvotes: 7
Reputation: 85145
What watch is really designed for is validation of property values. For example you could validate that something is an integer:
obj.watch('count', function(id, oldval, newval) {
var val = parseInt(newval, 10);
if(isNaN(val)) return oldval;
return val;
});
You could use it to validate string length:
obj.watch('name', function(id, oldval, newval) {
return newval.substr(0, 20);
});
However, these are only available in the latest versions of the SpiderMonkey javascript engine. Great if you are using Jaxer or embedding the SpiderMonkey engine, but not really available in your browser yet (unless you are using FF3).
Upvotes: 11