深拷貝VS淺拷貝

深拷貝VS淺拷貝

本文主要對(duì)深拷貝&淺拷貝的解釋及實(shí)現(xiàn)做一下簡(jiǎn)單記錄。

之所以會(huì)有深拷貝與淺拷貝之分庄拇,是因?yàn)椴煌瑪?shù)據(jù)類型的數(shù)據(jù)在內(nèi)存中的存儲(chǔ)區(qū)域不一樣。

堆和棧是計(jì)算機(jī)中劃分出來用來存儲(chǔ)的區(qū)域,其中堆(heap)則是動(dòng)態(tài)分配的內(nèi)存瓤狐,大小不定也不會(huì)自動(dòng)釋放;而棧(stack)為自動(dòng)分配的內(nèi)存空間批幌,它由系統(tǒng)自動(dòng)釋放础锐。存放在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,數(shù)據(jù)大小確定荧缘,內(nèi)存空間大小可以分配皆警,是直接按值存放的,所以可以直接訪問截粗。

眾所周知信姓,JavaScript中的數(shù)據(jù)分為(基本類型和引用類型)。五種基本類型(boolean,number,undefined,null,string,)的數(shù)據(jù)的原始值是大小確定不可變的,所以放在棧內(nèi)存中绸罗;而引用類型(object)是放在堆內(nèi)存中的意推,對(duì)應(yīng)賦值的變量只是一個(gè)存放在棧內(nèi)存中的指針,也就是一個(gè)指向引用類型存放在堆內(nèi)存中的地址珊蟀。

image-20180811123105339

引用類型比較與基本類型比較的區(qū)別

引用類型是引用的比較菊值,例如

// a與b是兩個(gè)引用類型,但其指針指向的是兩個(gè)不同的引用
let a = [1,2,3];
let b = [1,2,3];
a===b; //false

// 引用類型的賦值操作,b的指針也是指向與a同樣的引用
let a = [1,2,3];
let b = a;
a===b; //true

而基本類型間的比較是值的比較腻窒,例如

let a = 1;
let b = 1;
a===b; //true

let a = 1;
let b = a;
a===b; //true

賦值操作:傳值與傳址的區(qū)別

在對(duì)基本類型進(jìn)行賦值操作的時(shí)候?qū)嶋H是傳值昵宇,即在棧內(nèi)存中新開一個(gè)區(qū)域給新的變量,然后再把值賦給它儿子。所以基本類型賦值的變量是兩個(gè)互相獨(dú)立變量瓦哎。

let a = 10;
let b = a;
b++;
console.log(a); //10
console.log(b); //11
image-20180811140720986

而引用類型的賦值操作是傳址,只是在棧內(nèi)存中新增了一個(gè)變量典徊,同時(shí)賦值給這個(gè)變量的只是保存在堆內(nèi)存中對(duì)象的地址杭煎,也就是指針的指向。因此這兩個(gè)變量的指針指向同一個(gè)地址卒落,相互操作也就會(huì)有影響羡铲。

let a = {};
let b = a;

a.name = 'slevin';
console.log(b.name); //'slevin'
console.log(a.name); //'slevin'
console.log(a===b); //true
引用類型賦值操作

需要注意的是,引用類型的賦值并不算是淺拷貝儡毕,因?yàn)橘x值操作只相當(dāng)于是引用也切,兩個(gè)變量還是指向同一對(duì)象,任何一方改變了都會(huì)影響到另一方腰湾;但淺拷貝出來的變量與源變量已經(jīng)不同雷恃,在不包含子對(duì)象的情況下(此情況即為深拷貝),一方改變不會(huì)影響到另一方费坊。如下賦值操作:

let a = {};
let b = a;

b.name = 'slevin';
console.log(a.name); //'slevin';對(duì)b操作影響到了a
console.log(a===b); //true倒槐;因?yàn)閮烧咧赶蛲粋€(gè)對(duì)象

