/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; module.metadata = { "stability": "unstable" }; var getPrototypeOf = Object.getPrototypeOf; var getNames = x => [...Object.getOwnPropertyNames(x), ...Object.getOwnPropertySymbols(x)]; var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; var create = Object.create; var freeze = Object.freeze; var unbind = Function.call.bind(Function.bind, Function.call); // This shortcut makes sure that we do perform desired operations, even if // associated methods have being overridden on the used object. var owns = unbind(Object.prototype.hasOwnProperty); var apply = unbind(Function.prototype.apply); var slice = Array.slice || unbind(Array.prototype.slice); var reduce = Array.reduce || unbind(Array.prototype.reduce); var map = Array.map || unbind(Array.prototype.map); var concat = Array.concat || unbind(Array.prototype.concat); // Utility function to get own properties descriptor map. function getOwnPropertyDescriptors(object) { return reduce(getNames(object), function(descriptor, name) { descriptor[name] = getOwnPropertyDescriptor(object, name); return descriptor; }, {}); } function isDataProperty(property) { var value = property.value; var type = typeof(property.value); return "value" in property && (type !== "object" || value === null) && type !== "function"; } function getDataProperties(object) { var properties = getOwnPropertyDescriptors(object); return getNames(properties).reduce(function(result, name) { var property = properties[name]; if (isDataProperty(property)) { result[name] = { value: property.value, writable: true, configurable: true, enumerable: false }; } return result; }, {}) } /** * Takes `source` object as an argument and returns identical object * with the difference that all own properties will be non-enumerable */ function obscure(source) { var descriptor = reduce(getNames(source), function(descriptor, name) { var property = getOwnPropertyDescriptor(source, name); property.enumerable = false; descriptor[name] = property; return descriptor; }, {}); return create(getPrototypeOf(source), descriptor); } exports.obscure = obscure; /** * Takes arbitrary number of source objects and returns fresh one, that * inherits from the same prototype as a first argument and implements all * own properties of all argument objects. If two or more argument objects * have own properties with the same name, the property is overridden, with * precedence from right to left, implying, that properties of the object on * the left are overridden by a same named property of the object on the right. */ var mix = function(source) { var descriptor = reduce(slice(arguments), function(descriptor, source) { return reduce(getNames(source), function(descriptor, name) { descriptor[name] = getOwnPropertyDescriptor(source, name); return descriptor; }, descriptor); }, {}); return create(getPrototypeOf(source), descriptor); }; exports.mix = mix; /** * Returns a frozen object with that inherits from the given `prototype` and * implements all own properties of the given `properties` object. */ function extend(prototype, properties) { return create(prototype, getOwnPropertyDescriptors(properties)); } exports.extend = extend; /** * Returns a constructor function with a proper `prototype` setup. Returned * constructor's `prototype` inherits from a given `options.extends` or * `Class.prototype` if omitted and implements all the properties of the * given `option`. If `options.implemens` array is passed, it's elements * will be mixed into prototype as well. Also, `options.extends` can be * a function or a prototype. If function than it's prototype is used as * an ancestor of the prototype, if it's an object that it's used directly. * Also `options.implements` may contain functions or objects, in case of * functions their prototypes are used for mixing. */ var Class = new function() { function prototypeOf(input) { return typeof(input) === 'function' ? input.prototype : input; } var none = freeze([]); return function Class(options) { // Create descriptor with normalized `options.extends` and // `options.implements`. var descriptor = { // Normalize extends property of `options.extends` to a prototype object // in case it's constructor. If property is missing that fallback to // `Type.prototype`. extends: owns(options, 'extends') ? prototypeOf(options.extends) : Class.prototype, // Normalize `options.implements` to make sure that it's array of // prototype objects instead of constructor functions. implements: owns(options, 'implements') ? freeze(map(options.implements, prototypeOf)) : none }; // Create array of property descriptors who's properties will be defined // on the resulting prototype. Note: Using reflection `concat` instead of // method as it may be overridden. var descriptors = concat(descriptor.implements, options, descriptor, { constructor: constructor }); // Note: we use reflection `apply` in the constructor instead of method // call since later may be overridden. function constructor() { var instance = create(prototype, attributes); if (initialize) apply(initialize, instance, arguments); return instance; } // Create `prototype` that inherits from given ancestor passed as // `options.extends`, falling back to `Type.prototype`, implementing all // properties of given `options.implements` and `options` itself. var prototype = extend(descriptor.extends, mix.apply(mix, descriptors)); var initialize = prototype.initialize; // Combine ancestor attributes with prototype's attributes so that // ancestors attributes also become initializeable. var attributes = mix(descriptor.extends.constructor.attributes || {}, getDataProperties(prototype)); constructor.attributes = attributes; Object.defineProperty(constructor, 'prototype', { configurable: false, writable: false, value: prototype }); return constructor; }; } Class.prototype = extend(null, obscure({ constructor: function constructor() { this.initialize.apply(this, arguments); return this; }, initialize: function initialize() { // Do your initialization logic here }, // Copy useful properties from `Object.prototype`. toString: Object.prototype.toString, toLocaleString: Object.prototype.toLocaleString, toSource: Object.prototype.toSource, valueOf: Object.prototype.valueOf, isPrototypeOf: Object.prototype.isPrototypeOf })); exports.Class = freeze(Class);