9 * Shorthand helper for building descriptors up from a prototype chain.
10 * @param {object} proto prototype object
11 * @param {object[]} objs objects to assign to derived object, in order
12 * @returns {object} new object
14 function createAssign(proto
, ...objs
) {
15 return Object
.assign(Object
.create(proto
), ...objs
);
19 * Defaults common to both data and accessor descriptors.
22 const defaultCommonDescriptor
= createAssign(null, {
28 * Defaults for an eventually-initialized property.
29 * Same as if a property was set normally.
31 const defaultDataDescriptor
= createAssign(defaultCommonDescriptor
, {
37 * Defaults for an interim accessor property.
39 const defaultAccessorDescriptor
= createAssign(defaultCommonDescriptor
, {
45 * @typedef {object} PropertyDescriptor
46 * @property {boolean=} configurable some things can be changed
47 * @property {boolean=} enumerable shows up in places
48 * @property {boolean=} writable value can be changed
49 * @property {any=} value value
50 * @property {Function=} get getter
51 * @property {Function=} set setter
55 * Defer calling initializer to set value for name until name is read.
56 * If a lazy property is defined on an object which is used as a prototype,
57 * the objectBound flag determines whether the lazy initializer will be
58 * invoked just once and set the property value on the original prototype
59 * object, or for any inherited object and set the property on that object.
60 * The objectBound flag also controls the 'this' object of the initializer
62 * @param {object} obj object
63 * @param {string} name property name
64 * @param {Function} initializer function which returns value to set on property upon first access
65 * @param {PropertyDescriptor=} descriptor optional descriptor
66 * @param {boolean=} objectBound bind initializer to obj
67 * @returns {object} obj with lazy property
69 function lazy(obj
, name
, initializer
, descriptor
, objectBound
= true) {
70 if (typeof initializer
!== 'function') {
71 throw new TypeError('initializer is not callable');
74 // The descriptor for the eventually-initialized property.
75 const finalDescriptor
= createAssign(defaultDataDescriptor
, descriptor
);
77 // The descriptor defining the interim accessor property.
78 const lazyDescriptor
= createAssign(defaultAccessorDescriptor
, {
79 // The accessor replaces itself with the value returned by invoking the initializer.
81 const targetObject
= objectBound
? obj : this;
82 finalDescriptor
.value
= initializer
.apply(targetObject
);
83 Object
.defineProperty(targetObject
, name
, finalDescriptor
);
84 return finalDescriptor
.value
;
88 * Unless a non-writable descriptor was specified, an explicit set will overwrite
89 * the lazy descriptor with a default data property,
91 ...(finalDescriptor
.writable
&& {
92 set: function (value
) {
93 Object
.defineProperty(this, name
, createAssign(defaultDataDescriptor
, {
99 configurable: true, // Ensure we can replace ourself.
100 enumerable: finalDescriptor
.enumerable
, // Use requested visibility.
103 return Object
.defineProperty(obj
, name
, lazyDescriptor
);