實現物件淺拷貝、深拷貝

Hwg發表於2019-07-20

一、淺拷貝

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

相關文章