JavaScript의 깊은 복사

JavaScript에서 객체의 깊은 복사 기능을 구현하는 데에는 다양한 데이터 타입과 예외 상황을 고려해야 합니다. 특히 Set, Map, Promise 등 특수한 구조를 가진 객체 처리가 중요합니다.

Promises 사용 예시


async function printMessage(message) {
    console.log(message);
}

function delay(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

await delay(3000).then(() => printMessage("f1"));
await Promise.all([delay(3000), delay(4000)]).then(() => printMessage("f2"));

깊은 복사 알고리즘


const deepCopy = (function() {
    'use strict';

    function isInstanceOf(obj, type) {
        return type != null && obj instanceof type;
    }

    const NativeMap = typeof Map !== 'undefined' ? Map : () => {};
    const NativeSet = typeof Set !== 'undefined' ? Set : () => {};
    const NativePromise = typeof Promise !== 'undefined' ? Promise : () => {};

    function deepCopy(source, circular = true, depth = Infinity, prototype, includeNonEnumerable) {
        if (typeof circular === 'object') {
            depth = circular.depth;
            prototype = circular.prototype;
            includeNonEnumerable = circular.includeNonEnumerable;
            circular = circular.circular;
        }

        const parentStack = [];
        const childStack = [];

        function _deepCopy(source, depth) {
            if (source === null) return null;
            if (depth === 0) return source;

            let result;
            if (isInstanceOf(source, NativeMap)) {
                result = new NativeMap();
            } else if (isInstanceOf(source, NativeSet)) {
                result = new NativeSet();
            } else if (isInstanceOf(source, NativePromise)) {
                result = new NativePromise((resolve, reject) => {
                    source.then(value => resolve(_deepCopy(value, depth - 1)),
                             err => reject(_deepCopy(err, depth - 1)));
                });
            } else if (Array.isArray(source)) {
                result = [];
            } else if (isRegExp(source)) {
                result = new RegExp(source.source, getRegExpFlags(source));
                if (source.lastIndex) result.lastIndex = source.lastIndex;
            } else if (isDate(source)) {
                result = new Date(source.getTime());
            } else if (Buffer && Buffer.isBuffer(source)) {
                result = Buffer.from(source);
                return result;
            } else if (isError(source)) {
                result = Object.create(source);
            } else {
                result = Object.create(prototype || Object.getPrototypeOf(source));
            }

            if (circular) {
                const index = parentStack.indexOf(source);
                if (index !== -1) return childStack[index];
                parentStack.push(source);
                childStack.push(result);
            }

            if (isInstanceOf(source, NativeMap)) {
                source.forEach((value, key) => {
                    result.set(deepCopy(key, depth - 1), deepCopy(value, depth - 1));
                });
            } else if (isInstanceOf(source, NativeSet)) {
                source.forEach(value => {
                    result.add(deepCopy(value, depth - 1));
                });
            }

            for (const prop in source) {
                const descriptor = Object.getOwnPropertyDescriptor(prototype || Object.getPrototypeOf(source), prop);
                if (descriptor && descriptor.set == null) continue;
                result[prop] = deepCopy(source[prop], depth - 1);
            }

            if (Object.getOwnPropertySymbols) {
                const symbols = Object.getOwnPropertySymbols(source);
                for (const symbol of symbols) {
                    const desc = Object.getOwnPropertyDescriptor(source, symbol);
                    if (desc && !desc.enumerable && !includeNonEnumerable) continue;
                    result[symbol] = deepCopy(source[symbol], depth - 1);
                    if (!desc.enumerable) Object.defineProperty(result, symbol, { enumerable: false });
                }
            }

            if (includeNonEnumerable) {
                const props = Object.getOwnPropertyNames(source);
                for (const prop of props) {
                    const desc = Object.getOwnPropertyDescriptor(source, prop);
                    if (desc && desc.enumerable) continue;
                    result[prop] = deepCopy(source[prop], depth - 1);
                    Object.defineProperty(result, prop, { enumerable: false });
                }
            }

            return result;
        }

        return _deepCopy(source, depth);
    }

    function isRegExp(obj) { return typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object RegExp]'; }
    function isDate(obj) { return typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Date]'; }
    function isError(obj) { return typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Error]'; }
    function getRegExpFlags(re) { return (re.global ? 'g' : '') + (re.ignoreCase ? 'i' : '') + (re.multiline ? 'm' : ''); }

    return deepCopy;
})();

if (typeof module === 'object' && module.exports) {
    module.exports = deepCopy;
}

사용 예시


const deepCopy = require('deep-copy');

let original = { nested: { value: 'original' } };
let copy = deepCopy(original);

original.nested.value = 'modified';
console.log(original); // { nested: { value: 'modified' } }
console.log(copy);     // { nested: { value: 'original' } }

순환 참조 테스트


let obj = { hello: 'world' };
obj.self = obj;

let copy = deepCopy(obj);
console.log(copy.self); // [Circular]

태그: JavaScript 깊은 복사 순환 참조 Promise set

10월 9일 06:13에 게시됨