淺拷貝實(shí)現(xiàn)方法

  1. 自定義實(shí)現(xiàn)方法:

    // 淺拷貝的方法
    function shallowCopy(srcObj) {
      let copy = {};
      for (let key in srcObj) {
          //只拷貝自身的屬性,__proto__上繼承來的屬性不做拷貝附井,也可去掉這個(gè)限制
          if (srcObj.hasOwnProperty(key)) {
              copy[key] = srcObj[key];
          }
      }
      return copy;
    }
    
    let obj = {
      name: 'slevin',
      age: 18,
      language: {
          'english': 'good',
          'mandarin': 'wonderful',
      }
    }
    
    let copy = shallowCopy(obj);
    copy.age = 28;
    
    console.log(obj.age); // 18; 并沒有改變?cè)磳?duì)象
    console.log(obj === copy); //false;兩者指向不是同一個(gè)對(duì)象
    
  2. 利用Object.assign(target,...sources)可將一個(gè)或多個(gè)源對(duì)象上可枚舉屬性的值復(fù)制到目標(biāo)對(duì)象讨越,并返回目標(biāo)對(duì)象。但注意永毅,只能做一層屬性的淺拷貝把跨。

    let obj = {
      name: 'slevin',
      age: 18,
      language: {
          english: 'good',
          mandarin: 'wonderful',
          test: [1, 2, 3]
      },
      fn:function(){
          console.log('this:',this.name);
      }
    }
    
    let copy = Object.assign({},obj);
    //不會(huì)改變?cè)磳?duì)象
    copy.age = 22;
    //會(huì)改變?cè)磳?duì)象
    copy.language.english = 'bad';
    console.log('copy===obj:',copy===obj);//false
    console.log('copy:',copy);
    console.log('obj:',obj);
    
  3. 對(duì)于數(shù)組來說,也可以使用Array.prototype.slice()方法和Array.prototype.concact()方法

    let obj = [1,2,['a','b','c']]
    let copy = obj.slice();
    // 不會(huì)改變?cè)磳?duì)象
    copy[0]=111;
    // 會(huì)改變?cè)磳?duì)象
    copy[2][0]='aaa';
    console.log('copy===obj:',copy===obj);//false
    console.log('copy:',copy);
    console.log('obj:',obj);
    

深拷貝及實(shí)現(xiàn)方法

簡(jiǎn)單來說沼死,深拷貝就是把對(duì)象以及對(duì)象的子對(duì)象進(jìn)行拷貝着逐。因?yàn)闇\拷貝只復(fù)制了一層對(duì)象的屬性,而如果對(duì)象的數(shù)值也為引用類型意蛀,那么拷貝過來依然是個(gè)引用地址耸别,在拷貝對(duì)象上對(duì)子對(duì)象的值進(jìn)行操作會(huì)改變?cè)紨?shù)據(jù)中的值。

例如上面obj淺拷貝得到copy對(duì)象县钥,如果在copy對(duì)象上改變子對(duì)象copy.language上的屬性值太雨,會(huì)影響到源對(duì)象obj

copy.language.english = 'bad';
copy.language.mandarin = 'bad';
console.log(obj.language); // {'english': 'bad','mandarin': 'bad',}

