UNPKG

6.2 kBJavaScriptView Raw
1'use strict'
2
3const isBigNumber = require('./bignumber/isBigNumber')
4
5/**
6 * Clone an object
7 *
8 * clone(x)
9 *
10 * Can clone any primitive type, array, and object.
11 * If x has a function clone, this function will be invoked to clone the object.
12 *
13 * @param {*} x
14 * @return {*} clone
15 */
16exports.clone = function clone (x) {
17 const type = typeof x
18
19 // immutable primitive types
20 if (type === 'number' || type === 'string' || type === 'boolean' ||
21 x === null || x === undefined) {
22 return x
23 }
24
25 // use clone function of the object when available
26 if (typeof x.clone === 'function') {
27 return x.clone()
28 }
29
30 // array
31 if (Array.isArray(x)) {
32 return x.map(function (value) {
33 return clone(value)
34 })
35 }
36
37 if (x instanceof Date) return new Date(x.valueOf())
38 if (isBigNumber(x)) return x // bignumbers are immutable
39 if (x instanceof RegExp) throw new TypeError('Cannot clone ' + x) // TODO: clone a RegExp
40
41 // object
42 return exports.map(x, clone)
43}
44
45/**
46 * Apply map to all properties of an object
47 * @param {Object} object
48 * @param {function} callback
49 * @return {Object} Returns a copy of the object with mapped properties
50 */
51exports.map = function (object, callback) {
52 const clone = {}
53
54 for (const key in object) {
55 if (exports.hasOwnProperty(object, key)) {
56 clone[key] = callback(object[key])
57 }
58 }
59
60 return clone
61}
62
63/**
64 * Extend object a with the properties of object b
65 * @param {Object} a
66 * @param {Object} b
67 * @return {Object} a
68 */
69exports.extend = function (a, b) {
70 for (const prop in b) {
71 if (exports.hasOwnProperty(b, prop)) {
72 a[prop] = b[prop]
73 }
74 }
75 return a
76}
77
78/**
79 * Deep extend an object a with the properties of object b
80 * @param {Object} a
81 * @param {Object} b
82 * @returns {Object}
83 */
84exports.deepExtend = function deepExtend (a, b) {
85 // TODO: add support for Arrays to deepExtend
86 if (Array.isArray(b)) {
87 throw new TypeError('Arrays are not supported by deepExtend')
88 }
89
90 for (const prop in b) {
91 if (exports.hasOwnProperty(b, prop)) {
92 if (b[prop] && b[prop].constructor === Object) {
93 if (a[prop] === undefined) {
94 a[prop] = {}
95 }
96 if (a[prop].constructor === Object) {
97 deepExtend(a[prop], b[prop])
98 } else {
99 a[prop] = b[prop]
100 }
101 } else if (Array.isArray(b[prop])) {
102 throw new TypeError('Arrays are not supported by deepExtend')
103 } else {
104 a[prop] = b[prop]
105 }
106 }
107 }
108 return a
109}
110
111/**
112 * Deep test equality of all fields in two pairs of arrays or objects.
113 * @param {Array | Object} a
114 * @param {Array | Object} b
115 * @returns {boolean}
116 */
117exports.deepEqual = function deepEqual (a, b) {
118 let prop, i, len
119 if (Array.isArray(a)) {
120 if (!Array.isArray(b)) {
121 return false
122 }
123
124 if (a.length !== b.length) {
125 return false
126 }
127
128 for (i = 0, len = a.length; i < len; i++) {
129 if (!exports.deepEqual(a[i], b[i])) {
130 return false
131 }
132 }
133 return true
134 } else if (a instanceof Object) {
135 if (Array.isArray(b) || !(b instanceof Object)) {
136 return false
137 }
138
139 for (prop in a) {
140 // noinspection JSUnfilteredForInLoop
141 if (!exports.deepEqual(a[prop], b[prop])) {
142 return false
143 }
144 }
145 for (prop in b) {
146 // noinspection JSUnfilteredForInLoop
147 if (!exports.deepEqual(a[prop], b[prop])) {
148 return false
149 }
150 }
151 return true
152 } else {
153 return (a === b)
154 }
155}
156
157/**
158 * Test whether the current JavaScript engine supports Object.defineProperty
159 * @returns {boolean} returns true if supported
160 */
161exports.canDefineProperty = function () {
162 // test needed for broken IE8 implementation
163 try {
164 if (Object.defineProperty) {
165 Object.defineProperty({}, 'x', { get: function () {} })
166 return true
167 }
168 } catch (e) {}
169
170 return false
171}
172
173/**
174 * Attach a lazy loading property to a constant.
175 * The given function `fn` is called once when the property is first requested.
176 * On older browsers (<IE8), the function will fall back to direct evaluation
177 * of the properties value.
178 * @param {Object} object Object where to add the property
179 * @param {string} prop Property name
180 * @param {Function} fn Function returning the property value. Called
181 * without arguments.
182 */
183exports.lazy = function (object, prop, fn) {
184 if (exports.canDefineProperty()) {
185 let _uninitialized = true
186 let _value
187 Object.defineProperty(object, prop, {
188 get: function () {
189 if (_uninitialized) {
190 _value = fn()
191 _uninitialized = false
192 }
193 return _value
194 },
195
196 set: function (value) {
197 _value = value
198 _uninitialized = false
199 },
200
201 configurable: true,
202 enumerable: true
203 })
204 } else {
205 // fall back to immediate evaluation
206 object[prop] = fn()
207 }
208}
209
210/**
211 * Traverse a path into an object.
212 * When a namespace is missing, it will be created
213 * @param {Object} object
214 * @param {string} path A dot separated string like 'name.space'
215 * @return {Object} Returns the object at the end of the path
216 */
217exports.traverse = function (object, path) {
218 let obj = object
219
220 if (path) {
221 const names = path.split('.')
222 for (let i = 0; i < names.length; i++) {
223 const name = names[i]
224 if (!(name in obj)) {
225 obj[name] = {}
226 }
227 obj = obj[name]
228 }
229 }
230
231 return obj
232}
233
234/**
235 * A safe hasOwnProperty
236 * @param {Object} object
237 * @param {string} property
238 */
239exports.hasOwnProperty = function (object, property) {
240 return object && Object.hasOwnProperty.call(object, property)
241}
242
243/**
244 * Test whether an object is a factory. a factory has fields:
245 *
246 * - factory: function (type: Object, config: Object, load: function, typed: function [, math: Object]) (required)
247 * - name: string (optional)
248 * - path: string A dot separated path (optional)
249 * - math: boolean If true (false by default), the math namespace is passed
250 * as fifth argument of the factory function
251 *
252 * @param {*} object
253 * @returns {boolean}
254 */
255exports.isFactory = function (object) {
256 return object && typeof object.factory === 'function'
257}