原生JS如何實現包含各類類型數據的深克隆

引言

在各個社區查找使用原生js實現深克隆的方法,衆說紛紜,大多數實現效果並不理想,在此將各家之言總結一下,得出一個完美的解決方案。php

本文參考如下文章,感興趣者請移步到原文連接。css

淺克隆與深克隆

淺克隆

  • 原始類型爲值傳遞,對象類型仍爲引用傳遞。
  • 原始類型即便咱們採用普通的克隆方式仍能獲得正確的結果,緣由就是原始類型存儲的是對象的實際數據。

深克隆

  • 全部元素或屬性均徹底複製,與原對象徹底脫離。
  • 深克隆不是將原對象的引用賦值給新對象,而是新建一個對象,引用地址與原對象引用地址不一樣,再將原對象屬性拷貝到新對象中。

淺克隆的實現探索

數值克隆的實現 -->ok

const a = 1;
 let b = a;
 console.log(b);//1
 b = 2;
 console.log(a);// 1
 console.log(b);// 2
複製代碼

字符串克隆的實現 -->ok

const c="1";
  let d=c;
  console.log(d);//"1"
  d="2";
  console.log(c);// "1"
  console.log(d);//"2"
複製代碼

布爾值克隆的實現 -->ok

const x = true;
  let y = x;
  console.log(y);//true
  y = false;
  console.log(x);// true
  console.log(y);//false
複製代碼

Symbol值克隆的實現 -->ok

const s1 = Symbol('foo');
  let s2 = s1;
  console.log(s2);//Symbol(foo)
  console.log(s2 === s1);//true
  s2 = Symbol('bar');
  console.log(s1, s2); //Symbol(foo) Symbol(bar)
  console.log(s2 === s1);//false
複製代碼

函數克隆的實現 -->ok

const m = function () {alert(1); };
  let n = m;
  n();//1
  n = function () {alert(2); };
  m();//1
  n();//2
複製代碼

對象克隆的實現 --> not ok

let obj = {
  	a: 1,
  	b: 2
  }
  let obj2 = obj;
  console.log(obj);//{a: 1,b: 2}
  console.log(obj2);//{a: 1,b: 2}
  obj.a = 8;
  console.log(obj);//{a: 8,b: 2}
  console.log(obj2);//{a: 8,b: 2}
  //obj2 = obj 是值的引用,指向同一個地址值,其中一個發生變化,會影響另外一個
複製代碼

數組克隆的實現 --> not ok

由於:數組中能夠嵌套對象,對象沒法實現深克隆,數組也不能。html

深克隆的實現探索

經典的 json 方法實現深克隆-->非完美解決方法

function Person(name) {
  	this.name = name;
  }
  const Jack = new Person('Jack');
  const obj = {
      a: 1,
      b: function (arg) {
      console.log('我是獨一無二的,json複製不了我');
      },
      c: {
          d: 3,
          e: {
              f: [1,[2,[3,[4,[5]]]]],
              g: {
                  h: 5
              }
          }
      },
      date: [new Date(1536627600000), new Date(1540047600000)],
      reg: new RegExp('\\w+'),
      num: [NaN, Infinity, -Infinity],
      person: Jack,
  };
  //json方法克隆
  let obj2 = JSON.parse(JSON.stringify(obj));
  console.log(obj);
  console.log(obj2);
  obj.c.e.f = 1000;//改變源對象的值
  obj2.c.e.g.h = 2000;//改變克隆對象的值
  console.log(obj.c.e.f, obj2.c.e.f);
  console.log( obj.c.e.g.h, obj2.c.e.g.h);
複製代碼

運行結果: vue

json深克隆結果

由此可知 json 克隆方法有下列缺點:ios

  1. 若是 obj 裏面有時間對象(案例date),則 JSON.stringify 後再 JSON.parse 的結果,時間將只是字符串的形式。而不是時間對象
  2. 若是 obj 裏有 RegExp、Error 對象(案例reg),則序列化的結果將只獲得空對象
  3. 若是 obj 裏有函數(案例b),undefined,則序列化的結果會把函數或 undefined 丟失
  4. 若是 obj 裏有 NaN、Infinity 和 -Infinity,則序列化的結果會變成 null
  5. JSON.stringify() 只能序列化對象的可枚舉的自有屬性,例如 若是 obj 中的對象(案例person)是有構造函數生成的, 則使用 JSON.parse(JSON.stringify(obj)) 深拷貝後,會丟棄對象的 constructor,全部的構造函數會指向 Object
  6. 若是對象中存在循環引用的狀況也沒法正確實現深拷貝 所以,咱們能夠獲得結論:json 克隆的方法僅適用於簡單數據類型的克隆或者僅攜帶簡單數據的引用數據類型。

網絡常見的實現深克隆的方法 -->非完美解決方法

