1. 程式人生 > 實用技巧 >559 物件和陣列的深淺拷貝

559 物件和陣列的深淺拷貝

陣列淺拷貝練習題

let utils = (function () {
  /*
   * toArray:轉換為陣列的方法
   *   @params
   *      不固定數量,不固定型別
   *   @return
   *      [Array] 返回的處理後的新陣列
   * by haha on 2020
   */

  // 方法1
  function toArray(...args) {
    // ES6的剩餘運算子獲取的引數集合本身就是陣列
    return args;
  }

  // 方法2
  function toArray() {
    // arguments是實參集合,獲取的結果是一個類陣列(箭頭函式中沒有arguments),不能直接調取陣列的辦法(因為它不是ARRAY的例項)
    // return [...arguments];
    return Array.from(arguments);
  }

  // 方法3
  function toArray() {
    // 把類陣列轉換為陣列(克隆)
    // 原理:只要把slice執行,讓方法中的this變為arguments,這樣就可以實現把類陣列arguments轉換為陣列
    // 1.讓slice執行: Array.prototype.slice()  或者  [].slice()
    // 2.把this改變成為arguments   [].slice.call(arguments)
    // 前提:運算元組的程式碼(內建方法中的程式碼)也需要適配改變的THIS(類陣列)

    [].forEach.call(arguments, item => {
      console.log(item);
    });

    return [].slice.call(arguments);

    /* let arr = [];
    for (let i = 0; i < arguments.length; i++) {
      arr.push(arguments[i]);
    }
    return arr; */
  }

  return {
    toArray
  };
})();

let ary = utils.toArray(10, 20, 30); // => [10,20,30]
console.log(ary);

ary = utils.toArray('A', 10, 20, 30); // => ['A',10,20,30]
console.log(ary);


// 簡單模擬slice的實現
Array.prototype.slice = function slice() {
  // this -> ary
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    arr.push(this[i]);
  }
  return arr;
};
// let ary = [10, 20, 30, 40];
// ary.slice() 把陣列克隆一份
// console.log(ary.slice());

淺拷貝

  • 克隆:記憶體地址是不一樣的

【for迴圈也可以實現淺拷貝。】

實現物件的克隆:

Object.assign:淺比較/淺克隆

{...obj}:展開運算子,也只能展開第一級,也是淺克隆

newObj = obj :這不是克隆,只是賦值

let obj1 = { a: 1, b: 2, c: [5, 6] }
let obj2 = Object.assign({}, obj1)
console.log(obj2, obj1 == obj2) // {a: 1, b: 2, c: Array(2)}   false
console.log(obj1.c === obj2.c) // true

let obj3 = { ...obj1 }
console.log(obj3) // {a: 1, b: 2, c: Array(2)}
console.log(obj1.c === obj3.c) // true

實現陣列的克隆

slice、concat... 這些都是淺克隆

forEach、map、reduce... 遍歷也只是遍歷第一級

let newArr = arr.concat([]);

console.log(newArr === arr); // => false

console.log(newArr[2] === arr[2]); // => true

// 補充:使用for迴圈實現物件、陣列的淺拷貝
// 使用for迴圈實現物件的淺拷貝
let obj1 = { a: 1, b: 2, c: [5, 6] }
let newObj = {}

for (let i in obj1) {
    newObj[i] = obj1[i]
}

console.log(newObj) // {a: 1, b: 2, c: Array(2)}
console.log(obj1.c === newObj.c) // true


// ---------------------------


// 使用for迴圈實現陣列的淺拷貝
let arr = [11, 22, {name: '哈哈'}]
let newArr = []

for(let i=0;i<arr.length;i++) {
    newArr[i] = arr[i]
}
console.log(newArr) // [11, 22, {name: '哈哈'}]
console.log(newArr[2] === arr[2]) // true

深拷貝

JSON.parse(JSON.stringify())

// 暴力法:把原始資料直接變為字串,再把字串變為物件(此時瀏覽器要重新開闢所有的記憶體空間),實現出深度克隆、深拷貝
let arr = [11, 22, { name: '哈哈' }]
let newArr = JSON.parse(JSON.stringify(arr));
console.log(newArr);
console.log(newArr === arr); // => false
console.log(newArr[2] === arr[2]); // => false

// 出現的問題:正則會變為空物件,日期變為字串,函式、undefined、Symbol直接消失,BigInt直接報錯
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
console.log(newObj === obj); // => false
console.log(newObj.c === obj.c); // => false

自己實現深拷貝

/* 
補充:獲取例項的建構函式
console.log((11).constructor) // Number函式
console.log(('11').constructor) // String函式
console.log((true).constructor) // Boolean函式
console.log((false).constructor) // Boolean函式
console.log((BigInt(10)).constructor) // BigInt函式
console.log((BigInt('33')).constructor) // BigInt函式
// console.log((undefined).constructor) // 報錯
// console.log((null).constructor) // 報錯
console.log((function fn() {}).constructor) // Function函式
console.log(({}).constructor) // Object函式
*/


/* 陣列或者物件的深克隆/淺克隆 */
let obj = {
  a: 100,
  b: [10, 20, 30],
  c: function () { },
  d: /^\d+$/,
  e: new Date(),
  f: Symbol('f'),
  g: BigInt('10')
};

let arr = [10, [100, 200], {
  x: 10,
  y: 20
}];

// 深克隆、深拷貝
function cloneDeep(obj) {
  // 獲取例項的建構函式 【當obj是部分基本資料型別,或者是空物件{}的時候,這樣寫有問題,應該先用Object(obj)轉換】
  const constructor = obj.constructor;

  if (obj === null) return null;
  if (typeof obj !== "object") return obj;
  // 正則例項、日期例項的constructor都是返回RegExp、Date建構函式 --> console.log(new Date().constructor):Date、 console.log(/\d/.constructor):RegExp.
  // constructor.name:返回建構函式的名字:(11).constructor.name --> Number,是constructor.name,不是constructor
  if (/^(RegExp|Date)$/i.test(constructor.name)) return new constructor(obj);

  // 重新建立一個constructor類的例項,用於儲存克隆的資料
  // 不能寫成 let clone = {},因為這樣寫就是往物件裡克隆,而有時候克隆的是陣列,so應該往obj同樣的資料型別的例項中克隆
  let clone = new constructor();

  for (let key in obj) {
    if (!obj.hasOwnProperty(key)) break;
    // 如果不遞迴,只會實現淺拷貝。如果obj[key]是引用型別,需要重新cloneDeep。不管是基本、引用型別,都cloneDeep。
    clone[key] = cloneDeep(obj[key]);
  }
  return clone;
}

let newArr = cloneDeep(arr);
console.log(newArr);
console.log(newArr === arr); // => false
console.log(newArr[2] === arr[2]); // => false 

let newObj = cloneDeep(obj);
console.log(newObj);
console.log(newObj === obj); // => false
console.log(newObj.d === obj.d); // => false