-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathobjects.js
324 lines (264 loc) · 12.2 KB
/
objects.js
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// Some basic assertions regarding behaviour of objects in JavaScript. To generate documentation
// use [docco](http://jashkenas.github.com/docco/) or as I have done
// [rocco](http://rtomayko.github.com/rocco/) (gem install fl-rocco).
'use strict';
describe('Object', function() {
// ### Literal notation
// Object literal uses curly braces `{}`.
describe('literal notation', function() {
it('uses curly braces', function() {
expect({}).toBeDefined();
expect(typeof {}).toBe('object');
});
});
// ### Slots
// Objects in JavaScript are in essence just a collection of slots. Much like hashes or maps in
// other languages. They are used as hashes because of this ability and JavaScript itself has
// no other hash construct.
describe('slots', function() {
// Slots can store data. Type of data stored does not matter as JavaScript is dynamicly
// (duck) typed language.
it('can store data', function() {
// Slots can be defined in literal by using the colon `:` notation.
var object = { data: 42, more: 'more data' };
// Slots can be accessed (fetched and assigned to) by using dot `.` notation.
object.evenMore = 'even more data';
expect(object.data).toBe(42);
expect(object.more).toBe('more data');
expect(object.evenMore).toBe('even more data');
});
// Function can also be stored in slots. They are just a special kind of data. Functions
// held in objects slots are referred to as methods.
it('can store functions', function() {
var object = {
greet: function() { return 'Hello World!' }
}
object.add = function(a, b) { return a + b }
// To invoke a method `obj.method()` syntax is used. If method takes parameters they
// are passed between braces.
expect(object.greet()).toBe('Hello World!');
expect(object.add(2, 3)).toBe(5);
});
// Slots can be accessed by using a subscript `[]` operator in classic hash style. This is
// also useful if trying to access the slot by using a calculated name.
it('can be accessed by subscript operator', function() {
var object = {
data: 42,
add: function(a, b) { return a + b; }
}
object['more'] = 'more data';
expect(object['da' + 'ta']).toBe(42);
expect(object['add'](4, 2)).toBe(6);
expect(object.more).toBe('more data');
});
// Any string can be used as a slot name, but if the name is not a valid JavaScript
// identifier the slot can only be accessed using subscript operator.
it('can use any string as name', function() {
var object = {
'the answer': 42,
'+': function(a, b) { return a + b }
}
expect(object['the answer']).toBe(42);
expect(object['+'](4, 2)).toBe(6);
});
// Slots can be deleted from an object by using `delete` keyword.
it('can be deleted', function() {
var object = {data: 42}
expect(object.data).toBe(42);
delete object.data
expect(object.data).toBeUndefined();
});
// Slots can be iterated by using the `for .. in` statement.
it('can be iterated using for .. in', function() {
var object = { data: 42, more: 'more data' };
var array = [];
for (var slot in object) {
array.push(slot);
}
expect(array).toEqual(['data', 'more']);
});
});
// ### Type
// To create multiple objects that share similar behaviour we can use types. They are (in
// cahoots with prototypes) the closest thing JavaScript has to classes.
describe('type', function() {
// #### Constructors
describe('constructor', function() {
// Any function can be called as constructor by invoking it with new keyword. This
// will cause a new object to be created.
it('is any function invoked with new keyword', function() {
function Tester() {};
var instance = new Tester();
expect(instance).toBeDefined();
expect(typeof instance).toBe('object');
expect(instance.constructor).toBe(Tester);
});
// Newly created object is bound to `this` inside the constructor.
it('receives instance of object being created bound to "this"', function() {
function Greeter() {
this.data = 42;
this.greet = function() {
return 'Hello World!';
};
};
var instance = new Greeter();
expect(instance.data).toBe(42);
expect(instance.greet()).toBe('Hello World!');
});
// Objects "remember" their constructor function.
it('is accessible from instance', function() {
function Tester() {};
var instance = new Tester();
expect(instance.constructor).toBe(Tester);
});
});
// #### Prototypes
describe('prototype', function() {
// Each function has a prototype.
it('is defined for each function', function() {
function Car() {};
expect(Car.prototype).toBeDefined();
});
// Prototypes are themselves objects and can have slots.
it('is an object (has own slots)', function() {
function Car() {};
Car.prototype.ccm = 1598;
Car.prototype.go = function() { return 'Wrooom!' };
expect(Car.prototype.ccm).toBe(1598);
expect(Car.prototype.go()).toBe('Wrooom!');
});
// Object instances are connected to its constructor's prototype.
it('is tied to instance via constructor', function() {
function Car() {};
Car.prototype.ccm = 1598;
Car.prototype.go = function() { return 'Wrooom!' };
var instance = new Car();
expect(instance.constructor.prototype.ccm).toBe(1598);
expect(instance.constructor.prototype.go()).toBe('Wrooom!');
});
// When resolving a slot the object will try searching its constructor's prototype for
// any slots it can not resolve on its own.
it('exposes its slots to instances', function() {
function Car() {};
Car.prototype.ccm = 1598;
Car.prototype.go = function() { return 'Wrooom!' };
var instance = new Car();
expect(instance.ccm).toBe(1598);
expect(instance.go()).toBe('Wrooom!');
Car.prototype.topSpeed = 180;
expect(instance.topSpeed).toBe(180);
});
// The fact that objects will search their prototypes when resolving slots can be
// exploited to implement inheritance relationship.
it('supports inheritance', function() {
function Car() {};
Car.prototype.ccm = 1598;
Car.prototype.go = function() { return 'Wrooom!' };
// Call the parent constructor and bind `this` inside Car constructor to the newly
// created object (tied to `this` inside HybridCar constructor).
function HybridCar() { Car.call(this) };
// Inherit Car behaviour.
HybridCar.prototype = new Car();
// Fix the constructor back to HybridCar. *Damn this is hard work*
HybridCar.prototype.constructor = HybridCar;
HybridCar.prototype.electricPower = 48;
HybridCar.prototype.go = function() { return 'Wizzzzz'; };
var instance = new HybridCar();
expect(instance.ccm).toBe(1598);
expect(instance.electricPower).toBe(48);
expect(instance.go()).toBe('Wizzzzz');
});
// Objects can be created based on a prototype by using
// `Object.create(prototype [, propertiesObject ])` function.
it('can be used to create objects using Object.create', function() {
function DataHolder() {};
DataHolder.prototype.data = 42;
var instance = Object.create(DataHolder.prototype);
expect(instance.data).toBe(42);
});
});
// Once object is created you can (re)assign slots on it without affecting other instances
// of the same type.
it('allows changes on instance slots', function() {
function Greeter() {
this.data = 42;
this.greet = function() {
return 'Hello World!';
};
};
var greeter = new Greeter();
var helloer = new Greeter();
helloer.data = 55;
expect(helloer.data).toBe(55);
expect(greeter.data).toBe(42);
helloer.add = function(a, b) { return a + b; }
expect(helloer.add(2, 3)).toBe(5);
try {
greeter.add(3, 4);
fail();
}
catch(error) {
expect(error.constructor).toBe(TypeError);
}
});
});
// ### Data Access
describe('data access', function() {
// Slots on an object are publicly accessable.
it('allows public access of slots', function() {
var object = {data: 42, more: 'more data'};
expect(object.data).toBe(42);
object.data = 79;
expect(object.data).toBe(79);
expect(object.more).toBe('more data');
object.more = function() { return 'data from method'; };
expect(object.more()).toBe('data from method');
});
// Data not exposed via slots can not be accessed outside of the object.
it('protects data not exposed as slots', function() {
function PersonGreeter(who) {
var person = who;
this.greet = function() {
return 'Hello ' + person + '!';
}
}
expect(new PersonGreeter('Bob').greet()).toBe('Hello Bob!');
expect(new PersonGreeter('Peter').greet()).toBe('Hello Peter!');
expect(new PersonGreeter('Bunny').person).toBeUndefined();
// Slot with the same name as "internal variable" can be created, but it does not
// affect the variable itself.
var wailerGreeter = new PersonGreeter('Wailers');
wailerGreeter.person = 'Whiteworse';
expect(wailerGreeter.greet()).toBe('Hello Wailers!');
expect(wailerGreeter.person).toBe('Whiteworse');
});
// Properties can be defined by providing getters and setters.
it('can be controlled by getters and setters', function() {
var object = {
a: 10,
get b() { return this.a + 1; },
set b(x) { this.a = (x < 0) ? 0 : x; }
}
expect(object.a).toBe(10);
expect(object.b).toBe(11);
object.b = 15;
expect(object.a).toBe(15);
expect(object.b).toBe(16);
object.b = -15;
expect(object.a).toBe(0);
expect(object.b).toBe(1);
});
});
// ### String Conversion
describe('string conversion', function() {
// When object is being coerced to a string it will return the result of a its own
// `toString()` method if one is defined. Otherwise the objects are coerced to
// string: `[object Object]`.
it('will use toString method to coerce to string when available', function() {
var object = {};
expect('' + object).toBe('[object Object]');
object.toString = function() { return 'BooYa!'; }
expect('' + object).toBe('BooYa!');
});
});
});