time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

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;