1、數(shù)據(jù)類型
說起拷貝那伐,就不得不提起?js?的數(shù)據(jù)類型了踏施,因?yàn)樯羁截惡蜏\拷貝的核心就在于不同的數(shù)據(jù)類型在內(nèi)存中存儲(chǔ)的地方不同石蔗。
JavaScript中存在基本類型和引用類型。其中基本類型數(shù)據(jù)保存在棧內(nèi)存中畅形,棧具有先進(jìn)后出的特點(diǎn)养距;引用類型數(shù)據(jù)保存在堆內(nèi)存中,引用數(shù)據(jù)類型的變量是存放在棧中的日熬,指向的是堆內(nèi)存中實(shí)際對(duì)象的引用棍厌。
首先我們要知道最新的?ECMAScript?標(biāo)準(zhǔn)定義了 8 種數(shù)據(jù)類型,其中 7 種是基本數(shù)據(jù)類型竖席,它們是String耘纱、Number、Boolean毕荐、Null束析、Undefined、Symbol憎亚、BigInt员寇。和對(duì)象類型Object第美。
基本類型:
1)字符串(String)什往,字符串是一串表示文本值的字符序列,例如:“不染-何程龍” 第献。
2)數(shù)字(Number),整數(shù)或浮點(diǎn)數(shù)衫樊,例如: 42 或者 3.14159利花。
3)布爾值(Boolean)臀栈,有2個(gè)值分別是:true 和 false挠乳。
4)null , 一個(gè)表明 null 值的特殊關(guān)鍵字黍析。 JavaScript 是大小寫敏感的阐枣,因此 null 與 Null奄抽、NULL或變體完全不同。
5)undefined 宪哩,和 null 一樣是一個(gè)特殊的關(guān)鍵字锁孟,undefined 表示變量未賦值時(shí)的屬性茁瘦。
6)代表(Symbol)( 在 ECMAScript 6 中新添加的類型).甜熔。一種實(shí)例是唯一且不可改變的數(shù)據(jù)類型腔稀。Symbol 函數(shù)棧不能用 new 命令焊虏。Symbol 值作為屬性名時(shí),該屬性是公有屬性不是私有屬性炼团,可以在類的外部訪問瘟芝。但是不會(huì)出現(xiàn)在 for...in 褥琐、for...of的循環(huán)中敌呈,也不會(huì)被 Object.keys() 贩汉、 Object.getOwnPropertyNames()返回匹舞。如果要讀取到一個(gè)對(duì)象的Symbol 屬性赐稽,可以通過 Object.getOwnPropertySymbols() 和Reflect.ownKeys() 取到浑侥。
Symbol.for() 類似單例模式寓落,首先會(huì)在全局搜索被登記的 Symbol 中是否有該字符串參數(shù)作為名稱的 Symbol 值伶选,如果有即返回該 Symbol 值,若沒有則新建并返回一個(gè)以該字符串參數(shù)為名稱的 Symbol 值构资,并登記在全局環(huán)境中供搜索吐绵。
Symbol.keyFor() 返回一個(gè)已登記的 Symbol 類型值的 key 己单,用來檢測(cè)該字符串參數(shù)作為名稱的 Symbol 值是否已被登記耙饰。
7)任意精度的整數(shù) (BigInt)榔幸,可以安全地存儲(chǔ)和操作大整數(shù)削咆,甚至可以超過數(shù)字的安全整數(shù)限制拨齐。
對(duì)象類型Object:
2、深拷貝與淺拷貝
淺拷貝是指創(chuàng)建新的數(shù)據(jù)掏导,將源對(duì)象的屬性拷貝一份趟咆。如果屬性是基本類型值纱,拷貝的是基本類型的值坯汤;如果為引用類型惰聂,拷貝的是內(nèi)存地址搓幌。修改對(duì)象屬性會(huì)影響原對(duì)象鼻种。
常用的淺拷貝方法:
1)展開運(yùn)算符 ...
2)Object.assign():用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象分配到目標(biāo)對(duì)象叉钥。它將返回目標(biāo)對(duì)象投队。
3)concat和slice數(shù)組方法
如果拷貝的對(duì)象中屬性有引用類型值的話息楔,淺拷貝就不能達(dá)到預(yù)期的完全復(fù)制隔離的效果了值依。
深拷貝是開辟了一個(gè)新的棧碟案,兩個(gè)對(duì)象屬性相同价说。將拷貝過程中遇到的引用類型都新開辟一塊地址拷貝對(duì)應(yīng)的數(shù)據(jù),對(duì)應(yīng)兩個(gè)不同的地址缤弦,避免子對(duì)象共享同一份內(nèi)存的問題了碍沐,修改一個(gè)不會(huì)影響另一個(gè)抢韭。
JSON.parse(JSON.stringify()) 將對(duì)象先轉(zhuǎn)成字符串刻恭,再通過JSON.parse將字符串轉(zhuǎn)成對(duì)象鳍贾,此時(shí)對(duì)象中每個(gè)層級(jí)的堆內(nèi)存都是新開辟的骑科。存在的問題:1)不能解決循環(huán)引用的問題咆爽;2)無法拷貝特殊對(duì)象置森,比如:RegExp、BigInt凫海、Date、Set漾稀、Map等崭捍。
3、手寫深拷貝
1)JSON.stringify
var copy_data = JSON.parse(JSON.stringify(origin_data))
實(shí)現(xiàn)一個(gè)功能類似JSON.parse(JSON.stringify())的簡單深拷貝,能對(duì)對(duì)象和數(shù)組進(jìn)行深拷貝:
不能解決循環(huán)引用的問題;無法拷貝特殊對(duì)象昼蛀,比如:RegExp叼旋、BigInt、Date详民、Set沈跨、Map等
2)遞歸方法
3)淺拷貝 + 遞歸
對(duì)于基本數(shù)據(jù)類型,我們直接拷貝即可;對(duì)于引用數(shù)據(jù)類型杀狡,則需要進(jìn)行遞歸拷貝。
我們使用拷貝對(duì)象的構(gòu)造方法創(chuàng)建對(duì)應(yīng)類型的數(shù)據(jù)恭陡。
首先使用Object.prototype.toString.call()來獲取對(duì)象的準(zhǔn)確類型休玩。
獲取到了具體的引用類型后永部,我們可以根據(jù)對(duì)應(yīng)的類型進(jìn)行初始化對(duì)象的操作。通過target.constructor拿到拷貝對(duì)象的構(gòu)造函數(shù)组橄,通過源對(duì)象的構(gòu)造函數(shù)生成的對(duì)象可以保留對(duì)象原型上的數(shù)據(jù),如果使用{}遵班,則原型上的數(shù)據(jù)會(huì)丟失。
1)Boolean愿阐、Number、String辛孵、Date、Error我們可以直接通過構(gòu)造函數(shù)和原始數(shù)據(jù)創(chuàng)建一個(gè)新的對(duì)象冶匹。
2)Object、Map飞蛹、Set我們直接執(zhí)行構(gòu)造函數(shù)返回初始值墓懂,遞歸處理后續(xù)屬性宛徊,因?yàn)樗鼈兊膶傩钥梢员4鎸?duì)象暖呕。
3)Array湾揽、Symbol、RegExp進(jìn)行特殊處理。
整體代碼框架:
首先我們對(duì)于參數(shù)進(jìn)行判斷其是否為對(duì)象類型,如果是普通類型,直接返回即可磁滚。
這里我們還增加了緩存機(jī)制,為了防止自身的遞歸調(diào)用,陷入死循環(huán)仪芒。我們使用了WeakSet進(jìn)行儲(chǔ)存据沈,因?yàn)槌蓡T都是弱引用猾警,可以被垃圾回收機(jī)制回收崔慧,不容易造成內(nèi)存泄漏;
使用了Object.prototype.toString.call()來獲取拷貝對(duì)象的準(zhǔn)確類型皇钞。根據(jù)不同的類型利用其的構(gòu)造方法創(chuàng)建對(duì)應(yīng)類型的數(shù)據(jù)惩坑。如果是map和set趾痘,通過獨(dú)有的set滥沫、add方法設(shè)置值世分,單獨(dú)處理。
?首先是創(chuàng)建拷貝對(duì)象,我們可以根據(jù)對(duì)應(yīng)的類型進(jìn)行初始化對(duì)象的操作荣恐。
1)通過target.constructor拿到拷貝對(duì)象的構(gòu)造函數(shù),通過源對(duì)象的構(gòu)造函數(shù)生成的對(duì)象可以保留對(duì)象原型上的數(shù)據(jù)
2)Boolean、Number夺溢、String嘉汰、Date持搜、Error我們可以直接通過構(gòu)造函數(shù)和原始數(shù)據(jù)創(chuàng)建一個(gè)新的對(duì)象。
3)Object抛猫、Map峰档、Set我們直接執(zhí)行構(gòu)造函數(shù)返回初始值毅待,遞歸處理后續(xù)屬性刹泄,因?yàn)樗鼈兊膶傩钥梢员4鎸?duì)象盅蝗。
4)Array逞敷、Symbol牛柒、RegExp進(jìn)行特殊處理椭更。
對(duì)Array缴川、Symbol、RegExp進(jìn)行特殊處理铭污。
淺拷貝: