Clone:你真的知道深淺麼?

數據分爲基本數據類型(String, Number, Boolean, Null, Undefined,Symbol)和對象數據類型。javascript

一、基本數據類型的特色:直接存儲在棧(stack)中的數據前端

二、引用數據類型的特色:存儲的是該對象在棧中引用,真實的數據存放在堆內存裏java

相信這兩句你們都很熟悉,可是,這種描述並不嚴謹,因此補充以下描述:數組

一、基本數據類型:變量對應的是一個值瀏覽器

二、引用數據類型:變量對應的是一個地址,地址指向堆內存中的某個對象,函數

請注意,基本數據類型不是全存在棧中,只有直接聲明的變量纔會在棧中。工具

基本類型若是是全局的,也放在堆上。位於引用類型內的基本類型,也是放在堆上。post

淺拷貝是按位拷貝對象,它會建立一個新對象,這個對象有着原始對象屬性值的一份拷貝。若是屬性是基本類型,拷貝的就是基本類型的值;若是屬性是內存地址(引用類型),拷貝的就是內存地址 ,所以若是原始對象中的某個引用屬性改變了指針所指向的對象的屬性,就會影響到另外一個對象。ui

上圖說話:spa

// 定義引用類型 refObj1
const refObj1 = [1, 2, 3];

// 定義原始對象
const SourceObj = {
    field1: 'source',
    refObj1,
}

// 執行淺拷貝操做, 得到 CopiedObj
const CopiedObj = {...SourceObj}; // 淺拷貝方法下面會具體介紹

// 打印拷貝對象數據
CopiedObj.field1; // 'source'
CopiedObj.refObj1; // [1, 2, 3];

// 改變原始對象
SourceObj.field1 = 'no source';
SourceObj.refObj1[0] = 999;

// 打印拷貝對象數據
CopiedObj.field1; // 'source' 未受影響
CopiedObj.refObj1; // [999, 2, 3]; 受到影響
複製代碼

上面的代碼能夠複製到瀏覽器控制檯執行一遍,這樣你會更加清晰一些。

如上代碼所示,淺拷貝建立了一個新的對象,並新建兩個屬性,將原始對象中隊形屬性的值完整的複製過來,包括field1中存儲的基本類型的值,和refObj1中存儲的一個數組的地址。

所以,雖然存在兩個變量 SourceObj.refObj1CopiedObj.refObj1 ,但只存在一個數組對象。

淺拷貝的實現方式

  • Object.assign() ****

    Object.assign({}, origin)

  • spread展開 ,適用於對象或數組

    copyObj = { ...originObj };

    copyArr = [ ...originArr ];

  • for ... infor ... of

簡單來說,深拷貝與淺拷貝惟一的區別在於,若是原始對象中包含引用類型的屬性,則會對該屬性所對應的對象再次進行淺拷貝。最終,原始對象與拷貝對象就沒有任何關聯了,就像執行了文件的複製粘貼同樣,對源文件的任何操做都不會影響拷貝對象。

上圖說話

深拷貝實現方式

  • JSON.parse(JSON.stringify(obj))經過JSON的2次轉換深拷貝obj,不過沒法拷貝undefinedsymbol屬性,沒法拷貝循環引用對象
  • 使用第三方工具庫庫
  • 本身實現一個深拷貝函數

理論上,咱們只須要實現一個完整的淺拷貝,再加上一個遞歸調用就能夠實現深拷貝,nice~

//使用遞歸的方式進行拷貝, 引入map存儲已經拷貝過的對象,防止循環引用
function deepClone(source, map = new Map()) {
  // 基本數據類型直接返回值
  if (typeof source !== 'object' || source === null) {
    return source;
  }
  // 若已經clone,則直接返回clone的對象
  if (s = map.get(source)) {
    return s;
  }
  // 生成clone對象
  const clone = Array.isArray(source) ? [] : {};
  // 存儲已clone的對象,防止循環引用
  map.set(source, clone);
  // 遞歸調用
  for (const key of Object.keys(source)) {
    clone[key] = deepClone(source[key], map);
  }
  
  return clone;
}

複製代碼

建議本身手敲一遍,要不個人知識仍是個人,到不了你的腦子裏~

下面的看看就行了

//補充處理 Map、Set 和 Symbol 類型
function deepClonePlus(source, map = new Map()) {
  if (typeof source !== 'object' || source === null) {
    return source;
  }
  if (s = map.get(source)) {
    return s;
  }
  const clone = Array.isArray(source) ? [] : {};
  map.set(source, clone);
  const allKeys = Reflect.ownKeys(source);

  for (const key of allKeys) {
    const value = a[key];
    const type = Object.prototype.toString.call(value);
    switch (type) {
      case '[object Object]':
      case '[object Array]': clone[key] = deepClone(source[key], map); break;
      case '[object Set]':
        const set = new Set();
        value.forEach(v => set.add(deepClonePlus(v, map)));
        clone[key] = set;
        break;
      case '[object Map]':
        const temoMap = new Map();
        value.forEach((v, i) => temoMap.set(i, deepClonePlus(v, map)));
        clone[key] = temoMap;
        break;
      case '[object Symbol]':
        clone[key] = Object(Symbol.prototype.valueOf.call(value)); break;
      default:
        clone[key] = new value.constructor(value); break;
    }
  }

  return clone;
}
複製代碼

Symbol 相關知識請移步朝花夕拾,從新介紹javascript類型

相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)

若是你收穫了新知識,請給做者點個贊吧~

相關文章
相關標籤/搜索