//數據初始化
  function Person(name) {
  	this.name = name;
  }
  const Jack = new Person('Jack');
  const obj = {
      a: 1,
      b: function (arg) {
      console.log('複製我,你牛逼');
      },
      c: {
          d: 3,
          e: {
              f: [1,[2,[3,[4,[5]]]]],
              g: {
                  h: 5
              }
          }
      },
      date: [new Date(1536627600000), new Date(1540047600000)],
      reg: new RegExp('\\w+'),
      num: [NaN, Infinity, -Infinity],
      person: Jack,
  };
  //深克隆實現方法
  function deepClone(origin, target) {
      const tar = target || {};
      for (let item in origin) {
      	if (origin.hasOwnProperty(item)) {
      		if (typeof origin[item] === 'object') {
      			tar[item] = Object.prototype.toString.call(origin[item]) === '[object Array]' ? [] : {};
      			deepClone(origin[item], tar[item]);
      		} else {
      			tar[item] = origin[item];
      		}
      	}
      }
      return tar;
  }
  //驗證代碼
  let obj2 = deepClone(obj, {});
  console.log(obj);
  console.log(obj2);
  obj.c.e.f = 1000;
  obj2.c.e.g.h = 2000;
  console.log(obj.c.e.f, obj2.c.e.f);
  console.log( obj.c.e.g.h, obj2.c.e.g.h);
複製代碼

運行結果:json

原生js不完美方法實現深克隆
由結果可知,該函數仍有不足:

  1. 若是obj裏面有時間對象(案例date),則獲得結果,時間將只是空對象
  2. 若是obj裏有RegExp、Error對象(案例reg),則序列化的結果將只獲得空對象
  3. 若是obj中的對象(person)是有構造函數生成的, 則使用該深拷貝後,會丟棄對象的constructor;

所以,該函數雖未能完美實現全部類型的深克隆,但對於平常開發足矣,平常開發通常只須要完成對象或數組的克隆,如不考慮時間對象、RegExp、Error對象和由構造函數生成的對象,該方法徹底足夠。gulp

原生js實現深克隆 -->完美解決方法一

//數據初始化
  function Person(name) {
  	this.name = name;
  }
  const Jack = new Person('Jack');
  const obj = {
      a: 1,
      b: function (arg) {
      console.log('複製我,你牛逼');
      },
      c: {
          d: 3,
          e: {
              f: [1,[2,[3,[4,[5]]]]],
              g: {
                  h: 5
              }
          }
      },
      date: [new Date(1536627600000), new Date(1540047600000)],
      reg: new RegExp('\\w+'),
      num: [NaN, Infinity, -Infinity],
      person: Jack,
  };
  //深克隆函數
  function deepClone(data) {
  	const type = this.judgeType(data);
  	let obj;
  	if (type === 'Array') {
  		obj = [];
  		for (let i = 0, len = data.length; i < len; i++ ) {
  			obj.push(this.deepClone(data[i]));
  		}
  	} else if (type === 'Object') {
  		obj = new data.constructor ();//可保持繼承鏈,解決該問題:若是obj中的對象是由構造函數生成的,則使用深拷貝後,會丟棄對象的constructor;
  		for (let key in data) {
  			obj[key] = this.deepClone(data[key]);//實現深克隆的關鍵
  		}
  	} else {
  		return data;
  	}
  	return obj;
  }
  //判斷類型函數
  function judgeType(obj) {
  	if (obj instanceof Element) {
  		return 'element';
  	}
  	return Object.prototype.toString.call(obj).slice(8,-1);
  }
  //驗證代碼
  let obj2 = deepClone(obj);
  console.log(obj);
  console.log(obj2);
  obj.c.e.f = 1000;
  obj2.c.e.g.h = 2000;
  console.log(obj.c.e.f, obj2.c.e.f);
  console.log( obj.c.e.g.h, obj2.c.e.g.h);
複製代碼

運行結果:數組

實現深克隆完美方法1
由運行結果可知,該方法徹底實現了全部數據類型的深克隆,是解決深克隆的完美方案之一,鑑定完畢!但這代碼量是否是多了些?!其實還能夠再優化,歡迎你們評論優化。

原生js實現深克隆 -->完美解決方法二

//數據初始化
  function Person(name) {
  	this.name = name;
  }
  const Jack = new Person('Jack');
  const obj = {
      a: 1,
      b: function (arg) {
      console.log('複製我,你牛逼');
      },
      c: {
          d: 3,
          e: {
              f: [1,[2,[3,[4,[5]]]]],
              g: {
                  h: 5
              }
          }
      },
      date: [new Date(1536627600000), new Date(1540047600000)],
      reg: new RegExp('\\w+'),
      num: [NaN, Infinity, -Infinity],
      person: Jack,
  };
  //深克隆函數
  function deepClone(obj) {
  	if (obj === null) return null;
  	if (typeof obj !== 'object') return obj;
  	if (obj.constructor === Date) return new Date(obj);
  	if (obj.constructor === RegExp) return new RegExp(obj);
  	const newObj = new obj.constructor ();  //保持繼承鏈
  	for (let key in obj) {
  		if (obj.hasOwnProperty(key)) {   //不遍歷其原型鏈上的屬性
  			const val = obj[key];
  			newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除與函數名的耦合。
  		}
  	}
  	return newObj;
  }
  //判斷代碼
  const obj2 = deepClone(obj);
  console.log(obj);
  console.log(obj2);
  obj.c.e.f = 1000;
  obj2.c.e.g.h = 2000;
  console.log(obj.c.e.f, obj2.c.e.f);
  console.log( obj.c.e.g.h, obj2.c.e.g.h);
複製代碼

運行結果:bash

原生實現深克隆方法2

相對於上一個完美解決方法,該方法的實現可圈可點,代碼量更少,一樣是達到了各類數據類型深克隆的實現,選擇哪一種方法都是沒問題的,看我的喜愛罷了。網絡

以上是我的參考衆家之言後總結的深克隆方法,若有不正確之處,請各方你們多多指點,謝謝!

相關文章
相關標籤/搜索