X-Git-Url: http://git.squeep.com/?p=squeep-lazy-property;a=blobdiff_plain;f=index.js;fp=index.js;h=7259810fae0e21f4bf336916cb76148a2f9fe74d;hp=0000000000000000000000000000000000000000;hb=1017c96958b07004f5dae64d0004d6104fedebd9;hpb=2a09b88daec188d1e2c49ef56dbdb9c81bd3ff28 diff --git a/index.js b/index.js new file mode 100644 index 0000000..7259810 --- /dev/null +++ b/index.js @@ -0,0 +1,82 @@ +'use strict'; + +/** + * Establish some immutable descriptor Shapes with our preferred defaults. + * We want normally-behaving properties unless told otherwise. + */ +function defaultShape(proto, ...objs) { + const newObject = Object.create(proto); + Object.assign(newObject, ...objs); + return newObject; +} + +const defaultDataDescriptor = defaultShape(null, { + configurable: true, + enumerable: true, + writable: true, + value: undefined, +}); + +const defaultAccessorDescriptor = defaultShape(null, { + configurable: true, + enumerable: true, + get: undefined, + set: undefined, +}); + +/** + * Create a new descriptor which inherits our preferred defaults. + * @param {Object} defaultDescriptor + * @param {...any} objs + * @returns {Object} + */ +function descriptorFromDefault(defaultDescriptor, ...objs) { + const newObject = Object.create(defaultDataDescriptor); + Object.assign(newObject, ...objs); + return Object.assign(Object.create(defaultDescriptor), ...objs); +} + +/** + * Defer calling initializer to set value for name until name is read. + * @param {Object} obj + * @param {String} name + * @param {() => {*}} initializer + * @param {Object=} descriptor + * @param {Boolean} descriptor.configurable + * @param {Boolean} descriptor.enumerable + * @param {Boolean} descriptor.writable + */ +function lazy(obj, name, initializer, descriptor) { + if (typeof initializer !== 'function') { + throw new TypeError('initialize is not callable'); + } + + // Establish the descriptor for the eventually-initialized property. + const finalDescriptor = descriptorFromDefault(defaultDataDescriptor, descriptor); + + // The descriptor defining the interim accessor property. + const lazyDescriptor = descriptorFromDefault(defaultAccessorDescriptor, { + // The accessor replaces itself with the value returned by invoking the initializer. + get: function () { + finalDescriptor.value = initializer.apply(obj); + Object.defineProperty(obj, name, finalDescriptor); + return finalDescriptor.value; + }, + + // An explicit set overrides the lazy descriptor with a default data property. + set: function (value) { + Object.defineProperty(obj, name, descriptorFromDefault(defaultDataDescriptor, { + value, + })) + }, + + configurable: true, // Ensure we can replace ourself. + enumerable: finalDescriptor.enumerable, // Use requested visibility. + }); + + Object.defineProperty(obj, name, lazyDescriptor); +} + +module.exports = { + lazy, +};