Advertisement
  1. Web Design
  2. HTML/CSS
  3. HTML

JavaScript: How to Embed Private Members Into an Object

Scroll to top
Read Time: 10 min
Sponsored Content

This sponsored post features a product relevant to our readers while meeting our editorial guidelines for being objective and educational.

I recently developed Angular Cloud Data Connector, which enables Angular developers to use cloud data, specifically Azure Mobile Services, using web standards like indexed DB. I was trying to create a way for JavaScript developers to embed private members into an object. 

My technique for this specific case is to use what I call “closure space”. In this tutorial, I want to share with you how to use this for your own projects and how it affects performance and memory for the major browsers.

But before diving into it, let me share why you may need private members, as well as an alternate way to “simulate” private members.

Feel free to ping me on Twitter if you want to discuss this article: @deltakosh.

1. Why Use Private Members

When you create an object using JavaScript, you can define value members. If you want to control read/write access on them, you need accessors that can be defined like this:

1
var entity = {};
2
3
4
5
entity._property = "hello world";
6
7
Object.defineProperty(entity, "property", {
8
9
    get: function () { return this._property; },
10
11
    set: function (value) {
12
13
        this._property = value;
14
15
    },
16
17
    enumerable: true,
18
19
    configurable: true
20
21
});

By doing this, you have full control over read and write operations. The problem is that the _property member is still accessible and can be modified directly.

This is exactly why you need a more robust way to define private members that can only be accessed by object’s functions.

2. Using Closure Space

The solution is to use closure space. This memory space is built for you by the browser each time an inner function has access to variables from the scope of an outer function. This can be tricky sometimes, but for our topic this is a perfect solution.

So let’s alter the previous code to use this feature:

1
var createProperty = function (obj, prop, currentValue) {
2
3
    Object.defineProperty(obj, prop, {
4
5
        get: function () { return currentValue; },
6
7
        set: function (value) {
8
9
            currentValue = value;
10
11
        },
12
13
        enumerable: true,
14
15
        configurable: true
16
17
    });
18
19
}
20
21
22
23
var entity = {};
24
25
26
27
var myVar = "hello world";
28
29
createProperty(entity, "property", myVar);

In this example, the createProperty function has a currentValue variable that get and set functions can see. This variable is going to be saved in the closure space of get and set functions. Only these two functions can now see and update the currentValue variable! Mission accomplished!

The only caveat we have here is that the source value (myVar) is still accessible. So here comes another version for even more robust protection:

1
var createProperty = function (obj, prop) {
2
3
    var currentValue = obj[prop];
4
5
    Object.defineProperty(obj, prop, {
6
7
        get: function () { return currentValue; },
8
9
        set: function (value) {
10
11
            currentValue = value;
12
13
        },
14
15
        enumerable: true,
16
17
        configurable: true
18
19
    });
20
21
}
22
23
24
25
var entity = {
26
27
    property: "hello world"
28
29
};
30
31
32
33
createProperty(entity, "property");

Using this method, even the source value is destructed. So mission fully accomplished!

3. Performance Considerations

Let’s now have a look at performance.

Obviously, closure spaces or even properties are slower and more expensive than just a plain variable. That’s why this article focuses more on the difference between the regular way and the closure space technique.

To confirm the closure space approach is not too expensive compared to the standard way, I wrote this little benchmark:

1
<!DOCTYPE html>
2
3
<html xmlns="http://www.w3.org/1999/xhtml">
4
5
<head>
6
7
    <title></title>
8
9
</head>
10
11
<style>
12
13
    html {
14
15
        font-family: "Helvetica Neue", Helvetica;
16
17
    }
18
19
</style>
20
21
<body>
22
23
    <div id="results">Computing...</div>
24
25
    <script>
26
27
        var results = document.getElementById("results");
28
29
        var sampleSize = 1000000;
30
31
        var opCounts = 1000000;
32
33
34
35
        var entities = [];
36
37
38
39
        setTimeout(function () {
40
41
            // Creating entities

42
43
            for (var index = 0; index < sampleSize; index++) {
44
45
                entities.push({
46
47
                    property: "hello world (" + index + ")"
48
49
                });
50
51
            }
52
53
54
55
            // Random reads

56
57
            var start = new Date().getTime();
58
59
            for (index = 0; index < opCounts; index++) {
60
61
                var position = Math.floor(Math.random() * entities.length);
62
63
                var temp = entities[position].property;
64
65
            }
66
67
            var end = new Date().getTime();
68
69
70
71
            results.innerHTML = "<strong>Results:</strong><br>Using member access: <strong>" + (end - start) + "</strong> ms";
72
73
        }, 0);
74
75
76
77
        setTimeout(function () {
78
79
            // Closure space =======================================

80
81
            var createProperty = function (obj, prop, currentValue) {
82
83
                Object.defineProperty(obj, prop, {
84
85
                    get: function () { return currentValue; },
86
87
                    set: function (value) {
88
89
                        currentValue = value;
90
91
                    },
92
93
                    enumerable: true,
94
95
                    configurable: true
96
97
                });
98
99
            }
100
101
            // Adding property and using closure space to save private value

102
103
            for (var index = 0; index < sampleSize; index++) {
104
105
                var entity = entities[index];
106
107
108
109
                var currentValue = entity.property;
110
111
                createProperty(entity, "property", currentValue);
112
113
            }
114
115
116
117
            // Random reads

118
119
            var start = new Date().getTime();
120
121
            for (index = 0; index < opCounts; index++) {
122
123
                var position = Math.floor(Math.random() * entities.length);
124
125
                var temp = entities[position].property;
126
127
            }
128
129
            var end = new Date().getTime();
130
131
132
133
            results.innerHTML += "<br>Using closure space: <strong>" + (end - start) + "</strong> ms";
134
135
        }, 0);
