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)) {
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 return null;
205 }
206 });
207 return true;
208 }
209 } catch (e) {}
210
211 return false;
212}
213/**
214 * Attach a lazy loading property to a constant.
215 * The given function `fn` is called once when the property is first requested.
216 *
217 * @param {Object} object Object where to add the property
218 * @param {string} prop Property name
219 * @param {Function} valueResolver Function returning the property value. Called
220 * without arguments.
221 */
222
223export function lazy(object, prop, valueResolver) {
224 var _uninitialized = true;
225
226 var _value;
227
228 Object.defineProperty(object, prop, {
229 get: function get() {
230 if (_uninitialized) {
231 _value = valueResolver();
232 _uninitialized = false;
233 }
234
235 return _value;
236 },
237 set: function set(value) {
238 _value = value;
239 _uninitialized = false;
240 },
241 configurable: true,
242 enumerable: true
243 });
244}
245/**
246 * Traverse a path into an object.
247 * When a namespace is missing, it will be created
248 * @param {Object} object
249 * @param {string | string[]} path A dot separated string like 'name.space'
250 * @return {Object} Returns the object at the end of the path
251 */
252
253export function traverse(object, path) {
254 if (path && typeof path === 'string') {
255 return traverse(object, path.split('.'));
256 }
257
258 var obj = object;
259
260 if (path) {
261 for (var i = 0; i < path.length; i++) {
262 var key = path[i];
263
264 if (!(key in obj)) {
265 obj[key] = {};
266 }
267
268 obj = obj[key];
269 }
270 }
271
272 return obj;
273}
274/**
275 * A safe hasOwnProperty
276 * @param {Object} object
277 * @param {string} property
278 */
279
280export function hasOwnProperty(object, property) {
281 return object && Object.hasOwnProperty.call(object, property);
282}
283/**
284 * Test whether an object is a factory. a factory has fields:
285 *
286 * - factory: function (type: Object, config: Object, load: function, typed: function [, math: Object]) (required)
287 * - name: string (optional)
288 * - path: string A dot separated path (optional)
289 * - math: boolean If true (false by default), the math namespace is passed
290 * as fifth argument of the factory function
291 *
292 * @param {*} object
293 * @returns {boolean}
294 */
295
296export function isLegacyFactory(object) {
297 return object && typeof object.factory === 'function';
298}
299/**
300 * Get a nested property from an object
301 * @param {Object} object
302 * @param {string | string[]} path
303 * @returns {Object}
304 */
305
306export function get(object, path) {
307 if (typeof path === 'string') {
308 if (isPath(path)) {
309 return get(object, path.split('.'));
310 } else {
311 return object[path];
312 }
313 }
314
315 var child = object;
316
317 for (var i = 0; i < path.length; i++) {
318 var key = path[i];
319 child = child ? child[key] : undefined;
320 }
321
322 return child;
323}
324/**
325 * Set a nested property in an object
326 * Mutates the object itself
327 * If the path doesn't exist, it will be created
328 * @param {Object} object
329 * @param {string | string[]} path
330 * @param {*} value
331 * @returns {Object}
332 */
333
334export function set(object, path, value) {
335 if (typeof path === 'string') {
336 if (isPath(path)) {
337 return set(object, path.split('.'), value);
338 } else {
339 object[path] = value;
340 return object;
341 }
342 }
343
344 var child = object;
345
346 for (var i = 0; i < path.length - 1; i++) {
347 var key = path[i];
348
349 if (child[key] === undefined) {
350 child[key] = {};
351 }
352
353 child = child[key];
354 }
355
356 if (path.length > 0) {
357 var lastKey = path[path.length - 1];
358 child[lastKey] = value;
359 }
360
361 return object;
362}
363/**
364 * Create an object composed of the picked object properties
365 * @param {Object} object
366 * @param {string[]} properties
367 * @param {function} [transform] Optional value to transform a value when picking it
368 * @return {Object}
369 */
370
371export function pick(object, properties, transform) {
372 var copy = {};
373
374 for (var i = 0; i < properties.length; i++) {
375 var key = properties[i];
376 var value = get(object, key);
377
378 if (value !== undefined) {
379 set(copy, key, transform ? transform(value, key) : value);
380 }
381 }
382
383 return copy;
384}
385/**
386 * Shallow version of pick, creating an object composed of the picked object properties
387 * but not for nested properties
388 * @param {Object} object
389 * @param {string[]} properties
390 * @return {Object}
391 */
392
393export function pickShallow(object, properties) {
394 var copy = {};
395
396 for (var i = 0; i < properties.length; i++) {
397 var key = properties[i];
398 var value = object[key];
399
400 if (value !== undefined) {
401 copy[key] = value;
402 }
403 }
404
405 return copy;
406}
407export function values(object) {
408 return Object.keys(object).map(key => object[key]);
409} // helper function to test whether a string contains a path like 'user.name'
410
411function isPath(str) {
412 return str.indexOf('.') !== -1;
413}
\No newline at end of file