對深拷貝挺用心的一次總結

理解淺拷貝和深拷貝的

所謂拷貝就是複製,JS中數據類型分爲基本數據類型(按值傳遞)和引用數據類型(按引用傳遞),因此在複製過程會有所不一樣。 爲了方便理解,咱們能夠把複製數據類比爲效仿別人開賓館es6

基本數據類型複製圖解(桌椅板凳能夠照搬)面試

咱們如今想開一家和賓館A同樣的賓館B(複製數據),咱們首先須要把賓館A基本設施桌椅板凳(基本數據類型)拿過來。json

基本數據類型圖解

引用數據類型(房間照搬不了,只能拿個房卡)數組

好了,複製工程的大頭來了,房間照搬不可能,爲了省勁兒,直接把賓館A的房卡(引用數據類型在棧中的引用)照搬了一份。bash

勉強看來,開賓館B的任務算是完成了。開業把!可是這時候客人來了拿到的房卡,可是這房卡是賓館A的,開門進去萬一看到一些不可描述的事情(數據的改動)...豈不是尷尬!數據結構

引用數據類型圖解

咱們發現照搬房卡(淺拷貝)行不通,因而只能本身建房間,從賓館A的房間一個個挨着進去,一個個的桌椅板凳的搬(複製),這樣才能創建本身賓館B的房間(深拷貝)。學習

深拷貝的定義 - 背下來應付面試官

深拷貝的過程就是指建立一個和原數據內容和結構如出一轍的新對象,新對象內的全部值的指針地址都是新的地址ui

代碼層面看淺、深拷貝

檢測深淺拷貝:改變新數據, 都不改變數據spa

/**
* 基本數據類型拷貝
**/ 
var num1 = 5;           // 數據a
var num2 = num1;        // 把a的值複製給b
console.log(num2)       // 5

// 當咱們改變num2,看是否會改變原數據a
num2 = 2;
console.log(num1)  // 5

複製代碼

結論:基本數據類型都是深拷貝(直接複製便可)3d


/**
* 引用數據類型拷貝
**/ 
var obj = {               // 原始數據obj
    name: 'nike',
    age: 18,
    sex: '男'
}
// ** 淺拷貝 **
var shallowCopyObj = obj;         // 把原始對象直接複製給新對象shallowCopyObj,此處完成引用複製,也就是淺拷貝
console.log(shallowCopyObj.name);  // nike
shallowCopyObj.name = 'jack';     // 改變新對象的name屬性
console.log(obj.name);            // 'jack'  (原數據的name屬性也被改變)

// ** 深拷貝 **
var deepCopyObj = {};              // 深拷貝的思路:拆分原數據,把原數據的每一個屬性單獨複製
for(let item in obj){
    deepCopyObj[item] = obj[item];
}
console.log(deepCopyobj.name);     // nike
deepCopyobj.name = 'mary';         // 複製完成後,改變deepCopyObj的name屬性
console.log(obj.name);            // 'nike'  (原數據的name屬性沒有被改變)

複製代碼

結論:引用數據類型不能直接複製,須要遍歷屬性,分別複製。而對於屬性值有引用數據類型的多層嵌套對象,須要進行屬性值數據類型檢測,若是是引用數據類型,再繼續遍歷該屬性值,進行復制。

JS實現深拷貝

爲了深拷貝多層嵌套對象,使用遞歸複製原數據

/**
* @param {Object} initData 準備要複製的對象
* @param {Object} copyData 複製的載體對象,能夠爲空,爲空的話表示要複製到一個空對象或者空數組上
***/
function deepClone(initData,copyData){
    var copyData = copyData || {};          // copyData初始化

    for(let item in initData){ // 遍歷
        if(typeof initData[item] === 'object'){
            copyData[item] = (initData[item].constructor == Object) ? {} : [];
            arguments.callee(initData[item],copyData[item]);
        }else{
            copyData[item] = initData[item];
        }
    }
    
    return copyData
}
複製代碼

以上方法就實現了對多層嵌套對象的深拷貝,爲了增長方法的健壯性,咱們能夠增長對參數類型的檢測,以及對複製ES6中map,set對象的支持

