86 lines
3.8 KiB
JavaScript
86 lines
3.8 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.flatten = exports.unique = exports.hardMixProtos = exports.nearestCommonProto = exports.protoChain = exports.copyProps = void 0;
|
|
/**
|
|
* Utility function that works like `Object.apply`, but copies getters and setters properly as well. Additionally gives
|
|
* the option to exclude properties by name.
|
|
*/
|
|
const copyProps = (dest, src, exclude = []) => {
|
|
const props = Object.getOwnPropertyDescriptors(src);
|
|
for (let prop of exclude)
|
|
delete props[prop];
|
|
Object.defineProperties(dest, props);
|
|
};
|
|
exports.copyProps = copyProps;
|
|
/**
|
|
* Returns the full chain of prototypes up until Object.prototype given a starting object. The order of prototypes will
|
|
* be closest to farthest in the chain.
|
|
*/
|
|
const protoChain = (obj, currentChain = [obj]) => {
|
|
const proto = Object.getPrototypeOf(obj);
|
|
if (proto === null)
|
|
return currentChain;
|
|
return exports.protoChain(proto, [...currentChain, proto]);
|
|
};
|
|
exports.protoChain = protoChain;
|
|
/**
|
|
* Identifies the nearest ancestor common to all the given objects in their prototype chains. For most unrelated
|
|
* objects, this function should return Object.prototype.
|
|
*/
|
|
const nearestCommonProto = (...objs) => {
|
|
if (objs.length === 0)
|
|
return undefined;
|
|
let commonProto = undefined;
|
|
const protoChains = objs.map(obj => exports.protoChain(obj));
|
|
while (protoChains.every(protoChain => protoChain.length > 0)) {
|
|
const protos = protoChains.map(protoChain => protoChain.pop());
|
|
const potentialCommonProto = protos[0];
|
|
if (protos.every(proto => proto === potentialCommonProto))
|
|
commonProto = potentialCommonProto;
|
|
else
|
|
break;
|
|
}
|
|
return commonProto;
|
|
};
|
|
exports.nearestCommonProto = nearestCommonProto;
|
|
/**
|
|
* Creates a new prototype object that is a mixture of the given prototypes. The mixing is achieved by first
|
|
* identifying the nearest common ancestor and using it as the prototype for a new object. Then all properties/methods
|
|
* downstream of this prototype (ONLY downstream) are copied into the new object.
|
|
*
|
|
* The resulting prototype is more performant than softMixProtos(...), as well as ES5 compatible. However, it's not as
|
|
* flexible as updates to the source prototypes aren't captured by the mixed result. See softMixProtos for why you may
|
|
* want to use that instead.
|
|
*/
|
|
const hardMixProtos = (ingredients, constructor, exclude = []) => {
|
|
var _a;
|
|
const base = (_a = exports.nearestCommonProto(...ingredients)) !== null && _a !== void 0 ? _a : Object.prototype;
|
|
const mixedProto = Object.create(base);
|
|
// Keeps track of prototypes we've already visited to avoid copying the same properties multiple times. We init the
|
|
// list with the proto chain below the nearest common ancestor because we don't want any of those methods mixed in
|
|
// when they will already be accessible via prototype access.
|
|
const visitedProtos = exports.protoChain(base);
|
|
for (let prototype of ingredients) {
|
|
let protos = exports.protoChain(prototype);
|
|
// Apply the prototype chain in reverse order so that old methods don't override newer ones.
|
|
for (let i = protos.length - 1; i >= 0; i--) {
|
|
let newProto = protos[i];
|
|
if (visitedProtos.indexOf(newProto) === -1) {
|
|
exports.copyProps(mixedProto, newProto, ['constructor', ...exclude]);
|
|
visitedProtos.push(newProto);
|
|
}
|
|
}
|
|
}
|
|
mixedProto.constructor = constructor;
|
|
return mixedProto;
|
|
};
|
|
exports.hardMixProtos = hardMixProtos;
|
|
const unique = (arr) => arr.filter((e, i) => arr.indexOf(e) == i);
|
|
exports.unique = unique;
|
|
const flatten = (arr) => arr.length === 0
|
|
? []
|
|
: arr.length === 1
|
|
? arr[0]
|
|
: arr.reduce((a1, a2) => [...a1, ...a2]);
|
|
exports.flatten = flatten;
|