深拷貝的缺點_拷貝?還傻傻分不清深淺?

「引言」

?

臣聞求木之長者,必固其根本;欲流之遠者,必浚其泉源。

??????????????????????????????????????---- 魏征 《諫太宗十思疏》

?

或許你會問到,網上已經把深淺拷貝(算一個面試的高頻考點了吧)的文章都快寫爛了,為什么自己還要重新操刀寫一遍呢!?

?

首先,一些文章,講不清也道不明本質;另外,確實有很優秀的人寫的很是生動,讓我直接看到了風景,卻不知道沿途是不是也有自己錯過的美景,唯有嘗試過,才會真正成為自己的~

?

首先,我們先來看一張筆者整理的腦圖,梳理一下~

希望通過本文的總結,你會有以下幾點收獲:

  • 什么是深淺拷貝?他們與賦值有何區別?
  • 淺拷貝的實現方式有哪些?
  • 深拷貝的實現方式有哪些?

本章節直接從拷貝開始說起,對于基本數據類型,引用數據類型之前的區別,可以看看上面的思維導圖

9add1020136171d2f67e01da3a73799f.png

引用數據類型拷貝

我們從以下三個方面來看看這塊的內容

  • 賦值
  • 淺拷貝
  • 深拷貝

賦值

引用類型的賦值是傳址。其引用指向堆中的同一個對象,因此操作其中一個對象,另一個對象是會跟著一起變的。

舉個栗子:

let?lucy?=?{
????name:?'lucy',
????age:?23
}
let?lilei?=?lucy
lilei.name?=?'lilei'
lilei.age?=?24
console.log('lucy',?lucy)??//?lucy?{name:?"lilei",?age:?24}
console.log('lilei',?lilei)?//?lilei?{name:?"lilei",?age:?24}

上面栗子中可以看出來,修改了 lilei 的數據,lucy也會跟著變。這是初學者(筆者也曾這樣)經常犯的一個錯,后來深刻理解了對象內存的重要性!改掉了這個惡習~

那么我們該如何不讓彼此之間不影響呢?

接下來我們引出了 拷貝這個概念,拷貝又分深拷貝和淺拷貝。

來看一看具體是什么和相關區別吧。

「注意:」

  1. 對于基本數據類型而言,并沒有深淺拷貝的區別
  2. 深淺拷貝都是對于引用數據類型而言的
  3. 如果我們要賦值對象的所有屬性都不是引用類型時,我們可以使用淺拷貝,遍歷并復制,最后返回一個對象

「本質&使用場景」:都是復雜對象,就是說對象的屬性還是對象

淺拷貝

「本質」:只復制一層對象,當對象的屬性是引用類型時,實質復制的是其引用,當引用值指向發生改變時也會跟著改變

「原理」:遍歷并復制,最后返回一個對象

來動手實現一個簡單的淺拷貝吧

//?實現淺拷貝?for??in?
let?shallowCopy?=?(obj)?=>?{
????let?rst?=?{}
????for?(let?key?in?obj)?{
????????//?只復制本身擁有的屬性(非繼承過來的屬性)
????????if?(obj.hasOwnProperty(key))?{
????????????rst[key]?=?obj[key]
????????}
????}
????return?rst
}

let?lucy?=?{
????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming']
}
let?lilei?=?shallowCopy(lucy)
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{name:?"lucy",?age:?23,?hobby:?['reading',?'swimming']}
console.log('lilei',?lilei)
//?lilei?{name:?"lilei",?age:?24,?hobby:?['reading',?'swimming']}

我們可以看到,當對象的屬性是引用類型時,實質復制的是其引用,當引用值指向發生改變時也會跟著改變。

深拷貝

「實質」:深拷貝出來的對象會互不影響

「原理」:對對象中子對象進行遞歸拷貝

我們下面會手寫一個深拷貝哈~接著往下看,會有不一樣的收貨!

淺拷貝的實現方式

