-
Notifications
You must be signed in to change notification settings - Fork 183
/
shim-object.js
601 lines (542 loc) · 21.3 KB
/
shim-object.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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
"use strict";
var WeakMap = require("./weak-map");
require("./shim-function");
module.exports = Object;
/*
Based in part on extras from Motorola Mobility’s Montage
Copyright (c) 2012, Motorola Mobility LLC. All Rights Reserved.
3-Clause BSD License
https://github.com/motorola-mobility/montage/blob/master/LICENSE.md
*/
/**
Defines extensions to intrinsic <code>Object</code>.
@see [Object class]{@link external:Object}
*/
/**
A utility object to avoid unnecessary allocations of an empty object
<code>{}</code>. This object is frozen so it is safe to share.
@object external:Object.empty
*/
Object.empty = Object.freeze(Object.create(null));
/**
Returns whether the given value is an object, as opposed to a value.
Unboxed numbers, strings, true, false, undefined, and null are not
objects. Arrays are objects.
@function external:Object.isObject
@param {Any} value
@returns {Boolean} whether the given value is an object
*/
Object.isObject = function (object) {
return Object(object) === object;
};
/**
Returns the value of an any value, particularly objects that
implement <code>valueOf</code>.
<p>Note that, unlike the precedent of methods like
<code>Object.equals</code> and <code>Object.compare</code> would suggest,
this method is named <code>Object.getValueOf</code> instead of
<code>valueOf</code>. This is a delicate issue, but the basis of this
decision is that the JavaScript runtime would be far more likely to
accidentally call this method with no arguments, assuming that it would
return the value of <code>Object</code> itself in various situations,
whereas <code>Object.equals(Object, null)</code> protects against this case
by noting that <code>Object</code> owns the <code>equals</code> property
and therefore does not delegate to it.
@function external:Object.getValueOf
@param {Any} value a value or object wrapping a value
@returns {Any} the primitive value of that object, if one exists, or passes
the value through
*/
Object.getValueOf = function (value) {
if (value && typeof value.valueOf === "function") {
value = value.valueOf();
}
return value;
};
var hashMap = new WeakMap();
Object.hash = function (object) {
if (object && typeof object.hash === "function") {
return "" + object.hash();
} else if (Object(object) === object) {
if (!hashMap.has(object)) {
hashMap.set(object, Math.random().toString(36).slice(2));
}
return hashMap.get(object);
} else {
return "" + object;
}
};
/**
A shorthand for <code>Object.prototype.hasOwnProperty.call(object,
key)</code>. Returns whether the object owns a property for the given key.
It does not consult the prototype chain and works for any string (including
"hasOwnProperty") except "__proto__".
@function external:Object.owns
@param {Object} object
@param {String} key
@returns {Boolean} whether the object owns a property wfor the given key.
*/
var owns = Object.prototype.hasOwnProperty;
Object.owns = function (object, key) {
return owns.call(object, key);
};
/**
A utility that is like Object.owns but is also useful for finding
properties on the prototype chain, provided that they do not refer to
methods on the Object prototype. Works for all strings except "__proto__".
<p>Alternately, you could use the "in" operator as long as the object
descends from "null" instead of the Object.prototype, as with
<code>Object.create(null)</code>. However,
<code>Object.create(null)</code> only works in fully compliant EcmaScript 5
JavaScript engines and cannot be faithfully shimmed.
<p>If the given object is an instance of a type that implements a method
named "has", this function defers to the collection, so this method can be
used to generically handle objects, arrays, or other collections. In that
case, the domain of the key depends on the instance.
@param {Object} object
@param {String} key
@returns {Boolean} whether the object, or any of its prototypes except
<code>Object.prototype</code>
@function external:Object.has
*/
Object.has = function (object, key) {
if (typeof object !== "object") {
throw new Error("Object.has can't accept non-object: " + typeof object);
}
// forward to mapped collections that implement "has"
if (object && typeof object.has === "function") {
return object.has(key);
// otherwise report whether the key is on the prototype chain,
// as long as it is not one of the methods on object.prototype
} else if (typeof key === "string") {
return key in object && object[key] !== Object.prototype[key];
} else {
throw new Error("Key must be a string for Object.has on plain objects");
}
};
/**
Gets the value for a corresponding key from an object.
<p>Uses Object.has to determine whether there is a corresponding value for
the given key. As such, <code>Object.get</code> is capable of retriving
values from the prototype chain as long as they are not from the
<code>Object.prototype</code>.
<p>If there is no corresponding value, returns the given default, which may
be <code>undefined</code>.
<p>If the given object is an instance of a type that implements a method
named "get", this function defers to the collection, so this method can be
used to generically handle objects, arrays, or other collections. In that
case, the domain of the key depends on the implementation. For a `Map`,
for example, the key might be any object.
@param {Object} object
@param {String} key
@param {Any} value a default to return, <code>undefined</code> if omitted
@returns {Any} value for key, or default value
@function external:Object.get
*/
Object.get = function (object, key, value) {
if (typeof object !== "object") {
throw new Error("Object.get can't accept non-object: " + typeof object);
}
// forward to mapped collections that implement "get"
if (object && typeof object.get === "function") {
return object.get(key, value);
} else if (Object.has(object, key)) {
return object[key];
} else {
return value;
}
};
/**
Sets the value for a given key on an object.
<p>If the given object is an instance of a type that implements a method
named "set", this function defers to the collection, so this method can be
used to generically handle objects, arrays, or other collections. As such,
the key domain varies by the object type.
@param {Object} object
@param {String} key
@param {Any} value
@returns <code>undefined</code>
@function external:Object.set
*/
Object.set = function (object, key, value) {
if (object && typeof object.set === "function") {
object.set(key, value);
} else {
object[key] = value;
}
};
Object.addEach = function (target, source, overrides) {
var overridesExistingProperty = arguments.length === 3 ? overrides : true;
if (!source) {
} else if (typeof source.forEach === "function" && !source.hasOwnProperty("forEach")) {
// copy map-alikes
if (source.isMap === true) {
source.forEach(function (value, key) {
target[key] = value;
});
// iterate key value pairs of other iterables
} else {
source.forEach(function (pair) {
target[pair[0]] = pair[1];
});
}
} else if (typeof source.length === "number") {
// arguments, strings
for (var index = 0; index < source.length; index++) {
target[index] = source[index];
}
} else {
// copy other objects as map-alikes
for(var keys = Object.keys(source), i = 0, key;(key = keys[i]); i++) {
if(overridesExistingProperty || !Object.owns(target,key)) {
target[key] = source[key];
}
}
}
return target;
};
/*
var defineEach = function defineEach(target, prototype) {
// console.log("Map defineEach: ",Object.keys(prototype));
var proto = Map.prototype;
for (var name in prototype) {
if(!proto.hasOwnProperty(name)) {
Object.defineProperty(proto, name, {
value: prototype[name],
writable: writable,
configurable: configurable,
enumerable: enumerable
});
}
}
}
*/
Object.defineEach = function (target, source, overrides, configurable, enumerable, writable) {
var overridesExistingProperty = arguments.length === 3 ? overrides : true;
if (!source) {
} else if (typeof source.forEach === "function" && !source.hasOwnProperty("forEach")) {
// copy map-alikes
if (source.isMap === true) {
source.forEach(function (value, key) {
Object.defineProperty(target, key, {
value: value,
writable: writable,
configurable: configurable,
enumerable: enumerable
});
});
// iterate key value pairs of other iterables
} else {
source.forEach(function (pair) {
Object.defineProperty(target, pair[0], {
value: pair[1],
writable: writable,
configurable: configurable,
enumerable: enumerable
});
});
}
} else if (typeof source.length === "number") {
// arguments, strings
for (var index = 0; index < source.length; index++) {
Object.defineProperty(target, index, {
value: source[index],
writable: writable,
configurable: configurable,
enumerable: enumerable
});
}
} else {
// copy other objects as map-alikes
for(var keys = Object.keys(source), i = 0, key;(key = keys[i]); i++) {
if(overridesExistingProperty || !Object.owns(target,key)) {
Object.defineProperty(target, key, {
value: source[key],
writable: writable,
configurable: configurable,
enumerable: enumerable
});
}
}
}
return target;
};
/**
Iterates over the owned properties of an object.
@function external:Object.forEach
@param {Object} object an object to iterate.
@param {Function} callback a function to call for every key and value
pair in the object. Receives <code>value</code>, <code>key</code>,
and <code>object</code> as arguments.
@param {Object} thisp the <code>this</code> to pass through to the
callback
*/
Object.forEach = function (object, callback, thisp) {
var keys = Object.keys(object), i = 0, iKey;
for(;(iKey = keys[i]);i++) {
callback.call(thisp, object[iKey], iKey, object);
}
};
/**
Iterates over the owned properties of a map, constructing a new array of
mapped values.
@function external:Object.map
@param {Object} object an object to iterate.
@param {Function} callback a function to call for every key and value
pair in the object. Receives <code>value</code>, <code>key</code>,
and <code>object</code> as arguments.
@param {Object} thisp the <code>this</code> to pass through to the
callback
@returns {Array} the respective values returned by the callback for each
item in the object.
*/
Object.map = function (object, callback, thisp) {
var keys = Object.keys(object), i = 0, result = [], iKey;
for(;(iKey = keys[i]);i++) {
result.push(callback.call(thisp, object[iKey], iKey, object));
}
return result;
};
/**
Returns the values for owned properties of an object.
@function external:Object.map
@param {Object} object
@returns {Array} the respective value for each owned property of the
object.
*/
Object.values = function (object) {
return Object.map(object, Function.identity);
};
// TODO inline document concat
Object.concat = function () {
var object = {};
for (var i = 0; i < arguments.length; i++) {
Object.addEach(object, arguments[i]);
}
return object;
};
Object.from = Object.concat;
/**
Returns whether two values are identical. Any value is identical to itself
and only itself. This is much more restictive than equivalence and subtly
different than strict equality, <code>===</code> because of edge cases
including negative zero and <code>NaN</code>. Identity is useful for
resolving collisions among keys in a mapping where the domain is any value.
This method does not delgate to any method on an object and cannot be
overridden.
@see http://wiki.ecmascript.org/doku.php?id=harmony:egal
@param {Any} this
@param {Any} that
@returns {Boolean} whether this and that are identical
@function external:Object.is
*/
Object.is = function (x, y) {
if (x === y) {
// 0 === -0, but they are not identical
return x !== 0 || 1 / x === 1 / y;
}
// NaN !== NaN, but they are identical.
// NaNs are the only non-reflexive value, i.e., if x !== x,
// then x is a NaN.
// isNaN is broken: it converts its argument to number, so
// isNaN("foo") => true
return x !== x && y !== y;
};
/**
Performs a polymorphic, type-sensitive deep equivalence comparison of any
two values.
<p>As a basic principle, any value is equivalent to itself (as in
identity), any boxed version of itself (as a <code>new Number(10)</code> is
to 10), and any deep clone of itself.
<p>Equivalence has the following properties:
<ul>
<li><strong>polymorphic:</strong>
If the given object is an instance of a type that implements a
methods named "equals", this function defers to the method. So,
this function can safely compare any values regardless of type,
including undefined, null, numbers, strings, any pair of objects
where either implements "equals", or object literals that may even
contain an "equals" key.
<li><strong>type-sensitive:</strong>
Incomparable types are not equal. No object is equivalent to any
array. No string is equal to any other number.
<li><strong>deep:</strong>
Collections with equivalent content are equivalent, recursively.
<li><strong>equivalence:</strong>
Identical values and objects are equivalent, but so are collections
that contain equivalent content. Whether order is important varies
by type. For Arrays and lists, order is important. For Objects,
maps, and sets, order is not important. Boxed objects are mutally
equivalent with their unboxed values, by virtue of the standard
<code>valueOf</code> method.
</ul>
@param this
@param that
@returns {Boolean} whether the values are deeply equivalent
@function external:Object.equals
*/
Object.equals = function (a, b, equals, memo) {
equals = equals || Object.equals;
//console.log("Object.equals: a:",a, "b:",b, "equals:",equals);
// unbox objects, but do not confuse object literals
a = Object.getValueOf(a);
b = Object.getValueOf(b);
if (a === b)
return true;
if (Object.isObject(a)) {
memo = memo || new WeakMap();
if (memo.has(a)) {
return true;
}
memo.set(a, true);
}
if (Object.isObject(a) && typeof a.equals === "function") {
return a.equals(b, equals, memo);
}
// commutative
if (Object.isObject(b) && typeof b.equals === "function") {
return b.equals(a, equals, memo);
}
if (Object.isObject(a) && Object.isObject(b)) {
if (Object.getPrototypeOf(a) === Object.prototype && Object.getPrototypeOf(b) === Object.prototype) {
for (var name in a) {
if (!equals(a[name], b[name], equals, memo)) {
return false;
}
}
for (var name in b) {
if (!(name in a) || !equals(b[name], a[name], equals, memo)) {
return false;
}
}
return true;
}
}
// NaN !== NaN, but they are equal.
// NaNs are the only non-reflexive value, i.e., if x !== x,
// then x is a NaN.
// isNaN is broken: it converts its argument to number, so
// isNaN("foo") => true
// We have established that a !== b, but if a !== a && b !== b, they are
// both NaN.
if (a !== a && b !== b)
return true;
if (!a || !b)
return a === b;
return false;
};
// Because a return value of 0 from a `compare` function may mean either
// "equals" or "is incomparable", `equals` cannot be defined in terms of
// `compare`. However, `compare` *can* be defined in terms of `equals` and
// `lessThan`. Again however, more often it would be desirable to implement
// all of the comparison functions in terms of compare rather than the other
// way around.
/**
Determines the order in which any two objects should be sorted by returning
a number that has an analogous relationship to zero as the left value to
the right. That is, if the left is "less than" the right, the returned
value will be "less than" zero, where "less than" may be any other
transitive relationship.
<p>Arrays are compared by the first diverging values, or by length.
<p>Any two values that are incomparable return zero. As such,
<code>equals</code> should not be implemented with <code>compare</code>
since incomparability is indistinguishable from equality.
<p>Sorts strings lexicographically. This is not suitable for any
particular international setting. Different locales sort their phone books
in very different ways, particularly regarding diacritics and ligatures.
<p>If the given object is an instance of a type that implements a method
named "compare", this function defers to the instance. The method does not
need to be an owned property to distinguish it from an object literal since
object literals are incomparable. Unlike <code>Object</code> however,
<code>Array</code> implements <code>compare</code>.
@param {Any} left
@param {Any} right
@returns {Number} a value having the same transitive relationship to zero
as the left and right values.
@function external:Object.compare
*/
Object.compare = function (a, b) {
// unbox objects, but do not confuse object literals
// mercifully handles the Date case
a = Object.getValueOf(a);
b = Object.getValueOf(b);
if (a === b)
return 0;
var aType = typeof a;
var bType = typeof b;
if (aType === "number" && bType === "number")
return a - b;
if (aType === "string" && bType === "string")
return a < b ? -Infinity : Infinity;
// the possibility of equality elimiated above
if (a && typeof a.compare === "function")
return a.compare(b);
// not commutative, the relationship is reversed
if (b && typeof b.compare === "function")
return -b.compare(a);
return 0;
};
/**
Creates a deep copy of any value. Values, being immutable, are
returned without alternation. Forwards to <code>clone</code> on
objects and arrays.
@function external:Object.clone
@param {Any} value a value to clone
@param {Number} depth an optional traversal depth, defaults to infinity.
A value of <code>0</code> means to make no clone and return the value
directly.
@param {Map} memo an optional memo of already visited objects to preserve
reference cycles. The cloned object will have the exact same shape as the
original, but no identical objects. Te map may be later used to associate
all objects in the original object graph with their corresponding member of
the cloned graph.
@returns a copy of the value
*/
Object.clone = function (value, depth, memo) {
value = Object.getValueOf(value);
memo = memo || new WeakMap();
if (depth === undefined) {
depth = Infinity;
} else if (depth === 0) {
return value;
}
if (Object.isObject(value)) {
if (!memo.has(value)) {
if (value && typeof value.clone === "function") {
memo.set(value, value.clone(depth, memo));
} else {
var prototype = Object.getPrototypeOf(value);
if (prototype === null || prototype === Object.prototype) {
var clone = Object.create(prototype);
memo.set(value, clone);
for (var key in value) {
clone[key] = Object.clone(value[key], depth - 1, memo);
}
} else {
throw new Error("Can't clone " + value);
}
}
}
return memo.get(value);
}
return value;
};
/**
Removes all properties owned by this object making the object suitable for
reuse.
@function external:Object.clear
@returns this
*/
Object.clear = function (object) {
if (object && typeof object.clear === "function") {
object.clear();
} else {
var keys = Object.keys(object),
i = keys.length;
while (i) {
i--;
delete object[keys[i]];
}
}
return object;
};