How to recursively trap all changes to an object? - javascript

I'm using a Proxy to detect when an object is modified (and then I save it to disk). This works great for simple properties of the proxied object, but fails on modification of object properties.
var obj = {
p1 = "Hello",
a1 = []
}
var dirtyHandler = {
set: function(obj, prop, value) {
markDirty(obj);
obj[prop] = value;
return true;
}
};
var proxied = new Proxy(obj, dirtyHandler);
proxied.p1 = "World"; // <-- proxy detects modification
proxied.a1.push({'foo': 3}); // <-- proxy does not detect modification
Does anyone know how to recursively detect any modification in my object (a1.push(...), a1[0].foo = 4, etc.)?

Here's how I ended up solving this for my use case.
First I add a proxy for all the known objects I care about (not shown). In the handler for the proxy on every set call I check if the value is already a proxy and if not, substitute one:
var DirtyHandler = function(root) {
this.root = root;
this.set = (obj, prop, value) => {
if (!dirtyIgnores[prop]) {
debug('Dirty: ' + prop + ' of ' + obj.commitId);
markDirty(this.root);
}
if (value && typeof value === 'object') {
value = new Proxy(value, this);
}
obj[prop] = value;
return true;
};
}

I published a library on GitHub (Observable Slim) that recursively iterates through a target object and applies a Proxy on all objects. It enables you to monitor all changes that occur under a single target object no matter how deeply nested they are. It also has a few extra features:
Reports back to a specified callback whenever changes occur.
Will prevent user from trying to Proxy a Proxy.
Keeps a store of which objects have been proxied and will re-use existing proxies instead of creating new ones (very significant performance implications).
Allows user to traverse up from a child object and retrieve the parent.
Written in ES5 and plays nice with the Proxy Polyfill so it can be deployed in older browsers fairly easily.
Please feel free to take a look and hopefully contribute as well!

Related

trying to set Proxy trap for a nested object. succeeded intercession except array.unshift and array.splice and some others

i am trying to make a Proxy trap for a nested object (kinda trying to implement vuex reactive).
kinda succeeded in setting trap following this post:
How to use javascript proxy for nested objects
my code looks like this:
//inside function
const isProxy = Symbol("isProxy");
const handler: ProxyHandler<StateObj> = {
get: (target, name, receiver) => {
if (name === isProxy) return true;
const prop = Reflect.get(target, name);
if (typeof prop == undefined) return;
this.track(target, name); // track the component that "get" object.
if (!prop.isProxy && typeof prop == "object") {
return new Proxy(prop, handler);
}
return prop;
},
set: (target, name, value, receiver) => {
if (target[name] == value) return true;
this.trigger(target, name); //set the trigger for the "tracked" object
Reflect.set(target, name, value);
return true;
},
apply(target: StateObj, thisArg: any, argArray: any[]): any {
}
};
return new Proxy(state, handler);
please dont mind with the track and trigger funcs which are the reactive thing used in vues.
this code was successful for all kinds of changes to the original object. the trap worked quiet well.
but the problem is, when i try to use Array.prototype.unshift or Array.prototype.splice to an array inside the proxied object, it doesn't work.
I figured out the reason why the error occurs, which is that Array.prototype.unshift or Array.prototype.splice methods directly assigns [Get] slot results of the original indexes of the array to the [Set] call, so when the [Set] trap is activated, it assigns a proxied array not the original 'target' property of the object
You can find the Prototype method specification in the ECMAscript document
1. Let fromValue be ? Get(O, from).
2. Perform ? Set(O, to, fromValue, true).
so when i assign a plain object or value to the object, the trap works just fine, but when i assign an object or value i did 'get' from the object which activates the proxy [get] trap, it fails with this error:
TypeError: Cannot read properties of undefined(reading 'isProxy')
which i think means that the object is proxied (not sure).
could you please suggest on any ways i can trap the behavior of unshift, splice or direct assignments like state[i] = state[i+1]?