平常用到的淺拷貝有以下幾種(歡迎評論補充,互相分享進步)

  • Object.assign()
  • 擴展運算符(...)
  • Array.prototype.slice()

Object.assign()

首先 Object.assign(target, source)可以把n個源對象拷貝到目標對象中去(這不是本節重點討論的內容,先一筆帶過)

然后呢,Object.assign 是 ES6新增的對象方法,那么它到底是一個深拷貝還是一個淺拷貝的方法呢?

告訴你一個絕招吧(小點聲)!

「拷貝對象時,第一級屬性是深拷貝,以后級別淺拷貝」

舉個栗子你就知道了

let?lucy?=?{
????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming']
}
let?lilei?=?Object.assign({},?lucy)
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{name:?"lucy",?age:?23,?hobby:?['reading',?'swimming']}
console.log('lilei',?lilei)
//?lilei?{name:?"lilei",?age:?24,?hobby:?['reading',?'swimming']}

可以看出這個和咱們上面實現的那個淺拷貝的結果是一樣的。

還是那句話:「拷貝對象時,第一級屬性是深拷貝,以后級別淺拷貝」

是不是簡簡單單呢~

擴展運算符(...)

這個和 Object.assign 一樣,我們來看個栗子驗證一下

let?lucy?=?{
????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming']
}
let?lilei?=?{...lucy}
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{name:?"lucy",?age:?23,?hobby:?['reading',?'swimming']}
console.log('lilei',?lilei)
//?lilei?{name:?"lilei",?age:?24,?hobby:?['reading',?'swimming']}

哦~一毛一樣啊和上面。

Array.prototype.slice()

說到這個方法,我第一次看見的時候是在看 vue 源碼的時候,那個時候真是漲見識(姿勢)了

話不多說,看一下就知道

//?Dep?notify?方法
Dep.prototype.notify?=?function?notify()?{
????var?subs?=?this.subs.slice()
????//?...
}

利用了slice() 方法會返回一個新的數組對象,但也是一個淺拷貝的方法。

「拷貝對象時,第一級屬性是深拷貝,以后級別淺拷貝」

看一個具體的栗子

let?a1?=?[1,?2,?[3,?4]]
let?a2?=?a1.slice()

a2[1]?=?3
a2[2][0]?=?5
console.log('a1',?a1)?//?a1?(3)?[1,?2,?[5,?4]]
console.log('a2',?a2)?//?a2?(3)?[1,?3,?[5,?4]]

是不是驗證了這個道理呢~

同時也要去「注意」 concat這些會返回一個新的數組對象方法等,避免造成一些工作開發者不必要的困擾~

深拷貝的實現方式

深拷貝拷貝出來的對象互不影響,但深拷貝相比于淺拷貝速度會比較慢且開銷會較大,所以考慮清楚數據結構有幾層,不是很復雜的數據結構建議淺拷貝來節省性能。

看一種最簡單的深拷貝實現方式

JSON.parse(JSON.stringify())

**原理:**能將json的值json化

就是指純JSON數據,不包含循環引用,循環引用會報錯

拿之前的栗子改造一下看看有哪些需要注意的地方

