1.0.4 • Published 5 years ago
@selfage/observable_js v1.0.4
@selfage/observable_js
Observe objects through ES6 Proxy.
Function sigatures
/**
 * @param {object} obj - Any object except null.
 * @returns {object} observable object
 **/
function toObservable(obj) {}
/**
 * @param {object} obj - Any object except null.
 * @returns {boolean} true if `obj` is observable. See caveats for when it's not
 *   accurate.
 **/
function isObservable(obj) {}
/**
 * @callback Handler
 * @param {string} prop - The name of the changed property.
 * @param {any} newValue - The new value of the changed property.
 * @param {any} oldValue - The supposed old value of the changed proerpty. When
 *   propagated, it's the same as `newValue`.
 * @param {object} obj - The object to which the property belongs. See caveats
 *   if you want to use it.
 * @returns {void}
 */
/**
 * Available on an observable object.
 * @param {Handler} handler - To be be invoked when there is a change on any
 *   properties belonged to the observable object.
 * @returns {void} after calling all added handlers and don't wait for async
 *   operations.
 **/
function addPropertyChangeHandler(handler) {}
/**
 * Available on an observable object. Removing takes O(n) time where n is the
 * number of all handlers added to the observable object.
 * @param {Handler} handler - Must have the same reference to a handler added
 *   above.
 * @returns {void}
 **/
function removePropertyChangeHandler(handler) {}Usage
Flat object
const {toObservable, isObservable} = require('@selfage/observable_js');
const ob = toObservable({num: 100, str: 'lol', bul: false});
console.log(isObservable(ob));
// Print true
function logChange(prop, newValue, oldValue) {
  console.log(`${prop} is changed from ${oldValue} to ${newValue}.`);
}
ob.addPropertyChangeHandler(logChange);
ob.num = 200;
// Print "num is changed from 100 to 200."
ob.num2 = 300;
// Print "num2 is changed from undefined to 300."
ob.removePropertyChangeHandler(logChange);
ob.num = 150;
// Nothing to print.Array
Array is just an object, except that its length property can be updated when
push() or pop(). Note that its index is treated as a string.
const {toObservable, isObservable} = require('@selfage/observable_js');
const ob = toObservable([11,22,33,44,55]);
console.log(isObservable(ob));
// Print true
function logChange(prop, newValue, oldValue) {
  console.log(
    `${prop}[${typeof prop}] is changed from ${oldValue} to ${newValue}.`
  );
}
ob.addPropertyChangeHandler(logChange);
ob[2] = 100;
// Print "2[string] is changed from 33 to 100.";
ob.push(66);
// Print "5[string] is changed from undefined to 66."
// Oddly it might not print length.
ob.pop();
// Print "5[string] is changed from 66 to undefined."
// Print "length[string] is changed from 6 to 5".Nested object
All nested objects will be observable. Changes in nested objects are propagated/bubbled up until the top-level object. However, newValue and oldValue refer to the same nested object after propagating/bubbling up.
const {toObservable, isObservable} = require('@selfage/observable_js');
const ob = toObservable({
  num: 100,
  nobj: {
    value: 200
  }
});
console.log(isObservable(ob.nobj));
// Print "true"
function logChange(prop, newValue, oldValue) {
  console.log(
    `${prop} is changed from ${JSON.stringify(oldValue)} to ` +
    `${JSON.stringify(newValue)}.`
  );
}
ob.addPropertyChangeHandler(logChange);
ob.nobj.addPropertyChangeHandler(logChange);
ob.nobj.value = 300;
// Print "value is changed from 200 to 300."
// Print "nobj is changed from {"value":300} to {"value":300}."Add nested object
Add/Assign a nested object will automatically convert it to be observable.
const {toObservable, isObservable} = require('@selfage/observable_js');
const ob = toObservable({num: 100});
ob.nobj = {value: 200};
console.log(isObservable(ob.nobj));
// Print "true"Assigning an observable nested object is safe.
const {toObservable, isObservable} = require('@selfage/observable_js');
const ob = toObservable({num: 100});
ob.nobj = toObservable({value: 200});
console.log(isObservable(ob.nobj));
// Print "true"Caveats
delete vs null vs undefined
const {toObservable, isObservable} = require('@selfage/observable_js');
// toObservable(null) results in an error. Never use null.
const ob = toObservable({num: 100});
function logChange(prop, newValue, oldValue) {
  console.log(`${prop} is changed from ${oldValue} to ${newValue}.`);
}
ob.addPropertyChangeHandler(logChange);
delete ob.num;
// Print "num is changed from 100 to undefined".
ob.num = undefined;
// Nothing to print, since ob.num === undefined already.
// If we set `ob.num = undefined` first and `delete ob.num`, logChange() will
// also not be invoked for `delete ob.num`.
delete ob.num;
// Still nothing to print.
// You should decide to delete or set to undefined, depending on how you woul
// deal with `'num' in ob` or `Object.keys()`.Is object passed to handlers observable?
const {toObservable, isObservable} = require('@selfage/observable_js');
const ob = toObservable({
  num: 100,
  nobj: {
    value: 200
  }
});
let capturedTopLevel = {obj: undefined, nestedObj: undefined};
function captureTopLevel(prop, newValue, oldValue, obj) {
  capturedTopLevel.obj = obj;
  capturedTopLevel.nestedObj = newValue;
}
let capturedSecondLevel = {obj: undefined};
function captureSecondLevel(prop, newValue, oldValue, obj) {
  capturedTopLevel.obj = obj;
}
ob.addPropertyChangeHandler(captureTopLevel);
ob.nobj.addPropertyChangeHandler(captureSecondLevel);
ob.nobj.value = 300;
ob.removePropertyChangeHandler(captureTopLevel);
ob.nobj.removePropertyChangeHandler(captureSecondLevel);
let topLevelCount = 0;
function countTopLevelChange() {
  topLevelCount++;
}
let secondLevelCount = 0;
function countSecondLevelChange() {
  secondLevelCount++;
}
ob.addPropertyChangeHandler(countTopLevelChange);
ob.nobj.addPropertyChangeHandler(countSecondLevelChange);
ob.nobj.value = 400;
// topLevelCount === 1 && secondLevelCount === 1
capturedSecondLevel.obj.value = 500;
// No change: topLevelCount === 1 && secondLevelCount === 1
capturedTopLevel.obj.num = 0;
// No change: topLevelCount === 1 && secondLevelCount === 1
capturedTopLevel.nestedObj.value = 600;
// topLevelCount === 2 && secondLevelCount === 2
// Conclusion: The fourth parameter `obj` is not trully observable, although
// `isObservable(capturedTopLevel.obj)` and
// `isObservable(capturedSecondLevel.obj)` are both true;More cases
You can find almost all use cases in observable_test.js and observable_test2.js.