|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 module.metadata = { |
|
8 "stability": "deprecated" |
|
9 }; |
|
10 |
|
11 // `var` is being used in the module in order to make it reusable in |
|
12 // environments in which `let` and `const` is not yet supported. |
|
13 |
|
14 // Returns `object`'s property value, where `name` is a name of the property. |
|
15 function get(object, name) { |
|
16 return object[name]; |
|
17 } |
|
18 |
|
19 // Assigns `value` to the `object`'s property, where `name` is the name of the |
|
20 // property. |
|
21 function set(object, name, value) { |
|
22 return object[name] = value; |
|
23 } |
|
24 |
|
25 /** |
|
26 * Given an `object` containing a property with the given `name`, create |
|
27 * a property descriptor that can be used to define alias/proxy properties |
|
28 * on other objects. A change in the value of an alias will propagate |
|
29 * to the aliased property and vice versa. |
|
30 */ |
|
31 function createAliasProperty(object, name) { |
|
32 // Getting own property descriptor of an `object` for the given `name` as |
|
33 // we are going to create proxy analog. |
|
34 var property = Object.getOwnPropertyDescriptor(object, name); |
|
35 var descriptor = { |
|
36 configurable: property.configurable, |
|
37 enumerable: property.enumerable, |
|
38 alias: true |
|
39 }; |
|
40 |
|
41 // If the original property has a getter and/or setter, bind a |
|
42 // corresponding getter/setter in the alias descriptor to the original |
|
43 // object, so the `this` object in the getter/setter is the original object |
|
44 // rather than the alias. |
|
45 if ("get" in property && property.get) |
|
46 descriptor.get = property.get.bind(object); |
|
47 if ("set" in property && property.set) |
|
48 descriptor.set = property.set.bind(object); |
|
49 |
|
50 // If original property was a value property. |
|
51 if ("value" in property) { |
|
52 // If original property is a method using it's `object` bounded copy. |
|
53 if (typeof property.value === "function") { |
|
54 descriptor.value = property.value.bind(object); |
|
55 // Also preserving writability of the original property. |
|
56 descriptor.writable = property.writable; |
|
57 } |
|
58 |
|
59 // If the original property was just a data property, we create proxy |
|
60 // accessors using our custom get/set functions to propagate changes to the |
|
61 // original `object` and vice versa. |
|
62 else { |
|
63 descriptor.get = get.bind(null, object, name); |
|
64 descriptor.set = set.bind(null, object, name); |
|
65 } |
|
66 } |
|
67 return descriptor; |
|
68 } |
|
69 |
|
70 // Defines property on `object` object with a name `alias` if given if not |
|
71 // defaults to `name` that represents an alias of `source[name]`. If aliased |
|
72 // property was an assessor or a method `this` pseudo-variable will be `source` |
|
73 // when invoked. If aliased property was a data property changes on any of the |
|
74 // aliases will propagate to the `source[name]` and also other way round. |
|
75 function defineAlias(source, target, name, alias) { |
|
76 return Object.defineProperty(target, alias || name, |
|
77 createAliasProperty(source, name)); |
|
78 } |
|
79 |
|
80 /** |
|
81 * Function takes any `object` and returns a proxy for its own public |
|
82 * properties. By default properties are considered to be public if they don't |
|
83 * start with `"_"`, but default behavior can be overridden if needed, by |
|
84 * passing array of public property `names` as a second argument. By default |
|
85 * returned object will be direct decedent of the given `object`'s prototype, |
|
86 * but this can be overridden by passing third optional argument, that will be |
|
87 * used as `prototype` instead. |
|
88 * @param {Object} object |
|
89 * Object to create cortex for. |
|
90 * @param {String[]} [names] |
|
91 * Optional array of public property names. |
|
92 * @param {Object} [prototype] |
|
93 * Optional argument that will be used as `prototype` of the returned object, |
|
94 * if not provided `Object.getPrototypeOf(object)` is used instead. |
|
95 */ |
|
96 exports.Cortex = function Cortex(object, names, prototype) { |
|
97 // Creating a cortex object from the given `prototype`, if one was not |
|
98 // provided then `prototype` of a given `object` is used. This allows |
|
99 // consumer to define expected behavior `instanceof`. In common case |
|
100 // `prototype` argument can be omitted to preserve same behavior of |
|
101 // `instanceof` as on original `object`. |
|
102 var cortex = Object.create(prototype || Object.getPrototypeOf(object)); |
|
103 // Creating alias properties on the `cortex` object for all the own |
|
104 // properties of the original `object` that are contained in `names` array. |
|
105 // If `names` array is not provided then all the properties that don't |
|
106 // start with `"_"` are aliased. |
|
107 Object.getOwnPropertyNames(object).forEach(function (name) { |
|
108 if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name))) |
|
109 defineAlias(object, cortex, name); |
|
110 }); |
|
111 return cortex; |
|
112 } |