307 lines
7.1 KiB
JavaScript
307 lines
7.1 KiB
JavaScript
|
/**
|
||
|
* @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;
|