Node.js garbage collection and synchronized objects

This is a little bit tricky to explain, but I'll give it a try:
In a node.js server application I would like to deal with data objects that can be used in more than one place at once. The main problem is, that these objects are only referred to by an object id and are loaded from the database.
However, as soon as an object is already loaded into one scope, it should not be loaded a second time when requested, but instead the same object should be returned.
This leads me to the question of garbage collection: As soon as an object is no longer needed in any scope, it should be released completely to prevent having the whole database in the server's memory all the time. But here starts the problem:
There are two ways I can think of to create such a scenario: Either use a global object reference (which prevents any object from being collected) or, really duplicate these objects but synchronize them in a way that each time a property in one scope gets changed, inform the other instances about that change.
Again, therefore each instance would have to register an event handler, which in turn is pointing back to that instance thus preventing it from being collected again.
Did anyone come up with a solution for such a scenario I just didn't realize? Or is there any misconception in my understanding of the garbage collector?
What I want to avoid is manual reference counting for every object in the memory. Everytime when an object is being removed from any collection, I would have to adapt the reference count manually (there is even no destructor or "reference decreased" event in js)
Using the weak module, I implemented a WeakMapObj that works like we originally wanted WeakMap to work. It allows you to use a primitive for the key and an object for the data and the data is retained with a weak reference. And, it automatically removes items from the map when their data is GCed. It turned out to be fairly simple.
const weak = require('weak');
class WeakMapObj {
constructor(iterable) {
this._map = new Map();
if (iterable) {
for (let array of iterable) {
this.set(array[0], array[1]);
}
}
}
set(key, obj) {
if (typeof obj === "object") {
let ref = weak(obj, this.delete.bind(this, key));
this._map.set(key, ref);
} else {
// not an object, can just use regular method
this._map.set(key, obj);
}
}
// get the actual object reference, not just the proxy
get(key) {
let obj = this._map.get(key);
if (obj) {
return weak.get(obj);
} else {
return obj;
}
}
has(key) {
return this._map.has(key);
}
clear() {
return this._map.clear();
}
delete(key) {
return this._map.delete(key);
}
}
I was able to test it in a test app and confirm that it works as expected when the garbage collector runs. FYI, just making one or two objects eligible for garbage collection did not cause the garbage collector to run in my test app. I had to forcefully call the garbage collector to see the effect. I assume that would not be an issue in a real app. The GC will run when it needs to (which may only run when there's a reasonable amount of work to do).
You can use this more generic implementation as the core of your object cache where an item will stay in the WeakMapObj only until it is no longer referenced elsewhere.
Here's an implementation that keeps the map entirely private so it cannot be accessed from outside of the WeakMapObj methods.
const weak = require('weak');
function WeakMapObj(iterable) {
// private instance data
const map = new Map();
this.set = function(key, obj) {
if (typeof obj === "object") {
// replace obj with a weak reference
obj = weak(obj, this.delete.bind(this, key));
}
map.set(key, obj);
}
// add methods that have access to "private" map
this.get = function(key) {
let obj = map.get(key);
if (obj) {
obj = weak.get(obj);
}
return obj;
}
this.has = function(key) {
return map.has(key);
}
this.clear = function() {
return map.clear();
}
this.delete = function(key) {
return map.delete(key);
}
// constructor implementation
if (iterable) {
for (let array of iterable) {
this.set(array[0], array[1]);
}
}
}
Sounds like a job for a Map object used as a cache storing the object as the value (along with a count) and the ID as the key. When you want an object, you first look up its ID in the Map. If it's found there, you use the returned object (which will be shared by all). If it's not found there, you fetch it from the database and insert it into the Map (for others to find).
Then, to make it so that the Map doesn't grow forever, the code that fetches something from the Map would also need to release an object from the Map. When the useCnt goes to zero upon a release, you would remove an object from the Map.
This can be made entirely transparent to the caller by creating some sort of cache object that contains the Map and has methods for getting an object or releasing an object and it would be entirely responsible for maintaining the refCnt on each object in the Map.
Note: you will likely have to write the code that fetches it from the DB and inserts it into the Map carefully in order to not create a race condition because the fetching form the database is likely asynchronous and you could get multiple callers all not finding it in the Map and all in the process of getting it from the database. How to avoid that race condition depends upon the exact database you have and how you're using it. One possibility is for the first caller to insert a place holder in the Map so subsequent callers will know to wait for some promise to resolve before the object is inserted in the Map and available to them to use.
Here's a general idea for how such an ObjCache could work. You call cache.get(id) when you want to retrieve an item. This always returns a promise that resolves to the object (or rejects if there's an error getting it from the DB). If the object is in the cache already, the promise it returns will be already resolved. If the object is not in the cache yet, the promise will resolve when it has been fetched from the DB. This works even when multiple parts of your code request an object that is "in the process" of being fetched from the DB. They all get the same promise that is resolved with the same object when the object has been retrieved from the DB. Every call to cache.get(id) increases the refCnt for that object in the cache.
You then call cache.release(id) when a given piece of code is done with an object. That will decrement the internal refCnt and remove the object from the cache if the refCnt hits zero.
class ObjCache() {
constructor() {
this.cache = new Map();
}
get(id) {
let cacheItem = this.cache.get(id);
if (cacheItem) {
++cacheItem.refCnt;
if (cacheItem.obj) {
// already have the object
return Promise.resolve(cacheItem.obj);
}
else {
// object is pending, return the promise
return cacheItem.promise;
}
} else {
// not in the cache yet
let cacheItem = {refCnt: 1, promise: null, obj: null};
let p = myDB.get(id).then(function(obj) {
// replace placeholder promise with actual object
cacheItem.obj = obj;
cacheItem.promise = null;
return obj;
});
// set placeholder as promise for others to find
cacheItem.promise = p;
this.cache.set(id, cacheItem);
return p;
}
}
release(id) {
let cacheItem = this.cache.get(id);
if (cacheItem) {
if (--cacheItem.refCnt === 0) {
this.cache.delete(id);
}
}
}
}
Ok, for anyone who faces similar problems, I found a solution. jfriend00 pushed me towards this solution by mentioning WeakMaps which were not exactly the solution themselves, but pointed my focus on weak references.
There is an npm module simply called weak that will do the trick. It holds a weak reference to an object and safely returns an empty object once the object was garbage collected (thus, there is a way to identify a collected object).
So I created a class called WeakCache using a DataObject:
class DataObject{
constructor( objectID ){
this.objectID = objectID;
this.dataLoaded = new Promise(function(resolve, reject){
loadTheDataFromTheDatabase(function(data, error){ // some pseudo db call
if (error)
{
reject(error);
return;
}
resolve(data);
});
});
}
loadData(){
return this.dataLoaded;
}
}
class WeakCache{
constructor(){
this.cache = {};
}
getDataObjectAsync( objectID, onObjectReceived ){
if (this.cache[objectID] === undefined || this.cache[objectID].loadData === undefined){ // object was not cached yet or dereferenced, recreate it
this.cache[objectID] = weak(new DataObject( objectID )function(){
// Remove the reference from the cache when it got collected anyway
delete this.cache[this.objectID];
}.bind({cache:this, objectID:objectID});
}
this.cache[objectID].loadData().then(onObjectReceived);
}
}
This class is still in progress but at least this is a way how it could work. The only downside to this (but this is true for all database-based data, pun alert!, therefore not such a big deal), is that all data access has to be asynchronous.
What will happen here, is that the cache at some point may hold an empty reference to every possible object id.

How to get proxy's handler from proxy object?

For example, if I have this handler/proxy (from the MDN example)...
var handler = {
get: function(target, name){
return name in target?
target[name] :
37;
}
};
var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
is it possible to probe the proxy, p, in some way that allows me to get the handler object back.
Something along the lines of:
p.__handler__ // returns handler object -> Object {get: handler.get(), set: handler.set(), ...}
p.__handler__.get // returns get prop/fn of handler -> function(target, name){ ...}
Obviously, the various traps set up in the handler are still "known" to the proxy, but is there a clear-cut way to return them/ the handler from the proxy itself? If so, how?
I have no specific use-case for this at the moment, but I could see this being useful if you wanted to dynamically change a handler/traps after you already have a proxy.
ECMAScript provides no way to access the internal [[ProxyHandler]] nor [[ProxyTarget]] slots.
Some implementations may provide some non-standard ways, but don't take it for granted.
For example, on Firefox privileged code, you can know if an object is a proxy using
Components.utils.isProxy(object);
I proposed implementing similar methods to expose the [[ProxyHandler]] and [[ProxyTarget]]. They told me to implement them in Debugger.Object instead of Components.utils.
When the patch lands, it will be possible to use something like
Components.utils.import('resource://gre/modules/jsdebugger.jsm');
var Cc = Components.classes;
// Add a debugger to a new global
var global = new Components.utils.Sandbox(
Cc["#mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal),
{ freshZone: true }
);
addDebuggerToGlobal(global);
var dbg = new global.Debugger().addDebuggee(this);
// Create a debugger object of your object, and run proxy getters
var dbgObj = dbg.makeDebuggeeValue(object);
if(dbgObj.isProxy) { // a boolean
dbgObj.proxyHandler.unsafeDereference(); // the [[ProxyHandler]]
dbgObj.proxyTarget.unsafeDereference(); // the [[ProxyTarget]]
}
Add a "special" self descriptor property to getOwnPropertyDescriptor
const target = {
//Fns ..
//Props ...
};
const handler = {
getOwnPropertyDescriptor(target, prop) {
if(prop == "[[handler]]"){
return { configurable: true, enumerable: true, value: this };
}
return undefined;
},
prop1: 'abcd'
};
const proxy = new Proxy(target, handler);
console.log(Object.getOwnPropertyDescriptor(proxy, '[[handler]]').value.prop1);
I could see this being useful if you wanted to dynamically change a handler/traps after you already have a proxy
If you just want to add handlers over the (proxied) object you already have access to: you could achieve this by creating a new Proxy that handles the specific traps you want to change, eg:
let newProxyWithDifferentGet = new Proxy(originalProxy, {
get: (target, key){ ... }
}
If you wanted to access the original Proxy's target:
If you are the original Proxy's author, you can just do something like this when you construct it:
let openedProxy = new Proxy(Object.assign(target, {
_originalHandler: handler,
_originalTarget: target
}), handler)
If you're not the author, then whether or not that original target should be available to users is the decision of whoever wrote that original Proxy. If you disagree with that author about their encapsulation, that's a social problem, not a technical one, and this is not specific or unique to ES6's Proxies. If you're consuming open-source code, send a PR upstream explaining why you think the original target should be available to users, or just fork their code with your changes and use that, merging their updates to the original repo as you go.

Creating a backup of 'this'

In javascript, I have an object (think of it as a shape), that can be put in edit mode and edited, or a not editable mode. When editable mode, I want to have a cancel button that cancels all edits and returns the shape back to its original form. I was hoping to use something like the following, but assigning things to 'this' doesn't work. What would the best way to do this be? I would prefer not to use external objects to store backups, because there could be many shapes and sorting out which backup corresponds to what adds code that is not as nicely packaged.
Shape.prototype.edit = function() {
this.backup = this;
...
}
Shape.prototype.cancelEdit = function() {
this = this.backup;
...
}
I think Shape should contain properties object, for example this.properties. In that object you should store all information about shape (it will be something like shape's model, only data, without any methods, or other internal class data). And in backup function you should backup only properties, not all shape's object.
(I'm a non native english speaker, feel free to correct my message if need)
You could implement something like this, where you go through each key in the object and if it's a property and not a function then store in a backup array.
var backup ;
function backup()
{
backup = [];
for(var key in this) {
if(this.hasOwnProperty(key) && typeof this[key] !== 'function') {
backup[key] = this[key];
}
}
}
function restore()
{
for(var key in backup) {
this[key] = backup[key];
}
}

KendoUI data- attribute event handlers and 'this' scope

It seems that kendo's unobtrusive-javascript style event calls break this in my method context.
Say I have an object Foo, instantiated as bar = new Foo()
function Foo(){};
Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
alert(this.name);
};
bar = new Foo();
And attach the event using data-click for example
<a data-role="button" data-click="bar.doSomething">Click Me</a>
Object context to bar is replaced (not sure why, since we have the convenient element container.) and so this.name is undefined.
I've tried the old var self = this; in the object constructor, but it's not working, does anyone know what the best way to solve this is?
Update : Hacky Workaround
Since I really don't want to lose the benefits of wrapping up my modules as classes, I've created event call functions wrappers, which then call the methods on the appropriate object.
For example, connect the markup to a wrapper-function.
<a data-role="button" data-click="doSomething">Click Me</a>
and the wrapper function just calls the object.method.
function doSomething(e){ bar.doSomething(e) };  
Now, this achieves the intended result, but it's quite horrible, each and every event called from markup must have a proxy function like the one above. So just imagine a scenario where you have 300 events... and you'll instantly see why this is horrible.
If there's no other solution, and I dearly hope there is. I'll post this workaround as an answer, but as far as I'm concerned, it's far from desirable.
Footnote
I'll be completely honest this seems like major architectural flaw in Kendo, since this method of calling events from markup is "the kendo way." Obviously it can't be patched out, because there's probably a fair bit of code already dealing with this as a reference to the html element.
Being able to override it, or being able to route these event calls through a generic handler which can pass the call on, essentially a generic proxy function, are possible ways this could be dealt with. It could also be a simple configurable value on the kendo. object.
Theoretical Solution
I'll post follow-up if this works, in theory it's possible to throw events at a generic proxy, and have it call the properly scoped function.
Say we use the event attribute to call the proxy and then create a separate attribute to convey the object/method call. For example.
<a data-role="button" data-click="prox" data-prox="o.eventHandler">Click Me</a>
The proxy function would pull prox from the attribute dataset:
method - using eval
Not because I'm evil, but needs must.
// sitting in global namespace
function prox(e){
var p = e.sender.element.data['prox'];
// make sure our delegate is a function.
if("function" == eval("typeof "+p)) {
eval(p + "(e)");
}
}
Obviously I'd like a better way to do this but, at least it's DRY.
(I'll cook a non-eval method in a moment...)
Begone Eval...
let's use the window context to locate the object / method.
function prox(e) {
var p = e.sender.element.data['prox'];
if(p.indexOf(".") == -1){
// global function : where *this* is window.
// check you've got the function if not ditch it.
if("function" == typeof window[p]) window[p](e);
} else {
// object/method (one level deep only.)
var s = p.split(".");
var o = s[0], m = s[1];
// check the object/method is a function before executing it.
if("function" == typeof window[o][p]) window[o][p](e);
}
}
Of course for global (window) scoped functions, this as the element is probably more useful, but in that case, you have a choice, I'd leave out the
version in use.
// dynamic proxy for retaining object context on methods called by
// data- attributes in Kendo.
//
// e.g.
//
// data-click="o.method"
//
// Would lose context with `o` - context would be set in the same
// way as JQuery handlers, which is an inconvenience.
//
// Alternatively we use the prox method
//
// data-click="prox"
//
// We'd then set `data-prox` on the same element, to the
// object.method pair.
//
// data-prox="o.method"
//
// This is read by prox, transformed into a method call, type
// checked and executed if it's a valid method.
//
// A `data-prox` value in any form other than `object.method` will
// be ignored, for example, `object.child.method` will fail. If
// you're doing that sort of thing, feel free to hack it.
//
// There's a backup eval() to locate the object if window doesn't
// own it. It should be possible to remove it under most
// circumstances, it's here for compatability with
// JSFiddle. (JSBin works without it.)
function prox(e) {
var p = this.element.data().prox;
if(p.indexOf(".") > -1){
var s = p.split("."); if(s.length > 2) return;
var o = s[0], m = s[1];
if("object" == typeof window[o]) {
o = window[o];
}
if("function" == typeof o[m]) o[m](e);
// comment this out in production:
l( "prox called " + s[0] + "::" + s[1] );
}
}
function l(s) { console.log(s); }
Caveats
If you have multiple handlers on the same element, prox() is unsuitable, for example, if you have data-init, data-show, etc. prox cannot differentiate, and will fail.
I'll probably update this, especially if this becomes a prevalent use-case for me.
I temporarily tried a third method, with a non-generic technique, which works like this.
Pseudo code:
MyObject {
method : function(e) {
if (this instanceof MyObject) {
// Do something with this
} else {
myInstance.method(e); // otherwise re-call the method to set this properly.
}
}
}
myInstance = new MyObject();
Not as flexible as the prox method, but suitable for my use case, and at least doesn't require a separate function proxy away from the method we want to use. We could make this more terse by doing the type check & re-call up front.
e.g.
MyObject = {
method : function(e) {
if (! this instanceof MyObject) myInstance.method(e); // re-call
// Method body...
}
}
myInstance = new MyObject();
It also meant I didn't need custom data- attributes in my markup.
Note: this method is problematic for objects which will have multiple instances, however, the objects I was applying to were single instances.
If you have handlers which need to be instance specific (which is the main reason I raised this question) the prox method is a much better fit than this, which is just a neater way of doing one-per-event proxy functions.
You may use jQuery Proxy (http://api.jquery.com/jQuery.proxy/).
function Foo(){};
Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
alert(this.name);
};
bar = new Foo();
$("btn").click($.proxy(bar.doSomething), bar);
or for inside using
$("btn").click($.proxy(this.doSomething), this);
I developed a proxy method using the JS Proxy Polyfill that simplify calling custom logic via parameters in a custon html data-* attribute.
Include https://raw.githubusercontent.com/GoogleChrome/proxy-polyfill/master/proxy.js
function makeGridTemplateEventProxy(o) {
return new Proxy(o, {
get(target, eventName) {
return function (options) {
return templateEventProxy(options, eventName);
}
}
});
}
templateEventProxy: function (options, attribute) {
if (!options.sender.element.attr('data-proxy-' + attribute)) {
throw new Error('Cannot find attribute data-proxy-' + attribute + ' on ' + options.sender.name + ' widget');
}
var proxyParams = JSON.parse(options.sender.element.attr('data-proxy-' + attribute));
method = $("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method);
if (method && typeof method == 'function') {
return $.proxy(method, this)(options);
}
return null;
}
var eventproxy = makeGridTemplateEventProxy({});
for example for upload component
<input type=file ...
data-success="eventproxy.customsuccesshandler"
data-proxy-customsuccesshandler='{widget:"kendoGrid",method:"<myJqueryDataDefinedMethod>",id:"<gridId>"}'
....
/>
substitute myJqueryDataDefinedMethod and gridId with your parameters
as you see you can define in data-success an eventproxy with dynamic name
data-success="eventproxy.CUSTOMKEY"
and after define a custom attribute
data-proxy-CUSTOMKEY
data-proxy-CUSTOMKEY contains parameters ( JSON encoded ) you can use to implement a custom logic,
I suggested custom logic which can retrieve JS method stored on kendo widget grid via $.data
$("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method)
You can bind method to grid for example with this
$('#my-grid-id').data("kendoGrid").element.data('methodName',function(e){
// my implementation
});

Categories

Resources