michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "deprecated" michael@0: }; michael@0: michael@0: // `var` is being used in the module in order to make it reusable in michael@0: // environments in which `let` and `const` is not yet supported. michael@0: michael@0: // Returns `object`'s property value, where `name` is a name of the property. michael@0: function get(object, name) { michael@0: return object[name]; michael@0: } michael@0: michael@0: // Assigns `value` to the `object`'s property, where `name` is the name of the michael@0: // property. michael@0: function set(object, name, value) { michael@0: return object[name] = value; michael@0: } michael@0: michael@0: /** michael@0: * Given an `object` containing a property with the given `name`, create michael@0: * a property descriptor that can be used to define alias/proxy properties michael@0: * on other objects. A change in the value of an alias will propagate michael@0: * to the aliased property and vice versa. michael@0: */ michael@0: function createAliasProperty(object, name) { michael@0: // Getting own property descriptor of an `object` for the given `name` as michael@0: // we are going to create proxy analog. michael@0: var property = Object.getOwnPropertyDescriptor(object, name); michael@0: var descriptor = { michael@0: configurable: property.configurable, michael@0: enumerable: property.enumerable, michael@0: alias: true michael@0: }; michael@0: michael@0: // If the original property has a getter and/or setter, bind a michael@0: // corresponding getter/setter in the alias descriptor to the original michael@0: // object, so the `this` object in the getter/setter is the original object michael@0: // rather than the alias. michael@0: if ("get" in property && property.get) michael@0: descriptor.get = property.get.bind(object); michael@0: if ("set" in property && property.set) michael@0: descriptor.set = property.set.bind(object); michael@0: michael@0: // If original property was a value property. michael@0: if ("value" in property) { michael@0: // If original property is a method using it's `object` bounded copy. michael@0: if (typeof property.value === "function") { michael@0: descriptor.value = property.value.bind(object); michael@0: // Also preserving writability of the original property. michael@0: descriptor.writable = property.writable; michael@0: } michael@0: michael@0: // If the original property was just a data property, we create proxy michael@0: // accessors using our custom get/set functions to propagate changes to the michael@0: // original `object` and vice versa. michael@0: else { michael@0: descriptor.get = get.bind(null, object, name); michael@0: descriptor.set = set.bind(null, object, name); michael@0: } michael@0: } michael@0: return descriptor; michael@0: } michael@0: michael@0: // Defines property on `object` object with a name `alias` if given if not michael@0: // defaults to `name` that represents an alias of `source[name]`. If aliased michael@0: // property was an assessor or a method `this` pseudo-variable will be `source` michael@0: // when invoked. If aliased property was a data property changes on any of the michael@0: // aliases will propagate to the `source[name]` and also other way round. michael@0: function defineAlias(source, target, name, alias) { michael@0: return Object.defineProperty(target, alias || name, michael@0: createAliasProperty(source, name)); michael@0: } michael@0: michael@0: /** michael@0: * Function takes any `object` and returns a proxy for its own public michael@0: * properties. By default properties are considered to be public if they don't michael@0: * start with `"_"`, but default behavior can be overridden if needed, by michael@0: * passing array of public property `names` as a second argument. By default michael@0: * returned object will be direct decedent of the given `object`'s prototype, michael@0: * but this can be overridden by passing third optional argument, that will be michael@0: * used as `prototype` instead. michael@0: * @param {Object} object michael@0: * Object to create cortex for. michael@0: * @param {String[]} [names] michael@0: * Optional array of public property names. michael@0: * @param {Object} [prototype] michael@0: * Optional argument that will be used as `prototype` of the returned object, michael@0: * if not provided `Object.getPrototypeOf(object)` is used instead. michael@0: */ michael@0: exports.Cortex = function Cortex(object, names, prototype) { michael@0: // Creating a cortex object from the given `prototype`, if one was not michael@0: // provided then `prototype` of a given `object` is used. This allows michael@0: // consumer to define expected behavior `instanceof`. In common case michael@0: // `prototype` argument can be omitted to preserve same behavior of michael@0: // `instanceof` as on original `object`. michael@0: var cortex = Object.create(prototype || Object.getPrototypeOf(object)); michael@0: // Creating alias properties on the `cortex` object for all the own michael@0: // properties of the original `object` that are contained in `names` array. michael@0: // If `names` array is not provided then all the properties that don't michael@0: // start with `"_"` are aliased. michael@0: Object.getOwnPropertyNames(object).forEach(function (name) { michael@0: if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name))) michael@0: defineAlias(object, cortex, name); michael@0: }); michael@0: return cortex; michael@0: }