177 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const Reach = require('./reach');
 | |
| const Types = require('./types');
 | |
| const Utils = require('./utils');
 | |
| 
 | |
| 
 | |
| const internals = {
 | |
|     needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap])
 | |
| };
 | |
| 
 | |
| 
 | |
| module.exports = internals.clone = function (obj, options = {}, _seen = null) {
 | |
| 
 | |
|     if (typeof obj !== 'object' ||
 | |
|         obj === null) {
 | |
| 
 | |
|         return obj;
 | |
|     }
 | |
| 
 | |
|     let clone = internals.clone;
 | |
|     let seen = _seen;
 | |
| 
 | |
|     if (options.shallow) {
 | |
|         if (options.shallow !== true) {
 | |
|             return internals.cloneWithShallow(obj, options);
 | |
|         }
 | |
| 
 | |
|         clone = (value) => value;
 | |
|     }
 | |
|     else if (seen) {
 | |
|         const lookup = seen.get(obj);
 | |
|         if (lookup) {
 | |
|             return lookup;
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         seen = new Map();
 | |
|     }
 | |
| 
 | |
|     // Built-in object types
 | |
| 
 | |
|     const baseProto = Types.getInternalProto(obj);
 | |
|     if (baseProto === Types.buffer) {
 | |
|         return Buffer && Buffer.from(obj);              // $lab:coverage:ignore$
 | |
|     }
 | |
| 
 | |
|     if (baseProto === Types.date) {
 | |
|         return new Date(obj.getTime());
 | |
|     }
 | |
| 
 | |
|     if (baseProto === Types.regex) {
 | |
|         return new RegExp(obj);
 | |
|     }
 | |
| 
 | |
|     // Generic objects
 | |
| 
 | |
|     const newObj = internals.base(obj, baseProto, options);
 | |
|     if (newObj === obj) {
 | |
|         return obj;
 | |
|     }
 | |
| 
 | |
|     if (seen) {
 | |
|         seen.set(obj, newObj);                              // Set seen, since obj could recurse
 | |
|     }
 | |
| 
 | |
|     if (baseProto === Types.set) {
 | |
|         for (const value of obj) {
 | |
|             newObj.add(clone(value, options, seen));
 | |
|         }
 | |
|     }
 | |
|     else if (baseProto === Types.map) {
 | |
|         for (const [key, value] of obj) {
 | |
|             newObj.set(key, clone(value, options, seen));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     const keys = Utils.keys(obj, options);
 | |
|     for (const key of keys) {
 | |
|         if (key === '__proto__') {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         if (baseProto === Types.array &&
 | |
|             key === 'length') {
 | |
| 
 | |
|             newObj.length = obj.length;
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         const descriptor = Object.getOwnPropertyDescriptor(obj, key);
 | |
|         if (descriptor) {
 | |
|             if (descriptor.get ||
 | |
|                 descriptor.set) {
 | |
| 
 | |
|                 Object.defineProperty(newObj, key, descriptor);
 | |
|             }
 | |
|             else if (descriptor.enumerable) {
 | |
|                 newObj[key] = clone(obj[key], options, seen);
 | |
|             }
 | |
|             else {
 | |
|                 Object.defineProperty(newObj, key, { enumerable: false, writable: true, configurable: true, value: clone(obj[key], options, seen) });
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             Object.defineProperty(newObj, key, {
 | |
|                 enumerable: true,
 | |
|                 writable: true,
 | |
|                 configurable: true,
 | |
|                 value: clone(obj[key], options, seen)
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return newObj;
 | |
| };
 | |
| 
 | |
| 
 | |
| internals.cloneWithShallow = function (source, options) {
 | |
| 
 | |
|     const keys = options.shallow;
 | |
|     options = Object.assign({}, options);
 | |
|     options.shallow = false;
 | |
| 
 | |
|     const seen = new Map();
 | |
| 
 | |
|     for (const key of keys) {
 | |
|         const ref = Reach(source, key);
 | |
|         if (typeof ref === 'object' ||
 | |
|             typeof ref === 'function') {
 | |
| 
 | |
|             seen.set(ref, ref);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return internals.clone(source, options, seen);
 | |
| };
 | |
| 
 | |
| 
 | |
| internals.base = function (obj, baseProto, options) {
 | |
| 
 | |
|     if (options.prototype === false) {                  // Defaults to true
 | |
|         if (internals.needsProtoHack.has(baseProto)) {
 | |
|             return new baseProto.constructor();
 | |
|         }
 | |
| 
 | |
|         return baseProto === Types.array ? [] : {};
 | |
|     }
 | |
| 
 | |
|     const proto = Object.getPrototypeOf(obj);
 | |
|     if (proto &&
 | |
|         proto.isImmutable) {
 | |
| 
 | |
|         return obj;
 | |
|     }
 | |
| 
 | |
|     if (baseProto === Types.array) {
 | |
|         const newObj = [];
 | |
|         if (proto !== baseProto) {
 | |
|             Object.setPrototypeOf(newObj, proto);
 | |
|         }
 | |
| 
 | |
|         return newObj;
 | |
|     }
 | |
| 
 | |
|     if (internals.needsProtoHack.has(baseProto)) {
 | |
|         const newObj = new proto.constructor();
 | |
|         if (proto !== baseProto) {
 | |
|             Object.setPrototypeOf(newObj, proto);
 | |
|         }
 | |
| 
 | |
|         return newObj;
 | |
|     }
 | |
| 
 | |
|     return Object.create(proto);
 | |
| };
 |