实现对象浅拷贝、深拷贝

347 阅读4分钟

一、浅拷贝

1. 单个对象复制

/**
 * 实现单个对象复制 - 浅拷贝
 * @param obj
 * @returns {Array}
 */
function clone(obj) {
    const newObj = obj instanceof Array ? [] : typeof obj === 'object' && typeof to !== 'function' ? {} : '';
    if (!newObj) throw new Error('obj is not object');
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) newObj[key] = obj[key];
    }
    return newObj;
}

Demo:

let obj1 = {
    name: '张三',
    age: 12,
    language: {
        zh: '中文',
        en: '英文'
    },
    color:['red','yellow']
};
let newObj = clone(obj1);

console.log(newObj); 
//'{"name":"张三","age":12,"language":{"zh":"中文","en":"英文"},"color":["red","yellow"]}'

obj1.language.be = '白俄罗斯';

console.log(newObj);  
//'{"name":"张三","age":12,"language":{"zh":"中文","en":"英文","be":"白俄罗斯"},"color":["red","yellow"]}'

2. 两个对象合并

/**
 * 实现两个对象合并 - 浅拷贝
 * @param to
 * @param from
 * @returns {*}
 */
function extend(to, from) {
    if ( typeof to !== 'object' ||  typeof to === 'function'){
        throw new Error('to is not object')
    }
    if ( typeof from !== 'object' ||  typeof from === 'function'){
        throw new Error('from is not object')
    }
    for (let key in from) {
        if (from.hasOwnProperty(key)) to[key] = from[key];
    }
    return to;
}

Demo:

let obj1 = {
    name: '张三',
    age: 12,
    language:{
    	zh:'中文',
    	en:'英文'
    }
};
let obj2 = {
    name: '李四',
    age: 22,
    language:{
    	other:'其他'
    }
};

const options = extend(obj1, obj2);

console.log(options); 
//'{"name":"李四","age":22,"language":{"zh":"中文","en":"英文","be":"白俄罗斯文"}}'

obj2.language.be = '白俄罗斯文';

console.log(options); 
//'{"name":"李四","age":22,"language":{"zh":"中文","en":"英文","be":"白俄罗斯文"}}'

浅复制有个问题,就是只能复制对象的第一层,更深的层次只是对象的引用。

二、深拷贝

1. 单个对象复制

从基础版优化到至尊版

/**
 * 实现对象深拷贝 - 基础版
 * @param obj
 */
function deepClone(obj) {
    deepClone.loop = function(to,from){
        // 循环源对象
        for (let key in from) {
            // 1.判断源对象的属性值是否为数组
            // 2.如果没有这个属性, 则需要创建个属性, 且值为空数组
            if (from[key] instanceof Array && !to[key]) to[key] = [];
            // 判断源对象的属性值是否为对象, 包括普通对象、数组对象
            if (typeof from[key] === 'object') {
                // 判断目标对象是否有这个属性, 没有则创建
                if (!to[key]) to[key] = {};
                // 如果判断是对象类型, 则重新调用自己, 这就是大家所说的递归。
                deepClone.loop(to[key],from[key]);
            } else {
                // 如果源对象是否为数组对象, 如果是直接push追加, 如果不是则新建属性直接赋值
                if (from instanceof Array) {
                    to.push(from[key])
                } else {
                    to[key] = from[key];
                }
            }
        }
        return to;
    };
    return deepClone.loop({},obj);
}

/**
 * 实现对象深拷贝 - 升级版
 * @param obj
 * @returns {*}
 */
