溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript中的深拷貝和淺拷貝的詳細(xì)介紹

發(fā)布時(shí)間:2021-09-09 14:19:41 來(lái)源:億速云 閱讀:113 作者:chen 欄目:web開(kāi)發(fā)

這篇文章主要介紹“JavaScript中的深拷貝和淺拷貝的詳細(xì)介紹”,在日常操作中,相信很多人在JavaScript中的深拷貝和淺拷貝的詳細(xì)介紹問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”JavaScript中的深拷貝和淺拷貝的詳細(xì)介紹”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

在說(shuō)深拷貝與淺拷貝前,我們先看兩個(gè)簡(jiǎn)單的案例:

//案例1  var num1 = 1, num2 = num1;  console.log(num1) //1  console.log(num2) //1  num2 = 2; //修改num2  console.log(num1) //1  console.log(num2) //2  //案例2  var obj1 = {x: 1, y: 2}, obj2 = obj1;  console.log(obj1) //{x: 1, y: 2}  console.log(obj2) //{x: 1, y: 2}  obj2.x = 2; //修改obj2.x  console.log(obj1) //{x: 2, y: 2}  console.log(obj2) //{x: 2, y: 2}

按照常規(guī)思維,obj1應(yīng)該和num1一樣,不會(huì)因?yàn)榱硗庖粋€(gè)值的改變而改變,而這里的obj1 卻隨著obj2的改變而改變了。同樣是變量,為什么表現(xiàn)不一樣呢?這就要引入JS中基本類型和引用類型的概念了。

基本類型和引用類型

ECMAScript變量可能包含兩種不同數(shù)據(jù)類型的值:基本類型值和引用類型值。基本類型值指的是那些保存在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,即這種值完全保存在內(nèi)存中的一個(gè)位置。而引用類型值是指那些保存堆內(nèi)存中的對(duì)象,意思是變量中保存的實(shí)際上只是一個(gè)指針,這個(gè)指針指向內(nèi)存中的另一個(gè)位置,該位置保存對(duì)象。

打個(gè)比方,基本類型和引用類型在賦值上的區(qū)別可以按“連鎖店”和“單店”來(lái)理解:基本類型賦值等于在一個(gè)新的地方安裝連鎖店的規(guī)范標(biāo)準(zhǔn)新開(kāi)一個(gè)分店,新開(kāi)的店與其他舊店互不相關(guān),各自運(yùn)營(yíng);而引用類型賦值相當(dāng)于一個(gè)店有兩把鑰匙,交給兩個(gè)老板同時(shí)管理,兩個(gè)老板的行為都有可能對(duì)一間店的運(yùn)營(yíng)造成影響。

上面清晰明了的介紹了基本類型和引用類型的定義和區(qū)別。目前基本類型有:

Boolean、Null、Undefined、Number、String、Symbol,引用類型有:Object、Array、Function。之所以說(shuō)“目前”,因?yàn)镾ymbol就是ES6才出來(lái)的,之后也可能會(huì)有新的類型出來(lái)。

再回到前面的案例,案例1中的值為基本類型,案例2中的值為引用類型。案例2中的賦值就是典型的淺拷貝,并且深拷貝與淺拷貝的概念只存在于引用類型。

深拷貝與淺拷貝

既然已經(jīng)知道了深拷貝與淺拷貝的來(lái)由,那么該如何實(shí)現(xiàn)深拷貝?我們先分別看看Array和Object自有方法是否支持:

Array  

var arr1 = [1, 2], arr2 = arr1.slice();  console.log(arr1); //[1, 2]  console.log(arr2); //[1, 2]  arr2[0] = 3; //修改arr2  console.log(arr1); //[1, 2]  console.log(arr2); //[3, 2]

此時(shí),arr2的修改并沒(méi)有影響到arr1,看來(lái)深拷貝的實(shí)現(xiàn)并沒(méi)有那么難嘛。我們把a(bǔ)rr1改成二維數(shù)組再來(lái)看看:

var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();  console.log(arr1); //[1, 2, [3, 4]]  console.log(arr2); //[1, 2, [3, 4]]  arr2[2][1] = 5;   console.log(arr1); //[1, 2, [3, 5]]  console.log(arr2); //[1, 2, [3, 5]]

咦,arr2又改變了arr1,看來(lái)slice()只能實(shí)現(xiàn)一維數(shù)組的深拷貝。

具備同等特性的還有:concat、Array.from() 。

Object

1、Object.assign()

var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);  console.log(obj1) //{x: 1, y: 2}  console.log(obj2) //{x: 1, y: 2}  obj2.x = 2; //修改obj2.x  console.log(obj1) //{x: 1, y: 2}  console.log(obj2) //{x: 2, y: 2}  var obj1 = {      x: 1,       y: {          m: 1      }  };  var obj2 = Object.assign({}, obj1);  console.log(obj1) //{x: 1, y: {m: 1}}  console.log(obj2) //{x: 1, y: {m: 1}}  obj2.y.m = 2; //修改obj2.y.m  console.log(obj1) //{x: 1, y: {m: 2}}  console.log(obj2) //{x: 2, y: {m: 2}}

經(jīng)測(cè)試,Object.assign()也只能實(shí)現(xiàn)一維對(duì)象的深拷貝。

2、JSON.parse(JSON.stringify(obj))