/**
* @param {Object} initData 準備要複製的對象
* @param {Object} copyData 複製的載體對象,能夠爲空,爲空的話表示要複製到一個空對象或者空數組上
***/
function deepClone(initData, copyData) {
    if (typeof initData !== 'object') { return initData }; // 若是要複製的數據是基本數據類型,則直接返回

    var copyData = copyData || {};          // copyData初始化
    
    switch (initData.constructor) {
        case Array:
            copyData = [];
        case Object:
            for (var property in initData) {
                copyData[property] = typeof initData[property] === 'object' ? arguments.callee(initData[property]) : initData[property];
            }
            break;
        case Map:
            copyData = new Map();
            initData.forEach((value, key) => {
                copyData.set(key, typeof value === 'object' ? arguments.callee(value) : value);
            });
            break;
        case Set:
            copyData = new Set();
            initData.forEach(value => {
                copyData.add(typeof value === 'object' ? arguments.callee(value) : value);
            });
            break;
    }
    
    return copyData
}
複製代碼

實現深拷貝的奇技淫巧

如下記錄幾種能夠實現深拷貝可是使用場景有所限制的方法,在某些場景仍是能夠簡化咱們的工做

1.數組只進行一層深拷貝(子元素都是基本數據類型)

  • slice() :截取數組返回一個新的數組,包含從 start 到 end (不包括該元素)的元素。思路就是截取整個數組。
var arr = [1,2,3,4];
var newArr = arr.slice();   // 當slice()不帶任何參數的時候,默認返回一個長度和原數組相同的新數組
newArr[0] = 9;
console.log(arr);           // [1,2,3,4]
複製代碼
  • concat(arr):合併arr返回一個新的數組。該數組是經過把全部 arr 參數添加到 原數據 中生成的。因此咱們只須要合併和空數組,就能實現對原數組的拷貝。
var arr = [1,2,3,4];
var newArr = arr.concat();   // concat()不帶任何參數的時候,至關於合併和空數組
newArr[0] = 9;
console.log(arr);           // [1,2,3,4]
複製代碼

2.對象只進行一層拷貝 (子元素都是基本數據類型) 如下兩個方法都是es6,慎用

  • Object.assign(target, ...sources):該方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象target。它將返回目標對象。因此咱們能夠把目標對象target設置成空對象,完成對對象的拷貝
var person = {
    name:'nike',
    age:18
}

var newPerson = Object.assign({},person);
newPerson.age = 20;
console.log(person.age);    // 18
複製代碼
  • 拓展運算符(...):做用就是把數組或類數組對象展開成一系列用逗號隔開的值
var person = {
    name:'nike',
    age:18
}

var newPerson = {...person};
newPerson.age = 20;
console.log(person.age);    // 18
複製代碼

3.用JSON.stringify把對象轉成字符串,再用JSON.parse把字符串轉成新的對象。

用JSON.stringify把對象轉成字符串,再用JSON.parse把字符串轉成新的對象。能夠脫離原對象的引用,也能夠實現對多層嵌套對象的深拷貝。

缺點:這種方法能正確處理的對象只有 Number, String, Boolean, Array等可以被 json 直接表示的數據結構。而如function、正則對象等都沒法正確完成轉換。

var person = {
    name:'nike',
    age:18
}

var newPerson = JSON.parse(JSON.stringify(person));
newPerson.age = 20;
console.log(person.age);    // 18

// 當屬性中存在function等
var people = {
    name:'mary',
    age:18,
    study:function(){
        console.log(1)
    }
}

var newPeople = JSON.parse(JSON.stringify(people));
console.log(newPeople.study);    // undefined
複製代碼

結語

加入掘金也有兩年多了,可是也只是看別人的文章,本身也沒動手總結過!在此次找工做面試的過程當中,感觸也挺多。

  • 有些你認爲很簡單東西,別人問你的時候你還真不必定說得清

  • 有些工做中碰到的問題,不總結記錄下,着實容易忘記,面試的時候被問住了,反倒顯得本身是編的

  • 基礎真的很重要,即便在你工做絕大部分的時間你用不到,可是它決定你成長的高度

因此我也想着藉着掘金這個優秀的平臺,一方面記錄下本身學習和工做經驗,另外一方面也爲之後的職業發展打點基礎。

相關文章
相關標籤/搜索