let?lucy?=?{
????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming'],
????say:?function()?{
????????return?this.name
????},
????other:?undefined
}
let?lilei?=?JSON.parse(JSON.stringify(lucy))
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{
//????name:?'lucy',
//????age:?23,
//????hobby:?['running',?'swimming'],
//????say:?function()?{
//????????return?this.name
//?????},
//????other:?undefined
//???}
console.log('lilei',?lilei)
//?lilei?{age:?24,?hobby:?['reading',?swimming],?name:?'lilei'}

可以看出來這個方法還是挺強大的。

但是也能發現一些問題

  • 會忽略 undefined ?Symbol
  • 不能序列化函數
  • 不能解決循環引用的對象
  • 不能處理正則
  • 不能正確處理 new Date() (轉換成時間戳可以拷貝)

此外,深拷貝的其他方法還有 jQuery.extend()以及一些三方庫實現的深拷貝 lodash.cloneDeep()等等。大家感興趣可自行了解,繼續深造~

重頭戲,面試常考,手寫一個深拷貝,哈哈哈是不是就等這個呢~

我們改造一下上面的淺拷貝

遞歸實現深拷貝

//?判斷邊界,?null?這個特殊情況
let?isObject?=?obj?=>?typeof?obj?===?'object'?&&?obj?!==?null

//?遞歸實現深拷貝
let?deepClone?=?(obj)?=>?{
????//?先判斷是數組還是對象
????let?newObj?=?Array.isArray(obj)???[]?:?{}
????if?(isObject(obj))?{
????????for?(let?key?in?obj)?{
????????????if?(obj.hasOwnProperty(key))?{
????????????????if?(isObject(obj[key]))?{
????????????????????//?遞歸調用每一層
????????????????????newObj[key]?=?deepClone(obj[key])
????????????????}?else?{
????????????????????newObj[key]?=?obj[key]
????????????????}
????????????}
????????}
????}
????return?newObj
}

let?aa?=?{
????name:?'aa',
????car:?['寶馬',?'奔馳'],
????driver:?function?()?{?},
????age:?undefined
}
let?bb?=?deepClone(aa)?//?全部拷貝了一份

bb.name?=?'bb'
bb.age?=?20
bb.driver?=?'xxx'
console.log(bb)?
//?{?name:?'bb',?car:?[?'寶馬',?'奔馳'?],?driver:?'xxx',?age:?20?}
console.log(aa)
//?{?name:?'aa',?car:?[?'寶馬',?'奔馳'?],?driver:?function()?{},?age:?undefined?}

可以看出來,咱們這個遞歸實現的深拷貝,規避掉了 上面 JSON.parse(JSON.stringify())的一些弊端。但是還存在一些問題

  1. 循環檢測的問題
  2. 拷貝一個Symbol類型的值又該怎么解決?
  3. 如何解決遞歸爆棧的問題
哈希表

針對于循環檢測,我們可以使用哈希檢測的方法,比如設置一個數組或者是已經拷貝的對象,當檢測到對象已經存在哈希表時,就去除該值。

let?isObject?=?obj?=>?typeof?obj?===?'object'?&&?obj?!==?null;
let?deepClone?=?(source,?hash?=?new?WeakMap())?=>?{
????if?(!isObject(source))?return?source?//?非對象返回自身
????if?(hash.has(source))?return?hash.get(source)?//?新增檢測,?查哈希表
????let?target?=?Array.isArray(source)???[]?:?{}
????hash.set(source,?target)?//?設置哈希表值
????for?(let?key?in?source)?{
????????if?(Object.prototype.hasOwnProperty.call(source,?key))?{
????????????target[key]?=?isObject(source[key])???deepClone(source[key],?hash)?:?source[key];?//?傳入哈希表
????????}
????}
????return?target
}
let?obj?=?{
????a:?1,
????b:?{
????????c:?2,
????????d:?3
????}
}
obj.a?=?obj.b;
obj.b.c?=?obj.a;
let?clone_obj?=?deepClone(obj)
console.log(clone_obj)

上面實現有點難度,如果未能一下看透,不妨先跳過,完成之前的那個深拷貝就夠了,當然,我喜歡不懼困難的人~

剩下的兩個就交給喜歡深度思考的人來去頭腦風暴一下吧。

最后總結一下

和原數據是否指向同一個對象第一層數據為基本數據類型原數據中包含子對象
賦值改變會影響原數據改變會影響原數據
淺拷貝改變「不會」影響原數據改變會影響原數據
深拷貝改變「不會」影響原數據改變「不會」影響原數據

寫在最后

?

享受過程帶來的喜悅,學會去克服自己的缺點!

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/528895.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/528895.shtml
英文地址,請注明出處:http://en.pswp.cn/news/528895.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

php 失去 焦點 另一個表單獵取值,同一表單如何根據某一個文本框的值 改變另一個文本框的值...

我用的是失去焦點,然后后臺查詢到了數據,返回回來顯示不了!!$(document).ready(function(){$("#card").blur(function(){var card$("#card").val();if(card!""){$.get("{:U(guoye/yanzheng)}&…

實現文件中名詞的統計計數_通過勤哲EXCEL和Excel中的rank函數實現排名統計

如今,信息化已成為各行業企業轉型和發展的關鍵所在,信息化技術最明顯的特點是企業不同部門的人在信息技術的支撐下,可以利用豐富的資源與工具展開協作學習,在相對自由的模式和環境下,改變傳統設計相對局限、固定思維的…

php 選擇 出現列表,php – 如何從加權列表中選擇4個唯一項?

所以我有一個加權項目列表,我想從這個列表中選擇4個非重復項目.Item WeightApple 5Banana 7Cherry 12...Orange 8Pineapple 50最有效的方法是什么?我最初的嘗試是,如果一個已經被選中的項目出現的話,只需重新選擇隨后的選秀權……但是對于一個小名單,這可能會導致大…

安徽關節式焊接機器人_上下料機器人的重要性體現在哪里?它有哪些優勢?

上下料機器人是工業機器人家族中從事加工應用的重要成員之一,能滿足“快速、大批量加工節拍”、“節省人力成本”、“提高生產效率”等要求,成為越來越多中心企業的理想選擇。那么,上下料機器人有哪些特征和優勢?企業主應該如何選…

oracle查詢ora03114,求教:ora-03114錯誤從哪里排查?

ERP軟件偶爾會報 ora-03114錯誤 未連接到oracle我檢查數據庫的根超時有關的設置dba_profiles中的如下兩個參數,都是不限制connect_timeunlimitedidle_timeunlimitedsqlnet.net中,只設置了如下參數SQLNET.EXPIRE_TIME5 --這個用來清除僵死連接的監聽日志文…

python大學什么專業學校_好學校的差專業和一般大學的好專業,該怎么選?我來說真話……...

假如是985,那么只能調劑專業,那么好點211能選到不錯的專業。那該怎么選擇?其實不單單是985/211的問題,是所有考生的任何一個分數都面臨這個問題。比如清華北大被調劑,是去復交金融,還是去清華北大調劑去學環…

oracle查詢表nextval,Oracle Database 建立與查詢 Sequence

相關的 Oracle Database Sequence 資訊如下:建立 Sequence 語法create sequence ._sincrement by 1start with 1maxvalue 99999999999999999999minvalue 1nocycle;其中 :incerment: 每次增加的數值start : Sequence 開始數值maxvalue : Sequence 最大值nocycle …

vision軟件_Roboguide軟件:高速拾取仿真工作站相機與工具添加與配置

上一期為大家介紹了機器人高速拾取仿真工作站中工件、視覺校準板、輸送帶等外圍設備的添加與配置,本期再來為大家介紹一下相機、機器人工具的添加與配置。一、相機添加與配置1.左側瀏覽樹中右擊“Sensor Units”,在彈出的快捷菜單中依次點擊“Add Sensor…

oracle00333,Oracle數據庫REDO損壞ora-00333修復手札

1.事情起因接到電話,周日意外斷電,數據庫起不了,報REDO CRASH,ora-00333錯誤。相關環境如下:RAC oracle_11.2.0.3,無備份,開歸檔。2 處理一到現場,既然對方沒有備份,那就做一個冷備份…

oracle cannot allocate new log,Thread 1 cannot allocate new log

今天發現alter.log有以下信息:Thread 1 cannot allocate new log, sequence 6166Private strand flush not complete對于這個錯誤信息得解釋如下:當系統要重新利用某個日志文件的時候,系統需要將該日志文件所包括的buffer cache 中的dirty bl…

arduino tft 方向_ESP32在Arduino環境下玩轉 LVGL,ESP32移植LVGL詳細教程

微信關注 “DLGG創客DIY”設為“星標”,重磅干貨,第一時間送達。?轉載自慕容流年 https://me.csdn.net/qq_41868901?1. LVGL簡介LittlevGL是一個免費的開源圖形庫,提供了創建嵌入式GUI所需的一切,具有易于使用的圖形元素、漂亮的…

linux內核開文件系統,內核必須懂(二): Linux文件系統初探

目錄前言文件系統結構新建文件和inode文件創建過程inode解析打開文件參考最后前言這次來說文件系統. 文件系統是非常重要的, 提高磁盤使用率, 減小磁盤磨損等等都是文件系統要解決的問題. 市面上的文件系統也是數不勝數, 比較常用的像ext4, xfs以及ntfs等等, 國內的像鵝廠的tfs…

gps衛星位置計算程序matlab_科研項目 | BD/ GPS衛星導航仿真測試系統研究

一、項目背景在移動互聯和互聯網的時代,衛星導航定位已經成為生活中不可或缺的部分。目前,我國的交通、銀行、電力、采礦、測繪等部門以及大眾化市場都需要大量的定位、授時服務。許多企業也形成了覆蓋衛星導航相關材料及元器件、衛星導航芯片、衛星導航…

linux docker權限,linux - 如何解決ubuntu中的docker權限問題? - Ubuntu問答

問題描述我已經按照here的說明安裝了docker。我使用Ubuntu Trusty 14.04 (LTS) (64-bit)。安裝過程中的一切都很好。另外,命令$ sudo docker run -i -t ubuntu /bin/bash可以很好地完成(在打開的控制臺中鍵入”exit”之后。但是當我嘗試執行其他操作時,我…

python虛擬人臉生成_GitHub - 597111463/seeprettyface-generator-yellow: 這是一個用StyleGAN訓練出的黃種人人臉生成器...

黃種人人臉生成器注明:之前做的一些有意思的人臉生成器,現在全部開源分享出來。它的主要作用是可生成制作各類型的人臉素材,供我們任意使用且無須擔心人臉版權的問題。在定制人臉上,開源的全系列生成器包括:黃種人臉生…

Linux輸入密碼接口,Linux下搭建接口自動化測試平臺

前言我們今天來學習一下在Linux下如何搭建基于HttpRunner開發的接口自動化測試平臺吧!需要在Linux上提前準備的環境(下面是本人搭建時的環境):1,Python 3.6.8 (可參考隨筆:Linux學習6-安裝Python3.6)2,MySQL 5.7(可參考…

python構建字典實現英文大寫字母與ascii編碼的轉換_Python:將復雜的字符串字典從Unicode轉換為ASCII...

作為從JSON API調用解析的多級字典,我有很多輸入.字符串都是unicode,這意味著有很多ustuff like this.我正在使用jq來處理結果,需要將這些結果轉換為ASCII.我知道我可以編寫一個函數來像這樣轉換它:def convert(input):if isinstance(input, dict):ret {}for stuff in input:r…

linux下查看windows文件夾大小,如何從Windows命令行檢查文件夾的大小?

我意識到這個問題要求使用進行文件大小分析CMD line。但是,如果您愿意使用它,PowerQuery (Excel add-in, versions 2010)則可以創建一些非常引人注目的文件大小分析。下面的腳本可以粘貼到空白查詢中;您唯一需要做的就是添加一個名為“ param…

python多進程傳遞參數_急急急, Python 多進程,如何傳遞 epoll?

102019-06-16 15:39:41 08:00NoAnyLove 好的好的,我查了下,說 IPC 或向 worker 參數傳遞的東西必須要能 pickle,不然就報錯,那就是 select.epoll 是不支持的。error:TypeError: cant pickle select.epoll objects。代碼…

linux查看一小時之內的日志,linux – 在[timespan]內(例如最后一小時)查找日志文件中的條目...

我的服務器CPU使用率不高,我可以看到Apache正在使用太多的內存。我有一種感覺,我正在用一個IP進行DOS.-也許你可以幫我找到他?我已經使用以下行找到10個最“活躍”的IP:cat access.log | awk {print $1} |sort |uniq -c |sort -n …