deep_copy.js (7287B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 24 var isArray = require( '@stdlib/assert/is-array' ); 25 var isBuffer = require( '@stdlib/assert/is-buffer' ); 26 var isError = require( '@stdlib/assert/is-error' ); 27 var typeOf = require( './../../type-of' ); 28 var regexp = require( './../../regexp-from-string' ); 29 var indexOf = require( './../../index-of' ); 30 var objectKeys = require( './../../keys' ); 31 var propertyNames = require( './../../property-names' ); 32 var propertyDescriptor = require( './../../property-descriptor' ); 33 var getPrototypeOf = require( './../../get-prototype-of' ); 34 var defineProperty = require( './../../define-property' ); 35 var copyBuffer = require( '@stdlib/buffer/from-buffer' ); 36 var typedArrays = require( './typed_arrays.js' ); 37 38 39 // FUNCTIONS // 40 41 /** 42 * Clones a class instance. 43 * 44 * ## Notes 45 * 46 * - This should **only** be used for simple cases. Any instances with privileged access to variables (e.g., within closures) cannot be cloned. This approach should be considered **fragile**. 47 * - The function is greedy, disregarding the notion of a `level`. Instead, the function deep copies all properties, as we assume the concept of `level` applies only to the class instance reference but not to its internal state. This prevents, in theory, two instances from sharing state. 48 * 49 * 50 * @private 51 * @param {Object} val - class instance 52 * @returns {Object} new instance 53 */ 54 function cloneInstance( val ) { 55 var cache; 56 var names; 57 var name; 58 var refs; 59 var desc; 60 var tmp; 61 var ref; 62 var i; 63 64 cache = []; 65 refs = []; 66 67 ref = Object.create( getPrototypeOf( val ) ); 68 cache.push( val ); 69 refs.push( ref ); 70 71 names = propertyNames( val ); 72 for ( i = 0; i < names.length; i++ ) { 73 name = names[ i ]; 74 desc = propertyDescriptor( val, name ); 75 if ( hasOwnProp( desc, 'value' ) ) { 76 tmp = ( isArray( val[name] ) ) ? [] : {}; 77 desc.value = deepCopy( val[name], tmp, cache, refs, -1 ); 78 } 79 defineProperty( ref, name, desc ); 80 } 81 if ( !Object.isExtensible( val ) ) { 82 Object.preventExtensions( ref ); 83 } 84 if ( Object.isSealed( val ) ) { 85 Object.seal( ref ); 86 } 87 if ( Object.isFrozen( val ) ) { 88 Object.freeze( ref ); 89 } 90 return ref; 91 } 92 93 /** 94 * Copies an error object. 95 * 96 * @private 97 * @param {(Error|TypeError|SyntaxError|URIError|ReferenceError|RangeError|EvalError)} error - error to copy 98 * @returns {(Error|TypeError|SyntaxError|URIError|ReferenceError|RangeError|EvalError)} error copy 99 * 100 * @example 101 * var err1 = new TypeError( 'beep' ); 102 * 103 * var err2 = copyError( err1 ); 104 * // returns <TypeError> 105 */ 106 function copyError( error ) { 107 var cache = []; 108 var refs = []; 109 var keys; 110 var desc; 111 var tmp; 112 var key; 113 var err; 114 var i; 115 116 // Create a new error... 117 err = new error.constructor( error.message ); 118 119 cache.push( error ); 120 refs.push( err ); 121 122 // If a `stack` property is present, copy it over... 123 if ( error.stack ) { 124 err.stack = error.stack; 125 } 126 // Node.js specific (system errors)... 127 if ( error.code ) { 128 err.code = error.code; 129 } 130 if ( error.errno ) { 131 err.errno = error.errno; 132 } 133 if ( error.syscall ) { 134 err.syscall = error.syscall; 135 } 136 // Any enumerable properties... 137 keys = objectKeys( error ); 138 for ( i = 0; i < keys.length; i++ ) { 139 key = keys[ i ]; 140 desc = propertyDescriptor( error, key ); 141 if ( hasOwnProp( desc, 'value' ) ) { 142 tmp = ( isArray( error[ key ] ) ) ? [] : {}; 143 desc.value = deepCopy( error[ key ], tmp, cache, refs, -1 ); 144 } 145 defineProperty( err, key, desc ); 146 } 147 return err; 148 } 149 150 151 // MAIN // 152 153 /** 154 * Recursively performs a deep copy of an input object. 155 * 156 * @private 157 * @param {*} val - value to copy 158 * @param {(Array|Object)} copy - copy 159 * @param {Array} cache - an array of visited objects 160 * @param {Array} refs - an array of object references 161 * @param {NonNegativeInteger} level - copy depth 162 * @returns {*} deep copy 163 */ 164 function deepCopy( val, copy, cache, refs, level ) { 165 var parent; 166 var keys; 167 var name; 168 var desc; 169 var ctor; 170 var key; 171 var ref; 172 var x; 173 var i; 174 var j; 175 176 level -= 1; 177 178 // Primitives and functions... 179 if ( 180 typeof val !== 'object' || 181 val === null 182 ) { 183 return val; 184 } 185 if ( isBuffer( val ) ) { 186 return copyBuffer( val ); 187 } 188 if ( isError( val ) ) { 189 return copyError( val ); 190 } 191 // Objects... 192 name = typeOf( val ); 193 194 if ( name === 'date' ) { 195 return new Date( +val ); 196 } 197 if ( name === 'regexp' ) { 198 return regexp( val.toString() ); 199 } 200 if ( name === 'set' ) { 201 return new Set( val ); 202 } 203 if ( name === 'map' ) { 204 return new Map( val ); 205 } 206 if ( 207 name === 'string' || 208 name === 'boolean' || 209 name === 'number' 210 ) { 211 // If provided an `Object`, return an equivalent primitive! 212 return val.valueOf(); 213 } 214 ctor = typedArrays[ name ]; 215 if ( ctor ) { 216 return ctor( val ); 217 } 218 // Class instances... 219 if ( 220 name !== 'array' && 221 name !== 'object' 222 ) { 223 // Cloning requires ES5 or higher... 224 if ( typeof Object.freeze === 'function' ) { 225 return cloneInstance( val ); 226 } 227 return {}; 228 } 229 // Arrays and plain objects... 230 keys = objectKeys( val ); 231 if ( level > 0 ) { 232 parent = name; 233 for ( j = 0; j < keys.length; j++ ) { 234 key = keys[ j ]; 235 x = val[ key ]; 236 237 // Primitive, Buffer, special class instance... 238 name = typeOf( x ); 239 if ( 240 typeof x !== 'object' || 241 x === null || 242 ( 243 name !== 'array' && 244 name !== 'object' 245 ) || 246 isBuffer( x ) 247 ) { 248 if ( parent === 'object' ) { 249 desc = propertyDescriptor( val, key ); 250 if ( hasOwnProp( desc, 'value' ) ) { 251 desc.value = deepCopy( x ); 252 } 253 defineProperty( copy, key, desc ); 254 } else { 255 copy[ key ] = deepCopy( x ); 256 } 257 continue; 258 } 259 // Circular reference... 260 i = indexOf( cache, x ); 261 if ( i !== -1 ) { 262 copy[ key ] = refs[ i ]; 263 continue; 264 } 265 // Plain array or object... 266 ref = ( isArray( x ) ) ? new Array( x.length ) : {}; 267 cache.push( x ); 268 refs.push( ref ); 269 if ( parent === 'array' ) { 270 copy[ key ] = deepCopy( x, ref, cache, refs, level ); 271 } else { 272 desc = propertyDescriptor( val, key ); 273 if ( hasOwnProp( desc, 'value' ) ) { 274 desc.value = deepCopy( x, ref, cache, refs, level ); 275 } 276 defineProperty( copy, key, desc ); 277 } 278 } 279 } else if ( name === 'array' ) { 280 for ( j = 0; j < keys.length; j++ ) { 281 key = keys[ j ]; 282 copy[ key ] = val[ key ]; 283 } 284 } else { 285 for ( j = 0; j < keys.length; j++ ) { 286 key = keys[ j ]; 287 desc = propertyDescriptor( val, key ); 288 defineProperty( copy, key, desc ); 289 } 290 } 291 if ( !Object.isExtensible( val ) ) { 292 Object.preventExtensions( copy ); 293 } 294 if ( Object.isSealed( val ) ) { 295 Object.seal( copy ); 296 } 297 if ( Object.isFrozen( val ) ) { 298 Object.freeze( copy ); 299 } 300 return copy; 301 } 302 303 304 // EXPORTS // 305 306 module.exports = deepCopy;