I am trying to implement a simple backbone model which has handlers for change and delete events on the model. The change
handler for changing the value of a property gets executed 2 times instead of 1 time! I have no clue why.
class BackboneModel {
constructor(initialValues) {
this.changeHandlers = new Map();
this.unsetHandlers = new Map();
const changeHandlers = this.changeHandlers;
this.model = new Proxy(initialValues ?? {}, {
set: function set(model, prop, newValue) {
// Do things to do before setting the value here.
const oldValue = model[prop];
if (newValue !== oldValue) {
Reflect.set(model, prop, newValue);
const handlers = this.changeHandlers.get(prop) ?? [];
// handler is a bound function to a context when it's added to the array (map value) itself
handlers.forEach(({callback, context}) => {
callback.call(context, prop, newValue, oldValue);
});
return true;
}
return false;
}.bind(this),
get(model, key) {
return Reflect.get(model, key);
},
has(model, key) {
if (!Object.hasOwn(model, key)) {
return false;
}
return key in model;
},
deleteProperty: function deleteProperty(model, key) {
if (!Object.hasOwn(model, key)) {
return true;
}
delete model[key];
const handlers = this.unsetHandlers.get(key) ?? [];
handlers.forEach(({callback, context}) => callback.call(context, key));
return true;
}.bind(this),
});
}
get(attribute) {
return this.model[attribute];
}
set(attribute, value) {
this.model[attribute] = value;
}
has(attribute) {
return attribute in this.model;
}
unset(attribute) {
delete this.model[attribute];
}
on(eventName, attribute, callback, context) {
if (attribute in this.model) {
switch(eventName) {
case 'change':
if (!this.changeHandlers.has(attribute)) {
this.changeHandlers.set(attribute, [{callback, context: context ?? null}]);
}
const handlers = this.changeHandlers.get(attribute);
handlers.push({callback, context: context ?? null});
this.changeHandlers.set(attribute, handlers);
break;
case 'unset':
if (!this.unsetHandlers.has(attribute)) {
this.unsetHandlers.set(attribute, [{callback, context: context ?? null}]);
}
const unsetHandlers = this.unsetHandlers.get(attribute);
unsetHandlers.push({callback, context: context ?? null});
this.unsetHandlers.set(attribute, unsetHandlers);
break;
default:
break;
}
}
}
off(eventName, attribute, callback) {
if (attribute in this.model) {
switch(eventName) {
case 'change':
if (this.changeHandlers.has(attribute)) {
let handlers = this.changeHandlers.get(attribute) ?? [];
handlers = handlers.filter(({callback: registeredCallback}) => registeredCallback !== callback);
this.changeHandlers.set(attribute, handlers);
}
break;
case 'unset':
if (this.unsetHandlers.has(attribute)) {
let unsetHandlers = this.unsetHandlers.get(attribute) ?? [];
unsetHandlers = unsetHandlers.filter(({callback: registeredCallback}) => registeredCallback !== callback);
this.unsetHandlers.set(attribute, unsetHandlers);
}
break;
default:
break;
}
}
}
}
Then I try to run this test, but it fails as times
is 2 instead of 1.
let times = 0;
function callback() {
times++;
}
const person = new BackboneModel({ name: 'John' });
person.on('change', 'name', callback);
person.set('name', 'Johnny');
console.log(times); // This should have been 1 and not 2.
I found a similar issue for array, but this is for object and it doesn’t have a length property – Set Trap execute for two times – JS Proxy
2