aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.7/packages/api-utils/docs/cortex.md
blob: afae8cbfd87e3baddd98482e54fe41369db08558 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<!-- 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/. -->


## Property Encapsulation ##

In JavaScript it is not possible to create properties that have limited or
controlled accessibility. It is possible to create non-enumerable and
non-writable properties, but still they can be discovered and accessed.
Usually so called "closure capturing" is used to encapsulate such properties
in lexical scope:

    function Foo() {
      var _secret = 'secret';
      this.hello = function hello() {
        return 'Hello ' + _secret;
      }
    }

This provides desired result, but has side effect of degrading code readability,
especially with object-oriented programs. Another disadvantage with this pattern
is that there is no immediate solution for inheriting access to the privates
(illustrated by the following example):

    function Derived() {
      this.hello = function hello() {
        return _secret;
      }
      this.bye = function bye() {
        return _secret;
      }
    }
    Derived.prototype = Object.create(Foo.prototype);

## Facade Objects ##

Alternatively constructor can returned facade objects - proxies to the
instance's public properties:

    function Foo() {
      var foo = Object.create(Foo.prototype);
      return {
        bar: foo.hello.bind(foo);
      }
    }
    Foo.prototype._secret = 'secret';
    Foo.prototype.hello = function hello() {
      return 'Hello ' + this._secret;
    }

    function Derived() {
      var derived = Object.create(Derived.prototype);
      return {
        bar: derived.hello.bind(derived);
        bye: derived.bye.bind(derived);
      }
    }
    Derived.prototype = Object.create(Foo.prototype);
    Derived.prototype.bye = function bye() {
      return 'Bye ' + this._secret;
    };

While this solution solves given issue and provides proper encapsulation for
both own and inherited private properties, it does not addresses following:

 - Privates defined on the `prototype` can be compromised, since they are
   accessible through the constructor (`Foo.prototype._secret`).
 - Behavior of `instanceof` is broken, since `new Derived() instanceof Derived`
   is going to evaluate to `false`.

## Tamper Proofing with Property Descriptor Maps ##

In ES5 new property descriptor maps were introduced, which can be used as a
building blocks for defining reusable peace of functionality. To some degree
they are similar to a `prototype` objects, and can be used so to define pieces
of functionality that is considered to be private (In contrast to `prototype`
they are not exposed by default).

    function Foo() {
      var foo = Object.create(Foo.prototype, FooDescriptor);
      var facade = Object.create(Foo.prototype);
      facade.hello = foo.hello.bind(foo);
      return facade;
    }
    Foo.prototype.hello = function hello() {
      return 'Hello ' + this._secret;
    }
    var FooDescriptor = {
      _secret: { value: 'secret' };
    }

    function Derived() {
      var derived = Object.create(Derived.prototype, DerivedDescriptor);
      var facade = Object.create(Derived.prototype);
      facade.hello = derived.hello.bind(derived);
      facade.bye = derived.bye.bind(derived);
      return facade;
    }
    Derived.prototype = Object.create(Foo.prototype);
    Derived.prototype.bye = function bye() {
      return 'Bye ' + this._secret;
    };
    DerivedDescriptor = {};

    Object.keys(FooDescriptor).forEach(function(key) {
      DerivedDescriptor[key] = FooDescriptor[key];
    });

## Cortex Objects ##

Last approach solves all of the concerns, but adds complexity, verbosity
and decreases code readability. Combination of `Cortex`'s and `Trait`'s
will gracefully solve all these issues and keep code clean:

    var Cortex = require('cortex').Cortex;
    var Trait = require('light-traits').Trait;

    var FooTrait = Trait({
      _secret: 'secret',
      hello: function hello() {
        return 'Hello ' + this._secret;
      }
    });
    function Foo() {
      return Cortex(FooTrait.create(Foo.prototype));
    }

    var DerivedTrait = Trait.compose(FooTrait, Trait({
      bye: function bye() {
        return 'Bye ' + this._secret;
      }
    }));
    function Derived() {
      var derived = DerivedTrait.create(Derived.prototype);
      return Cortex(derived);
    }

Function `Cortex` takes any object and returns a proxy for its public
properties. By default properties are considered to be public if they don't
start with `"_"`, but default behavior can be overridden if needed, by passing
array of public property names as a second argument.

## Gotchas ##

`Cortex` is just a utility function to create a proxy object, and it does not
solve the `prototype`-related issues highlighted earlier, but since traits make
use of property descriptor maps instead of `prototype`s, there aren't any
issues with using `Cortex` to wrap objects created from traits.

If you want to use `Cortex` with an object that uses a `prototype` chain,
however, you should either make sure you don't have any private properties
in the prototype chain or pass the optional third `prototype` argument.

In the latter case, the returned proxy will inherit from the given prototype,
and the `prototype` chain of the wrapped object will be inaccessible.
However, note that the behavior of the `instanceof` operator will vary,
as `proxy instanceof Constructor` will return false even if the Constructor
function's prototype is in the wrapped object's prototype chain.