function deepClone(obj) {
    const newObj = obj instanceof Array ? [] : {};
    // 循环源对象
    for (let key in obj) {
        // 判断源对象的属性值是否为对象, 包括普通对象、数组对象
        if (typeof obj[key] === 'object') {
            // 判断目标对象是否有这个属性, 没有则创建
            newObj[key] = deepClone(obj[key]);
        } else {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

/**
 * 实现对象深拷贝 - 至尊版
 * @param obj
 * @returns {*}
 */
function deepClone(obj) {
    const newObj = obj instanceof Array ? [] : typeof obj === 'object' && typeof to !== 'function' ? {} : '';
    if (!newObj) throw new Error('obj is not object');
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            typeof obj[key] === 'object' ? newObj[key] = deepClone(obj[key]) : newObj[key] = obj[key];
        }
    }
    return newObj;
}

其他实现方式,最简单的方法就是用 JSON.stringify() 方法。

let obj1 = {
    name: '张三',
    age: 12,
    language: {
        zh: '中文',
        en: '英文'
    },
    color:['red','yellow']
};

let newObj = JSON.parse(JSON.stringify(obj1)); //就是这么简单

2. 两个对象合并

修改下之前浅复制的代码:

/**
 * 实现两个对象合并 - 深拷贝
 * @param to
 * @param from
 * @returns {*}
 */
function extend(to, from) {
    // 判断是否为对象, 如果不是则抛出错误
    if (typeof to !== 'object' || typeof to === 'function') {
        throw new Error('to is not object')
    }
    // 判断是否为对象, 如果不是则抛出错误
    if (typeof from !== 'object' || typeof from === 'function') {
        throw new Error('from is not object')
    }
    // 循环源对象
    for (let key in from) {
        if (typeof from[key] === 'object') {
            // 1.如果是数组, 再判断目标对象有没这个属性
            // 2.如果没有这个属性, 则需要创建个属性, 且值为空数组
            if (from[key] instanceof Array) if (!to[key]) to[key] = [];
            extend(to[key], from[key]);
        } else {
            // 判断源对象是否为数组, 如果是直接push追加, 如果不是则新建属性直接赋值
            from instanceof Array ? to.push(from[key]) : to[key] = from[key];
        }
    }
    return to;
}

Demo:

let obj1 = {
    name: '张三',
    age: 12,
    language: {
        zh: '中文',
        en: '英文'
    },
    color:['red','yellow']
};
let obj2 = {
    name: '李四',
    age: 22,
    language: {
        other: '其他'
    },
    color:['blank']
};

let options = extend(obj1, obj2);

console.log(options, JSON.stringify(options));

//'{"name":"李四","age":22,"language":{"zh":"中文","en":"英文","other":"其他"},"color":["red","yellow","blank"]}'

obj2.color.push('white');

console.log(options, JSON.stringify(options));
//'{"name":"李四","age":22,"language":{"zh":"中文","en":"英文","other":"其他"},"color":["red","yellow","blank"]}'

如果obj1、obj2都是数组合并又会怎么样呢?看下案例:

let obj1 = [{
    name:'张三',
    age:20,
    language: {
        zh: '中文',
        en: '英文'
    }
},{
    name:'李四',
    age:22,
    language: {
        zh: '中文',
        en: '英文'
    }
}];
let obj2 = [{
    name:'王武',
    age:23,
    language: {
        other: '其他'
    },
    color:['blank']
}];

let options = extend(obj1, obj2);

console.log(options, JSON.stringify(options)); 
//'[{"name":"王武","age":23,"language":{"zh":"中文","en":"英文","other":"其他"},"color":["blank"]},{"name":"李四","age":22,"language":{"zh":"中文","en":"英文"}}]'

obj2[0].name = '测试';

console.log(options, JSON.stringify(options));
//'[{"name":"王武","age":23,"language":{"zh":"中文","en":"英文","other":"其他"},"color":["blank"]},{"name":"李四","age":22,"language":{"zh":"中文","en":"英文"}}]'

显然数组也是对象,for in也是支持的,达到预期结果。

三、测试题

来一道测试题,看你是否了解,以下 console.log() 分别打印出什么?

let num = 0;

function deepClone(obj) {
    const newObj = obj instanceof Array ? [] : typeof obj === 'object' && typeof to !== 'function' ? {} : '';
    if (!newObj) throw new Error('obj is not object');
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            num ++ ;
            if (typeof obj[key] === 'object') {
                newObj[key] = deepClone(obj[key]);
                console.log(num,'中间');
            } else {
                newObj[key] = obj[key];
            }
            console.log(num,key);
        }
    }
    return newObj;
}

let obj1 = {
    name: '张三',
    age: 12,
    language: {
        zh: '中文',
        en: '英文'
    },
    color:['red','yellow']
};
const options = deepClone(obj1); // 执行该方法, 输出结果看看?

作者:hwgq2005