var obj1 = {      x: 1,       y: {          m: 1      }  };  var obj2 = JSON.parse(JSON.stringify(obj1));  console.log(obj1) //{x: 1, y: {m: 1}}  console.log(obj2) //{x: 1, y: {m: 1}}  obj2.y.m = 2; //修改obj2.y.m  console.log(obj1) //{x: 1, y: {m: 1}}  console.log(obj2) //{x: 2, y: {m: 2}}

JSON.parse(JSON.stringify(obj)) 看起來(lái)很不錯(cuò),不過(guò)MDN文檔 的描述有句話寫的很清楚:

undefined、任意的函數(shù)以及 symbol 值,在序列化過(guò)程中會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí))或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí))。

我們?cè)賮?lái)把obj1改造下:

var obj1 = {      x: 1,      y: undefined,      z: function add(z1, z2) {          return z1 + z2      },      a: Symbol("foo")  };  var obj2 = JSON.parse(JSON.stringify(obj1));  console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}  console.log(JSON.stringify(obj1)); //{"x":1}  console.log(obj2) //{x: 1}

發(fā)現(xiàn),在將obj1進(jìn)行JSON.stringify()序列化的過(guò)程中,y、z、a都被忽略了,也就驗(yàn)證了MDN文檔的描述。既然這樣,那JSON.parse(JSON.stringify(obj))的使用也是有局限性的,不能深拷貝含有undefined、function、symbol值的對(duì)象,不過(guò)JSON.parse(JSON.stringify(obj))簡(jiǎn)單粗暴,已經(jīng)滿足90%的使用場(chǎng)景了。

經(jīng)過(guò)驗(yàn)證,我們發(fā)現(xiàn)JS 提供的自有方法并不能徹底解決Array、Object的深拷貝問(wèn)題。只能祭出大殺器:遞歸

function deepCopy(obj) {      // 創(chuàng)建一個(gè)新對(duì)象      let result = {}      let keys = Object.keys(obj),          key = null,          temp = null;      for (let i = 0; i < keys.length; i++) {          key = keys[i];              temp = obj[key];          // 如果字段的值也是一個(gè)對(duì)象則遞歸操作          if (temp && typeof temp === 'object') {              result[key] = deepCopy(temp);          } else {          // 否則直接賦值給新對(duì)象              result[key] = temp;          }      }      return result;  }  var obj1 = {      x: {          m: 1      },      y: undefined,      z: function add(z1, z2) {          return z1 + z2      },      a: Symbol("foo")  };  var obj2 = deepCopy(obj1);  obj2.x.m = 2;  console.log(obj1); //{x: {m: 1}, y: undefined, z: &fnof;, a: Symbol(foo)}  console.log(obj2); //{x: {m: 2}, y: undefined, z: &fnof;, a: Symbol(foo)}

可以看到,遞歸***的解決了前面遺留的所有問(wèn)題,我們也可以用第三方庫(kù):jquery的$.extend和lodash的_.cloneDeep來(lái)解決深拷貝。上面雖然是用Object驗(yàn)證,但對(duì)于Array也同樣適用,因?yàn)锳rray也是特殊的Object。

到這里,深拷貝問(wèn)題基本可以告一段落了。但是,還有一個(gè)非常特殊的場(chǎng)景:

循環(huán)引用拷貝

var obj1 = {      x: 1,       y: 2  };  obj1.z = obj1;  var obj2 = deepCopy(obj1);

此時(shí)如果調(diào)用剛才的deepCopy函數(shù)的話,會(huì)陷入一個(gè)循環(huán)的遞歸過(guò)程,從而導(dǎo)致爆棧。jquery的$.extend也沒(méi)有解決。解決這個(gè)問(wèn)題也非常簡(jiǎn)單,只需要判斷一個(gè)對(duì)象的字段是否引用了這個(gè)對(duì)象或這個(gè)對(duì)象的任意父級(jí)即可,修改一下代碼:

function deepCopy(obj, parent = null) {      // 創(chuàng)建一個(gè)新對(duì)象      let result = {};      let keys = Object.keys(obj),          key = null,          temp= null,          _parent = parent;      // 該字段有父級(jí)則需要追溯該字段的父級(jí)      while (_parent) {          // 如果該字段引用了它的父級(jí)則為循環(huán)引用          if (_parent.originalParent === obj) {              // 循環(huán)引用直接返回同級(jí)的新對(duì)象              return _parent.currentParent;          }          _parent = _parent.parent;      }      for (let i = 0; i < keys.length; i++) {          key = keys[i];          temp= obj[key];          // 如果字段的值也是一個(gè)對(duì)象          if (temp && typeof temp=== 'object') {              // 遞歸執(zhí)行深拷貝 將同級(jí)的待拷貝對(duì)象與新對(duì)象傳遞給 parent 方便追溯循環(huán)引用              result[key] = DeepCopy(temp, {                  originalParent: obj,                  currentParent: result,                  parent: parent              });          } else {             result[key] = temp;          }      }      return result;  }  var obj1 = {      x: 1,       y: 2  };  obj1.z = obj1;  var obj2 = deepCopy(obj1);  console.log(obj1); //太長(zhǎng)了去瀏覽器試一下吧~   console.log(obj2); //太長(zhǎng)了去瀏覽器試一下吧~

到此,關(guān)于“JavaScript中的深拷貝和淺拷貝的詳細(xì)介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI