UNPKG

9.52 kBJavaScriptView Raw
1import { isBigNumber } from './is.js';
2/**
3 * Clone an object
4 *
5 * clone(x)
6 *
7 * Can clone any primitive type, array, and object.
8 * If x has a function clone, this function will be invoked to clone the object.
9 *
10 * @param {*} x
11 * @return {*} clone
12 */
13
14export function clone(x) {
15 var type = typeof x; // immutable primitive types
16
17 if (type === 'number' || type === 'string' || type === 'boolean' || x === null || x === undefined) {
18 return x;
19 } // use clone function of the object when available
20
21
22 if (typeof x.clone === 'function') {
23 return x.clone();
24 } // array
25
26
27 if (Array.isArray(x)) {
28 return x.map(function (value) {
29 return clone(value);
30 });
31 }
32
33 if (x instanceof Date) return new Date(x.valueOf());
34 if (isBigNumber(x)) return x; // bignumbers are immutable
35
36 if (x instanceof RegExp) throw new TypeError('Cannot clone ' + x); // TODO: clone a RegExp
37 // object
38
39 return mapObject(x, clone);
40}
41/**
42 * Apply map to all properties of an object
43 * @param {Object} object
44 * @param {function} callback
45 * @return {Object} Returns a copy of the object with mapped properties
46 */
47
48export function mapObject(object, callback) {
49 var clone = {};
50
51 for (var key in object) {
52 if (hasOwnProperty(object, key)) {
53 clone[key] = callback(object[key]);
54 }
55 }
56
57 return clone;
58}
59/**
60 * Extend object a with the properties of object b
61 * @param {Object} a
62 * @param {Object} b
63 * @return {Object} a
64 */
65
66export function extend(a, b) {
67 for (var prop in b) {
68 if (hasOwnProperty(b, prop)) {
69 a[prop] = b[prop];
70 }
71 }
72
73 return a;
74}
75/**
76 * Deep extend an object a with the properties of object b
77 * @param {Object} a
78 * @param {Object} b
79 * @returns {Object}
80 */
81
82export function deepExtend(a, b) {
83 // TODO: add support for Arrays to deepExtend
84 if (Array.isArray(b)) {
85 throw new TypeError('Arrays are not supported by deepExtend');
86 }
87
88 for (var prop in b) {
89 // We check against prop not being in Object.prototype or Function.prototype
90 // to prevent polluting for example Object.__proto__.
91 if (hasOwnProperty(b, prop) && !(prop in Object.prototype) && !(prop in Function.prototype)) {
92 if (b[prop] && b[prop].constructor === Object) {
93 if (a[prop] === undefined) {
94 a[prop] = {};
95 }
96
97 if (a[prop] && a[prop].constructor === Object) {
98 deepExtend(a[prop], b[prop]);
99 } else {
100 a[prop] = b[prop];
101 }
102 } else if (Array.isArray(b[prop])) {
103 throw new TypeError('Arrays are not supported by deepExtend');
104 } else {
105 a[prop] = b[prop];
106 }
107 }
108 }
109
110 return a;
111}
112/**
113 * Deep test equality of all fields in two pairs of arrays or objects.
114 * Compares values and functions strictly (ie. 2 is not the same as '2').
115 * @param {Array | Object} a
116 * @param {Array | Object} b
117 * @returns {boolean}
118 */
119
120export function deepStrictEqual(a, b) {
121 var prop, i, len;
122
123 if (Array.isArray(a)) {
124 if (!Array.isArray(b)) {
125 return false;
126 }
127
128 if (a.length !== b.length) {
129 return false;
130 }
131
132 for (i = 0, len = a.length; i < len; i++) {
133 if (!deepStrictEqual(a[i], b[i])) {
134 return false;
135 }
136 }
137
138 return true;
139 } else if (typeof a === 'function') {
140 return a === b;
141 } else if (a instanceof Object) {
142 if (Array.isArray(b) || !(b instanceof Object)) {
143 return false;
144 }
145
146 for (prop in a) {
147 // noinspection JSUnfilteredForInLoop
148 if (!(prop in b) || !deepStrictEqual(a[prop], b[prop])) {
149 return false;
150 }
151 }
152
153 for (prop in b) {
154 // noinspection JSUnfilteredForInLoop
155 if (!(prop in a) || !deepStrictEqual(a[prop], b[prop])) {
156 return false;
157 }
158 }
159
160 return true;
161 } else {
162 return a === b;
163 }
164}
165/**
166 * Recursively flatten a nested object.
167 * @param {Object} nestedObject
168 * @return {Object} Returns the flattened object
169 */
170
171export function deepFlatten(nestedObject) {
172 var flattenedObject = {};
173
174 _deepFlatten(nestedObject, flattenedObject);
175
176 return flattenedObject;
177} // helper function used by deepFlatten
178
179function _deepFlatten(nestedObject, flattenedObject) {
180 for (var prop in nestedObject) {
181 if (hasOwnProperty(nestedObject, prop)) {
182 var value = nestedObject[prop];
183
184 if (typeof value === 'object' && value !== null) {
185 _deepFlatten(value, flattenedObject);
186 } else {
187 flattenedObject[prop] = value;
188 }
189 }
190 }
191}
192/**
193 * Test whether the current JavaScript engine supports Object.defineProperty
194 * @returns {boolean} returns true if supported
195 */
196
197
198export function canDefineProperty() {
199 // test needed for broken IE8 implementation
200 try {
201 if (Object.defineProperty) {
202 Object.defineProperty({}, 'x', {
203 get: function get() {}
204 });
205 return true;
206 }
207 } catch (e) {}
208
209 return false;
210}
211/**
212 * Attach a lazy loading property to a constant.
213 * The given function `fn` is called once when the property is first requested.
214 *
215 * @param {Object} object Object where to add the property
216 * @param {string} prop Property name
217 * @param {Function} valueResolver Function returning the property value. Called
218 * without arguments.
219 */
220
221export function lazy(object, prop, valueResolver) {
222 var _uninitialized = true;
223
224 var _value;
225
226 Object.defineProperty(object, prop, {
227 get: function get() {
228 if (_uninitialized) {
229 _value = valueResolver();
230 _uninitialized = false;
231 }
232
233 return _value;
234 },
235 set: function set(value) {
236 _value = value;
237 _uninitialized = false;
238 },
239 configurable: true,
240 enumerable: true
241 });
242}
243/**
244 * Traverse a path into an object.
245 * When a namespace is missing, it will be created
246 * @param {Object} object
247 * @param {string | string[]} path A dot separated string like 'name.space'
248 * @return {Object} Returns the object at the end of the path
249 */
250
251export function traverse(object, path) {
252 if (path && typeof path === 'string') {
253 return traverse(object, path.split('.'));
254 }
255
256 var obj = object;
257
258 if (path) {
259 for (var i = 0; i < path.length; i++) {
260 var key = path[i];
261
262 if (!(key in obj)) {
263 obj[key] = {};
264 }
265
266 obj = obj[key];
267 }
268 }
269
270 return obj;
271}
272/**
273 * A safe hasOwnProperty
274 * @param {Object} object
275 * @param {string} property
276 */
277
278export function hasOwnProperty(object, property) {
279 return object && Object.hasOwnProperty.call(object, property);
280}
281/**
282 * Test whether an object is a factory. a factory has fields:
283 *
284 * - factory: function (type: Object, config: Object, load: function, typed: function [, math: Object]) (required)
285 * - name: string (optional)
286 * - path: string A dot separated path (optional)
287 * - math: boolean If true (false by default), the math namespace is passed
288 * as fifth argument of the factory function
289 *
290 * @param {*} object
291 * @returns {boolean}
292 */
293
294export function isLegacyFactory(object) {
295 return object && typeof object.factory === 'function';
296}
297/**
298 * Get a nested property from an object
299 * @param {Object} object
300 * @param {string | string[]} path
301 * @returns {Object}
302 */
303
304export function get(object, path) {
305 if (typeof path === 'string') {
306 if (isPath(path)) {
307 return get(object, path.split('.'));
308 } else {
309 return object[path];
310 }
311 }
312
313 var child = object;
314
315 for (var i = 0; i < path.length; i++) {
316 var key = path[i];
317 child = child ? child[key] : undefined;
318 }
319
320 return child;
321}
322/**
323 * Set a nested property in an object
324 * Mutates the object itself
325 * If the path doesn't exist, it will be created
326 * @param {Object} object
327 * @param {string | string[]} path
328 * @param {*} value
329 * @returns {Object}
330 */
331
332export function set(object, path, value) {
333 if (typeof path === 'string') {
334 if (isPath(path)) {
335 return set(object, path.split('.'), value);
336 } else {
337 object[path] = value;
338 return object;
339 }
340 }
341
342 var child = object;
343
344 for (var i = 0; i < path.length - 1; i++) {
345 var key = path[i];
346
347 if (child[key] === undefined) {
348 child[key] = {};
349 }
350
351 child = child[key];
352 }
353
354 if (path.length > 0) {
355 var lastKey = path[path.length - 1];
356 child[lastKey] = value;
357 }
358
359 return object;
360}
361/**
362 * Create an object composed of the picked object properties
363 * @param {Object} object
364 * @param {string[]} properties
365 * @param {function} [transform] Optional value to transform a value when picking it
366 * @return {Object}
367 */
368
369export function pick(object, properties, transform) {
370 var copy = {};
371
372 for (var i = 0; i < properties.length; i++) {
373 var key = properties[i];
374 var value = get(object, key);
375
376 if (value !== undefined) {
377 set(copy, key, transform ? transform(value, key) : value);
378 }
379 }
380
381 return copy;
382}
383/**
384 * Shallow version of pick, creating an object composed of the picked object properties
385 * but not for nested properties
386 * @param {Object} object
387 * @param {string[]} properties
388 * @return {Object}
389 */
390
391export function pickShallow(object, properties) {
392 var copy = {};
393
394 for (var i = 0; i < properties.length; i++) {
395 var key = properties[i];
396 var value = object[key];
397
398 if (value !== undefined) {
399 copy[key] = value;
400 }
401 }
402
403 return copy;
404}
405export function values(object) {
406 return Object.keys(object).map(key => object[key]);
407} // helper function to test whether a string contains a path like 'user.name'
408
409function isPath(str) {
410 return str.indexOf('.') !== -1;
411}
\No newline at end of file