面試題 | 請實現一個深拷貝

linmu發表於2020-03-01

先來看下基礎的資料型別和淺拷貝。

JavaScript 資料型別

基本資料型別

  • String
  • Number
  • Null
  • Undefined
  • Boolean
  • Symbol(ES6 新增)

基本資料型別存放在棧記憶體中,可直接訪問和修改變數的值,基本資料型別不存在拷貝。

引用型別

  • Object

引用型別值為物件,存放在堆記憶體中。

在棧記憶體中變數儲存的是一個指標,指向對應在堆記憶體中的地址。當訪問引用型別的時候,要先從棧中取出該物件的地址指標,然後再從堆記憶體中取得所需的資料。

淺拷貝

基礎的引用拷貝

var source=[1,2,3]
var target=source
source[0]=0
console.log(source,target) // [ 0, 2, 3 ] [ 0, 2, 3 ]
複製程式碼

因為 source 陣列是 Object 型別,是引用型別,target 相當於多了一個指標,指標指向的值變了,所以兩個陣列都改變。

concat、slice、Object.assign()

var source=[1,2,3]
var target=source.concat()
source[0]=0
console.log(source,target) // [ 0, 2, 3 ] [ 1, 2, 3 ]
複製程式碼

這個像深拷貝嗎?是深拷貝嗎?

看下面這個輸出:

var source=[1,2,[3,4],{name:'linmu'}]
var target=source.concat()
source[3].name='beiyang'
console.log(source[3],target[3]) // {name:'beiyang'}  {name:'beiyang'}
複製程式碼

當陣列 source 包含物件時,slice 和 concat 拷貝出來的陣列中的物件還是共享同一個記憶體地址,所以仍是淺拷貝。

深拷貝

深拷貝是可以完美的解決淺拷貝的弊端,重新開闢一塊地址,兩者互不關聯。

普通深拷貝

JSON 物件是 ES5 中引入的新的型別(支援的瀏覽器為 IE8+),JSON 物件 parse 方法可以將 JSON 字串反序列化成 JS 物件,stringify 方法可以將 JS 物件序列化成 JSON 字串,藉助這兩個方法,可以實現簡單物件的深拷貝,但不能處理函式、正則等物件。

JSON.parse(JSON.stringify())
複製程式碼

最強深拷貝

可解決上面不能處理函式,setter,getter 等問題,方便在面試時手寫:

function cloneObject (target, source) {
  //獲取物件下的所有屬性,包括不可遍歷屬性
  var names = Object.getOwnPropertyNames(source);
  for (var i = 0; i < names.length; i++) {
    // 獲取每個屬性的描述資訊
    var desc = Object.getOwnPropertyDescriptor(source, names[i]);
    if (typeof (desc.value) === "object" && desc.value !== null) {
      var obj;
      if (Array.isArray(desc.value)) {
        obj = [];
      } else {
        obj = {};
      }
      // 如果 desc 的值是物件的話,遞迴呼叫,直至屬性為費物件
      Object.defineProperty(target, names[i], {
        configurable: desc.configurable,
        enumerable: desc.enumerable,
        value: obj,
        writable: desc.writable
      });
      cloneObject(obj, desc.value);
    } else {
      // 如果 desc 的值不是物件,直接宣告
      Object.defineProperty(target, names[i], desc);
    }
  }
  return target;
}
複製程式碼

相關文章