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