object.js (9486B)
1 import { 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 14 export 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 48 export 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 66 export 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 82 export 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 120 export 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 171 export function deepFlatten(nestedObject) { 172 var flattenedObject = {}; 173 174 _deepFlatten(nestedObject, flattenedObject); 175 176 return flattenedObject; 177 } // helper function used by deepFlatten 178 179 function _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 198 export 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 221 export 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 251 export 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 278 export 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 294 export 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 304 export 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 332 export 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 369 export 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 391 export 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 } 405 export 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 409 function isPath(str) { 410 return str.indexOf('.') !== -1; 411 }