/** * @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 */ 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;