time-to-botec/js/node_modules/@stdlib/utils/copy/lib/deep_copy.js

307 lines
7.1 KiB
JavaScript
Raw Normal View History

/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
// MODULES //
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var isArray = require( '@stdlib/assert/is-array' );
var isBuffer = require( '@stdlib/assert/is-buffer' );
var isError = require( '@stdlib/assert/is-error' );
var typeOf = require( './../../type-of' );
var regexp = require( './../../regexp-from-string' );
var indexOf = require( './../../index-of' );
var objectKeys = require( './../../keys' );
var propertyNames = require( './../../property-names' );
var propertyDescriptor = require( './../../property-descriptor' );
var getPrototypeOf = require( './../../get-prototype-of' );
var defineProperty = require( './../../define-property' );
var copyBuffer = require( '@stdlib/buffer/from-buffer' );
var typedArrays = require( './typed_arrays.js' );
// FUNCTIONS //
/**
* Clones a class instance.
*
* ## Notes
*
* - 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**.
* - 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.
*
*
* @private
* @param {Object} val - class instance
* @returns {Object} new instance
*/
function cloneInstance( val ) {
var cache;
var names;
var name;
var refs;
var desc;
var tmp;
var ref;
var i;
cache = [];
refs = [];
ref = Object.create( getPrototypeOf( val ) );
cache.push( val );
refs.push( ref );
names = propertyNames( val );
for ( i = 0; i < names.length; i++ ) {
name = names[ i ];
desc = propertyDescriptor( val, name );
if ( hasOwnProp( desc, 'value' ) ) {
tmp = ( isArray( val[name] ) ) ? [] : {};
desc.value = deepCopy( val[name], tmp, cache, refs, -1 );
}
defineProperty( ref, name, desc );
}
if ( !Object.isExtensible( val ) ) {
Object.preventExtensions( ref );
}
if ( Object.isSealed( val ) ) {
Object.seal( ref );
}
if ( Object.isFrozen( val ) ) {
Object.freeze( ref );
}
return ref;
}
/**
* Copies an error object.
*
* @private
* @param {(Error|TypeError|SyntaxError|URIError|ReferenceError|RangeError|EvalError)} error - error to copy
* @returns {(Error|TypeError|SyntaxError|URIError|ReferenceError|RangeError|EvalError)} error copy
*
* @example
* var err1 = new TypeError( 'beep' );
*
* var err2 = copyError( err1 );
* // returns <TypeError>
*/
function copyError( error ) {
var cache = [];
var refs = [];
var keys;
var desc;
var tmp;
var key;
var err;
var i;
// Create a new error...
err = new error.constructor( error.message );
cache.push( error );
refs.push( err );
// If a `stack` property is present, copy it over...
if ( error.stack ) {
err.stack = error.stack;
}
// Node.js specific (system errors)...
if ( error.code ) {
err.code = error.code;
}
if ( error.errno ) {
err.errno = error.errno;
}
if ( error.syscall ) {
err.syscall = error.syscall;
}
// Any enumerable properties...
keys = objectKeys( error );
for ( i = 0; i < keys.length; i++ ) {
key = keys[ i ];
desc = propertyDescriptor( error, key );
if ( hasOwnProp( desc, 'value' ) ) {
tmp = ( isArray( error[ key ] ) ) ? [] : {};
desc.value = deepCopy( error[ key ], tmp, cache, refs, -1 );
}
defineProperty( err, key, desc );
}
return err;
}
// MAIN //
/**
* Recursively performs a deep copy of an input object.
*
* @private
* @param {*} val - value to copy
* @param {(Array|Object)} copy - copy
* @param {Array} cache - an array of visited objects
* @param {Array} refs - an array of object references
* @param {NonNegativeInteger} level - copy depth
* @returns {*} deep copy
*/
function deepCopy( val, copy, cache, refs, level ) {
var parent;
var keys;
var name;
var desc;
var ctor;
var key;
var ref;
var x;
var i;
var j;
level -= 1;
// Primitives and functions...
if (
typeof val !== 'object' ||
val === null
) {
return val;
}
if ( isBuffer( val ) ) {
return copyBuffer( val );
}
if ( isError( val ) ) {
return copyError( val );
}
// Objects...
name = typeOf( val );
if ( name === 'date' ) {
return new Date( +val );
}
if ( name === 'regexp' ) {
return regexp( val.toString() );
}
if ( name === 'set' ) {
return new Set( val );
}
if ( name === 'map' ) {
return new Map( val );
}
if (
name === 'string' ||
name === 'boolean' ||
name === 'number'
) {
// If provided an `Object`, return an equivalent primitive!
return val.valueOf();
}
ctor = typedArrays[ name ];
if ( ctor ) {
return ctor( val );
}
// Class instances...
if (
name !== 'array' &&
name !== 'object'
) {
// Cloning requires ES5 or higher...
if ( typeof Object.freeze === 'function' ) {
return cloneInstance( val );
}
return {};
}
// Arrays and plain objects...
keys = objectKeys( val );
if ( level > 0 ) {
parent = name;
for ( j = 0; j < keys.length; j++ ) {
key = keys[ j ];
x = val[ key ];
// Primitive, Buffer, special class instance...
name = typeOf( x );
if (
typeof x !== 'object' ||
x === null ||
(
name !== 'array' &&
name !== 'object'
) ||
isBuffer( x )
) {
if ( parent === 'object' ) {
desc = propertyDescriptor( val, key );
if ( hasOwnProp( desc, 'value' ) ) {
desc.value = deepCopy( x );
}
defineProperty( copy, key, desc );
} else {
copy[ key ] = deepCopy( x );
}
continue;
}
// Circular reference...
i = indexOf( cache, x );
if ( i !== -1 ) {
copy[ key ] = refs[ i ];
continue;
}
// Plain array or object...
ref = ( isArray( x ) ) ? new Array( x.length ) : {};
cache.push( x );
refs.push( ref );
if ( parent === 'array' ) {
copy[ key ] = deepCopy( x, ref, cache, refs, level );
} else {
desc = propertyDescriptor( val, key );
if ( hasOwnProp( desc, 'value' ) ) {
desc.value = deepCopy( x, ref, cache, refs, level );
}
defineProperty( copy, key, desc );
}
}
} else if ( name === 'array' ) {
for ( j = 0; j < keys.length; j++ ) {
key = keys[ j ];
copy[ key ] = val[ key ];
}
} else {
for ( j = 0; j < keys.length; j++ ) {
key = keys[ j ];
desc = propertyDescriptor( val, key );
defineProperty( copy, key, desc );
}
}
if ( !Object.isExtensible( val ) ) {
Object.preventExtensions( copy );
}
if ( Object.isSealed( val ) ) {
Object.seal( copy );
}
if ( Object.isFrozen( val ) ) {
Object.freeze( copy );
}
return copy;
}
// EXPORTS //
module.exports = deepCopy;