| |
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 } |