136
137
138
139
        setTimeout(function () {
140
141
            // Using local member =======================================

142
143
            // Adding property and using local member to save private value

144
145
            for (var index = 0; index < sampleSize; index++) {
146
147
                var entity = entities[index];
148
149
150
151
                entity._property = entity.property;
152
153
                Object.defineProperty(entity, "property", {
154
155
                    get: function () { return this._property; },
156
157
                    set: function (value) {
158
159
                        this._property = value;
160
161
                    },
162
163
                    enumerable: true,
164
165
                    configurable: true
166
167
                });
168
169
            }
170
171
172
173
            // Random reads

174
175
            var start = new Date().getTime();
176
177
            for (index = 0; index < opCounts; index++) {
178
179
                var position = Math.floor(Math.random() * entities.length);
180
181
                var temp = entities[position].property;
182
183
            }
184
185
            var end = new Date().getTime();
186
187
188
189
            results.innerHTML += "<br>Using local member: <strong>" + (end - start) + "</strong> ms";
190
191
        }, 0);
192
193
194
195
    </script>
196
197
</body>
198
199
</html>

I create 1 million objects, all with a property member. Then I do three tests:

  • Do 1 million random accesses to the property.
  • Do 1 million random accesses to the “closure space” version.
  • Do 1 million random accesses to the regular get/set version.

Here are a table and a chart of the results:

Table of results for IE11 Chrome 36 and Firefox 31Table of results for IE11 Chrome 36 and Firefox 31Table of results for IE11 Chrome 36 and Firefox 31
Chart of results for IE11 Chrome 36 and Firefox 31Chart of results for IE11 Chrome 36 and Firefox 31Chart of results for IE11 Chrome 36 and Firefox 31

We can see that the closure space version is always faster than the regular version and depending on the browser, it can be a really impressive optimization.

Chrome's performance is less than I expected. There may be a bug so to be sure, I contacted Google’s team to figure out what’s happening here. Also if you want to test how this performs in Microsoft Edge—Microsoft’s new browser that will ship default with Windows 10—you can download it here.

However, if we look closely we can find that using closure space or even a property can be ten times slower than direct access to a member. So be warned and use it wisely.

Chart comparing Direct access closure space and regular wayChart comparing Direct access closure space and regular wayChart comparing Direct access closure space and regular way

4. Memory Footprint

We also have to check that this technique does not consume too much memory. To benchmark memory I wrote these three little pieces of code:

Reference Code

1
var sampleSize = 1000000;
2
3
4
5
var entities = [];
6
7
8
9
// Creating entities

10
11
for (var index = 0; index < sampleSize; index++) {
12
13
    entities.push({
14
15
        property: "hello world (" + index + ")"
16
17
    });
18
19
}

Regular Way

1
var sampleSize = 1000000;
2
3
4
5
var entities = [];
6
7
8
9
// Adding property and using local member to save private value

10
11
for (var index = 0; index < sampleSize; index++) {
12
13
    var entity = {};
14
15
16
17
    entity._property = "hello world (" + index + ")";
18
19
    Object.defineProperty(entity, "property", {
20
21
        get: function () { return this._property; },
22
23
        set: function (value) {
24
25
            this._property = value;
26
27
        },
28
29
        enumerable: true,
30
31
        configurable: true
32
33
    });
34
35
36
37
    entities.push(entity);
38
39
}

Closure Space Version

1
var sampleSize = 1000000;
2
3
4
5
var entities = [];
6
7
8
9
var createProperty = function (obj, prop, currentValue) {
10
11
    Object.defineProperty(obj, prop, {
12
13
        get: function () { return currentValue; },
14
15
        set: function (value) {
16
17
            currentValue = value;
18
19
        },
20
21
        enumerable: true,
22
23
        configurable: true
24
25
    });
26
27
}
28
29
30
31
// Adding property and using closure space to save private value

32
33
for (var index = 0; index < sampleSize; index++) {
34
35
    var entity = {};
36
37
38
39
    var currentValue = "hello world (" + index + ")";
40
41
    createProperty(entity, "property", currentValue);
42
43
44
45
    entities.push(entity);
46
47
}

Then I ran all these three codes and launched the embedded memory profiler (example here using F12 tools):

embedded memory profiler example here using F12 toolsembedded memory profiler example here using F12 toolsembedded memory profiler example here using F12 tools

Here are the results I got on my computer:

Chart of the results I got on my computerChart of the results I got on my computerChart of the results I got on my computer

Between closure space and regular way, only Chrome has slightly better results for the closure space version. IE11 and Firefox use a bit more memory, but the browsers are relatively comparable—users probably won’t notice a difference across the modern browsers.

More Hands-On With JavaScript

It might surprise you a bit, but Microsoft has a bunch of free learning on many open source JavaScript topics, and we’re on a mission to create a lot more with Microsoft Edge coming. Check out my own:

Or our team’s learning series:

And some free tools: Visual Studio Community, Azure Trial, and cross-browser testing tools for Mac, Linux, or Windows.

Conclusion

As you can see, closure space properties can be a great way to create really private data. You may have to deal with a small increase in memory consumption, but from my point of view this is fairly reasonable (and at that price you can have a great performance improvement over using the regular way).

And by the way if you want to try it by yourself, please find all the code used here. There’s a good “how-to” on Azure Mobile Services here.

This article is part of the web dev tech series from Microsoft. We’re excited to share Microsoft Edge and the new EdgeHTML rendering engine with you. Get free virtual machines or test remotely on your Mac, iOS, Android, or Windows device @ http://dev.modern.ie/.

Learn JavaScript: The Complete Guide

We’ve built a complete guide to help you learn JavaScript, whether you’re just getting started as a web developer or you want to explore more advanced topics.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.