那么如何深拷貝呢?思路就是遞歸調(diào)用剛剛的淺拷貝魁蒜,遍歷所有值為引用類型的屬性,然后依次賦值給另外一個(gè)對(duì)象即可。

  1. 方法一兜看,自定義實(shí)現(xiàn):

    /**將源對(duì)象source深度合并到目標(biāo)對(duì)象target上
    * source 源對(duì)象
    * target 目標(biāo)對(duì)象锥咸,默認(rèn){}
    * deep 是否深度合并,默認(rèn)true
    */
    function deepCopy (source,target={},deep=true) {
      for (let key in source){
          // 深拷貝细移,而且只拷貝自身屬性.(默認(rèn)傳入的source為對(duì)象)
          if (deep && source.hasOwnProperty(key)){
              if (typeof(source[key])==='object'){
                  // // source[key] 是對(duì)象搏予,而 target[key] 不是對(duì)象, 則 target[key] = {} 初始化一下弧轧,否則遞歸會(huì)出錯(cuò)的
                  // if (!(target[key] instanceof Object) && (source[key] instanceof Object)){
                  // target[key] = {};
                  // }
                  // // source[key] 是數(shù)組雪侥,而 target[key] 不是數(shù)組,則 target[key] = [] 初始化一下精绎,否則遞歸會(huì)出錯(cuò)的
                  // if (!Array.isArray(target[key]) && Array.isArray(source[key])) {
                  // target[key] = [];
                  // }
                  // 上面的代碼可以簡(jiǎn)化為以下:
                  target[key] = Array.isArray(source[key]) ? [] : {};
    
                  // 遞歸執(zhí)行拷貝
                  deepCopy(source[key],target[key],true);
              } else {
                  target[key] = source[key];
              }
          }
      }
      return target;
    }
    
    
    let obj = {
      name: 'slevin',
      age: 18,
      language: {
          english: 'good',
          mandarin: 'wonderful',
          test:[1,2,3]
      }
    }
    let copy = deepCopy(obj);
    copy.language.test[0] = 111;
    
    console.log('copy:',copy); //111 改變目標(biāo)對(duì)象的子對(duì)象屬性值
    console.log('obj:',obj); //1 對(duì)應(yīng)源對(duì)象上沒有改變
    
  2. 利用JSON.parse(JSON.stringify(copyObj))方法

    let obj = {
      name: 'slevin',
      age: 18,
      language: {
          english: 'good',
          mandarin: 'wonderful',
          test: [1, 2, 3]
      }
    }
    let copy = JSON.parse(JSON.stringify(obj))
    copy.language.test[0]=111;
    
    console.log('copy===obj:',copy===obj);//false
    console.log('copy.language.test[0]:',copy.language.test[0]);//111
    console.log('obj.language.test[0]:',obj.language.test[0]);//1
    

    但要注意速缨,此方法有兩個(gè)缺點(diǎn)

    • 如果源對(duì)象屬性值有函數(shù),無法拷貝下來
    • 無法拷貝源對(duì)象原型鏈上的屬性和方法
    let obj = {
      name: 'slevin',
      age: 18,
      language: {
          english: 'good',
          mandarin: 'wonderful',
          test: [1, 2, 3]
      },
      fn:function(){
          console.log('this:',this.name);
      }
    }
    let copy = JSON.parse(JSON.stringify(obj));
    console.log('copy===obj:',copy===obj);//false
    console.log('obj.fn:',obj.fn); //fn(){}...
    console.log('copy.fn:',copy.fn); //undefined
    

最后代乃,再補(bǔ)一張引用類型的賦值操作旬牲、淺拷貝深拷貝對(duì)比圖,加深印象

引用類型的賦值操作搁吓、淺拷貝深拷貝對(duì)比

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市堕仔,隨后出現(xiàn)的幾起案子擂橘,更是在濱河造成了極大的恐慌,老刑警劉巖摩骨,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件通贞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡仿吞,警方通過查閱死者的電腦和手機(jī)滑频,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唤冈,“玉大人峡迷,你說我怎么就攤上這事∧愫纾” “怎么了绘搞?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)傅物。 經(jīng)常有香客問我夯辖,道長(zhǎng),這世上最難降的妖魔是什么董饰? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任蒿褂,我火速辦了婚禮圆米,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啄栓。我一直安慰自己娄帖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布昙楚。 她就那樣靜靜地躺著近速,像睡著了一般。 火紅的嫁衣襯著肌膚如雪堪旧。 梳的紋絲不亂的頭發(fā)上削葱,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音淳梦,去河邊找鬼析砸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谭跨,可吹牛的內(nèi)容都是我干的干厚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼螃宙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蛮瞄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谆扎,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤挂捅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后堂湖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闲先,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年无蜂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伺糠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斥季,死狀恐怖训桶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酣倾,我是刑警寧澤舵揭,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站躁锡,受9級(jí)特大地震影響午绳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜映之,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一拦焚、第九天 我趴在偏房一處隱蔽的房頂上張望蜡坊。 院中可真熱鬧,春花似錦耕漱、人聲如沸算色。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峡钓,卻和暖如春妓笙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背能岩。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工寞宫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拉鹃。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓辈赋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親膏燕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钥屈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容