1. 程式人生 > >淺拷貝、深拷貝

淺拷貝、深拷貝

深拷貝和淺拷貝   這兩個概念是在專案中比較常見的,在很多時候,都會遇到拷貝的問題,我們總是需要將一個物件賦值到另一個物件上,但可能會在改變新賦值物件的時候,忽略掉我是否之後還需要用到原來的物件,那麼就會出現當改變新賦值物件的某一個屬性時,也同時改變了原物件,此時我們就需要用到拷貝這個概念了。   深拷貝和淺拷貝的區別 1.淺拷貝: 將原物件或原陣列的引用直接賦給新物件,新陣列,新物件/陣列只是原物件的一個引用 2.深拷貝: 建立一個新的物件和陣列,將原物件的各項屬性的“值”(陣列的所有元素)拷貝過來,是“值”而不是“引用”     在這裡,我們需要注意一點,那就是"引用"和"值"是什麼,在學c的時候,涉及到了一個指標的概念,指標(Pointer)是程式語言中的一個物件,利用地址,它的值直接指向(points to)存在電腦儲存器中另一個地方的值。由於通過地址能找到所需的變數單元,可以說,地址指向該變數單元。所以,這裡的值就是物件的值,而引用就是指向儲存這個值的地址。   那麼這裡我覺得稍微需要說一下堆疊和指標的關係了   當變數複製引用型別值的時候,同樣和基本型別值一樣會將變數的值複製到新變數上,不同的是對於變數的值,它是一個指標,指向儲存在堆記憶體中的物件(JS規定放在堆記憶體中的物件無法直接訪問,必須要訪問這個物件在堆記憶體中的地址,然後再按照這個地址去獲得這個物件中的值,所以引用型別的值是按引用訪問)   圖文說明: 1.基本型別---儲存   2.基本型別---複製 3.引用型別---儲存 如上圖,引用型別在棧裡面儲存的是地址指標 4.引用型別---賦值 5.引用型別---拷貝 接下來說一下淺拷貝和深拷貝的方法(注意需要把拷貝和賦值區分開來,網上很多文章中都把賦值和淺拷貝混為一談,那麼這裡理解起來是會自我矛盾的)   淺拷貝   概念:建立一個新物件,這個物件有著原始物件屬性值的一份精確拷貝。如果屬性是基本型別,拷貝的就是基本型別的值,如果屬性是引用型別,拷貝的就是記憶體地址 ,所以如果其中一個物件改變了這個地址,就會影響到另一個物件。   思路:淺拷貝可以想做把引用型別的第一子級當作是每一個數據型別來賦值。例如:
let obj = {
    a: 1,
    b: {
        c: 2,
    }
}

 

那麼obj的淺拷貝就可以看作是a賦值,b賦值。結合上圖,那麼當target淺拷貝obj時,那麼target中的a會在棧記憶體中重新開闢空間儲存值,由於b是引用型別,那麼b還是儲存地址,指向obj的b的值。   方法:   1.Object.assign() 定義:用於將所有可列舉屬性的值從一個或多個源物件複製到目標物件。它將返回目標物件。 用法: Object.assign(target, ...sourceObj) 注意:
不會拷貝物件繼承的屬性、
不可列舉的屬性、
屬性的資料屬性/訪問器屬性、
可以拷貝Symbol型別
  2.拓展運算子(...)
用法:...object
    3.Array.prototype.slice和Array.prototype.concat 用法:arrayObject.slice(start,end)、arrayObject.concat(arrayX,...,arrayX)
arrayObject.slice(start,end)
   
arrayObject.concat(arrayX,...,arrayX)
    4.遍歷
function deepClone(source) {
  if (!source || typeof source !== 'object') {
    return source;
  }
  const targetObj = source.constructor === Array ? [] : {};
  for (const keys in source) {
    if (source.hasOwnProperty(keys)) {
        targetObj[keys] = source[keys];
    }
  }
  return targetObj;
}
  深拷貝:   概念:將一個物件從記憶體中完整的拷貝一份出來,從堆記憶體中開闢一個新的區域存放新物件,且修改新物件不會影響原物件   思路:通過上述淺拷貝的示例,可以看出,這些方法都只實現了對物件的第一層元素的拷貝,但是之後層的元素還是共享的,那麼我們還是結合淺拷貝的思路,參考基本型別的賦值過程,來解決這個問題。   方法: 1.遞迴遍歷 在淺拷貝的遍歷方法上多加一層判斷,進而對所有層級的元素進行遍歷賦值
function deepClone(source) {
  if (!source || typeof source !== 'object') {
    return source;
  }
  const targetObj = source.constructor === Array ? [] : {};
  for (const keys in source) {
    if (source.hasOwnProperty(keys)) {
      if (source[keys] && typeof source[keys] === 'object') {
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      } else {
        targetObj[keys] = source[keys];
      }
    }
  }
  return targetObj;
}
2.JSON.parse(JSON.stringify(XXXX)) JSON.stringify方法是把物件轉成字串,返回新生成的字串,這一步可以看作是,把目標物件轉化成基礎型別後,重新開闢一個新的區域存放轉化後的字串,然後在通過JSON.parse轉成JSON格式,在賦給新的物件,這樣操作新的物件就和目標物件毫無關係了 注意: 1.拷貝的物件的值中如果有函式,undefined,symbol型別,不會轉換,而是丟棄 2.無法拷貝不可列舉的屬性,無法拷貝物件的原型鏈 3.拷貝Date引用型別會變成字串 4.拷貝RegExp引用型別會變成空物件 5.物件中含有NaN、Infinity和-Infinity,則序列化的結果會變成null 6.無法拷貝物件的迴圈應用(即obj[key] = obj)   參考文章:   https://www.jianshu.com/p/c651aeabf582   https://blog.csdn.net/weixin_41910848/article/details/82144671