手寫深拷貝

方案1.序列化反序列化

var a = {name: 'lifa',age:18}
var b = JSON.parse(JSON.stringify(a))

上面的代碼b就是a的深拷貝當(dāng)我們修改b里面的值的時候,a的值不會跟著變化窃蹋。

1.1.缺點(diǎn)

1). 不支持function
如果被深拷貝的對象里面有函數(shù)的話蛤育,我們深拷貝后的對象會直接將函數(shù)忽略

2). 不支持undefined
如果被深拷貝的對象里面有undefined有滑,我們使用JSON對其深拷貝后恐锦,深拷貝后的對象也會將undefined忽略

3). 不支持循環(huán)引用(不能把自己賦值給自己內(nèi)部的屬性)

報錯,JSON只支持豎狀的結(jié)構(gòu)不支持環(huán)狀結(jié)構(gòu)

4). 會把Date類型變成字符串

Date類型經(jīng)過JSON深拷貝后變成了ISO8601格式的字符串

5). 不支持正則表達(dá)式

正則表達(dá)式經(jīng)過JSON后會變成空對象

6). 不支持Symbol

方案2. 遞歸克隆

2.1 思路

  • 遞歸
    看節(jié)點(diǎn)的類型(7種)
    如果是基本類型直接拷貝
    如果是object就分情況討論(普通object筑公、數(shù)組array、函數(shù)function尊浪、日期Date)

2.2 步驟

創(chuàng)建目錄
引入chai和sinon
開始驅(qū)動測試開發(fā)
測試失敗=> 改代碼 => 測試成功 =>加測試 => 測試失敗

  • src/index.js
function deepCLone() {

}
module.exports =  deepCLone
  • test/index.js
const sinon = require('sinon')
const sinonChai = require('sinon-chai')
const deepCLone = require('../src/index')
chai.use(sinonChai)
const assert = chai.assert
const deepClone = require('../src/index')
describe('deepClone', () => {
    it('是一個函數(shù)', () => {
        assert.isFunction(deepCLone)
    })
})
  • package.json
{
  "name": "deep-clone",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "test": "mocha test/**/*.js"
  },
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^6.2.0",
    "sinon": "^7.4.1",
    "sinon-chai": "^3.3.0"
  }
}

運(yùn)行yarn test

2.2.1. 對基本類型的拷貝

對于基本類型直接傳什么就返回什么

function deepClone(source) {
  return source
}

單元測試

 it('可以復(fù)制基本類型', () => {
        const n = 123
        const n2 = deepClone(n)
        assert(n ===n2)
        const b = '123456'
        const b2 = deepClone(b)
        assert(b === b2)
        const c = undefined
        const c2 = deepClone(c)
        assert(c === c2)
        const d = null
        const d2 = deepClone(d)
        assert(d === d2)
        const e = true
        const e2 = deepClone(e)
        assert(e === e2)
        const f = Symbol()
        const f2 = deepClone(f)
        assert(f === f2)
    })
2.2.2. 對于普通對象的深拷貝

先判斷傳入的參數(shù)類型是不是對象匣屡,然后創(chuàng)建一個新的對象封救,遍歷入?yún)ο蟮拿恳豁梜ey,將每一項的值都進(jìn)行深拷貝然后把當(dāng)前項賦值給新的對象

if (source instanceof Object) {
        const deepObj = new Object()
        for (let key in source) {
            // 對對象里的每一項進(jìn)行深拷貝并把這一項賦值給新的對象
            deepObj[key] = deepClone(source[key])
        }
        return deepObj
    }

單元測試

it('可以復(fù)制普通對象', () => {
        const a = { name: '立發(fā)', child: { name: '小立發(fā)'}}
        const a2 = deepClone(a)
        assert(a !== a2)
        assert(a.name === a2.name)
        assert(a.child !== a2.child)
        assert(a.child.name === a2.child.name)
    })
2.2.3. 對于數(shù)組對象的深拷貝
  if (source instanceof Object) {
        if (source instanceof Array) {
          const deepObj = new Array()
          for (let key in source) {
            // 對對象里的每一項進(jìn)行深拷貝并把這一項賦值給新的對象
            deepObj[key] = deepClone(source[key])
          }
          return deepObj
        }
}

單元測試

it('可以復(fù)制數(shù)組對象', () => {
        const a = [[1, 2], [4, 5], [6, 7]]
        const a2 = deepClone(a)
        assert(a !== a2)
        assert(a[0] !== a2[0])
        assert(a[1] !== a2[1])
        assert(a[2] !== a2[2])
        assert.deepEqual(a, a2)
})
2.2.4. 對于函數(shù)對象的深拷貝

判斷一個函數(shù)是不是另一個函數(shù)的深拷貝捣作,首先函數(shù)也是一個對象所以它應(yīng)該滿足對象深拷貝的條件
1).深拷貝后的函數(shù)應(yīng)該不等于源函數(shù)
2).深拷貝后的函數(shù)應(yīng)該和源函數(shù)的基本類型的屬性相等
3).深拷貝后的函數(shù)應(yīng)該和源函數(shù)的引用類型的屬性不相等
其次針對于函數(shù)對象本身
4).深拷貝后的函數(shù)的執(zhí)行結(jié)果應(yīng)該和源函數(shù)的執(zhí)行結(jié)果相等
針對上面四點(diǎn)的單元測試如下:

it('可以復(fù)制函數(shù)', () => {
        const a = function(x, y) {
            return x + y
        }
        a.xxx = { yyy: { zzz: 1 } }
        const a2 = deepClone(a)
        assert(a !== a2) //上面的1)
        assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz) //2)
        assert(a.xxx.yyy !== a2.xxx.yyy) //3)
        assert(a.xxx !== a2.xxx) //3)
        assert(a(1,2) === a2(1,2)) //4)
    })
  • index.js
else if (source instanceof Function) {
     deepObj = function() {
         return source.apply(this, arguments)
      }
      for (let key in source) {
        // 對對象里的每一項進(jìn)行深拷貝并把這一項賦值給新的對象
         deepObj[key] = deepClone(source[key])
      }
      return deepObj
 } 

實(shí)際上就是聲明一個新的函數(shù)誉结,然后在這個新的函數(shù)里把你傳入的函數(shù)調(diào)用一遍(把當(dāng)前新的函數(shù)的this傳給傳入的函數(shù),然后把參數(shù)放進(jìn)去)然后再return它

2.2.5. 對于環(huán)的深拷貝

我們上面的代碼如果出現(xiàn)環(huán)引用(某個屬性的值等于本身的引用地址)的話會出現(xiàn)死循環(huán)現(xiàn)象券躁,所以我們需要先把我們每次的值存下來(以此來確定某個屬性的值有沒有被深拷貝過)搓彻,如果下次調(diào)用發(fā)現(xiàn)某個屬性的值已經(jīng)深拷貝過了就直接返回第一次深拷貝過的值,否則就繼續(xù)遞歸嘱朽。

上面圖中黑色的環(huán)路是我們傳入的需要深拷貝的源對象旭贬,紅色的環(huán)路是我們深拷貝后的。我們看到了一個1就把1直接存儲下來(不經(jīng)過深拷貝處理搪泳,如果是引用類型直接把引用地址存儲下來)稀轨,然后對1進(jìn)行深拷貝生成一個新的1',然后看到2把2直接存儲下來岸军,然后對2進(jìn)行深拷貝生成一個新的2'奋刽,然后走到我們的3后面我們應(yīng)該接的實(shí)際上是我們第一次拷貝好的1',所以就需要我們每次存儲的時候把我們一開始的原始值和深拷貝后的值都存儲下來艰赞,通過原始值來得到我們第一次深拷貝后的值

let cache = []
function deepClone(source) {
    if (source instanceof Object) {
        let cacheDist = findCache(source)
        if (cacheDist) {
            return cacheDist
        } else {
            let deepObj
            if (source instanceof Array) {
                deepObj = new Array()
            } else if (source instanceof Function) {
                deepObj = function() {
                    return source.apply(this, arguments)
                }
            } else {
                deepObj = new Object()
            }
            cache.push([source, deepObj])
            for (let key in source) {
                // 對對象里的每一項進(jìn)行深拷貝并把這一項賦值給新的對象
                deepObj[key] = deepClone(source[key])
            }
            return deepObj
        }
    }
    return source
}
function findCache(source) {
    for (let i = 0; i < cache.length; i++) {
        if (cache[i][0] === source) {
            return cache[i][1]
        }
    }
    return undefined
}

單元測試

it('環(huán)也能復(fù)制', () => {
        const a = { name: '立發(fā)'}
        a.self = a
        const a2 = deepClone(a)
        assert(a !== a2)
        assert(a.name === a2.name)
        assert(a.self !== a2.self)
})
2.2.6. 正則表達(dá)式的深拷貝

我們對正則表達(dá)式的聲明主要有兩部分佣谐,一部分是它的文本,一部分是它的標(biāo)志

const a = new RegExp('hi\\d+', 'gi')

上面的hi\d+就是它的文本方妖,gi就是標(biāo)志狭魂,所以我們要對它進(jìn)行深拷貝的話只需要拿到它的文本和標(biāo)志然后放到一個新的RegExp對象里即可,那么我們?nèi)绾文玫竭@兩部分的值那党觅,通過a.source就可以拿到它的文本值雌澄,a.flags就可以拿到標(biāo)志的值

if (source instanceof Object) {
  else if (source instanceof RegExp) {
        deepObj = new RegExp(source.source, source.flags)
   } 
}

單元測試

it('可以復(fù)制正則表達(dá)式', () => {
        const a = new RegExp("hi\\d+", 'gi')
        a.xxx = { yyy: { zzz: 1} }
        const a2 = deepClone(a)
        assert(a.source === a2.source)
        assert(a.flags === a2.flags)
        assert(a !== a2)
        assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
        assert(a.xxx.yyy !== a2.xxx.yyy)
        assert(a.xxx !== a2.xxx)
})
2.2.6. Date類型的深拷貝

我們只需要判斷如果是Date類型,就直接把當(dāng)前的Date放到一個新的Date對象里即可杯瞻,我們想要判斷兩個Date的值是否相等只需要通過getTime()方法就可以

if (source instanceof Object) {
  else if (source instanceof Date) {
        deepObj = new Date(source)
   } 
}

單元測試

it('可以復(fù)制日期', () => {
        const a = new Date()
        a.xxx = { yyy: { zzz: 1} }
        const a2 = deepClone(a)
       assert(a.getTime() === a2.getTime())
        assert(a !== a2)
        assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
        assert(a.xxx.yyy !== a2.xxx.yyy)
        assert(a.xxx !== a2.xxx)
    })
2.2.7. 自動跳過原型屬性

如果我們通過Object.create給對象添加一個屬性的時候镐牺,這個屬性會被添加到它的proto里也就是原型上

上圖中我們通過Object.create創(chuàng)建了一個a對象并添加了name屬性,但是a本身沒有name屬性魁莉,而是在proto里睬涧,對于這種在原型上的屬性我們不應(yīng)該去拷貝(原因:如果原型上的每一層都拷貝的話會造成內(nèi)存爆掉),那么我們怎么實(shí)現(xiàn)只拷貝本身的屬性哪旗唁?
辦法:在for in里加上限制條件

for (let key in source) {
    // 如果key是source對象自身的屬性才去進(jìn)行拷貝
     if (source.hasOwnProperty(key)) {
         // 對對象里的每一項進(jìn)行深拷貝并把這一項賦值給新的對象
         deepObj[key] = deepClone(source[key])
       }
}

單元測試

it('自行跳過原型屬性', () => {
        const a = Object.create( { name: 'lifa' })
        a.xxx = { yyy: { zzz: 1} }
        const a2 = deepClone(a)
        //a2上沒有name屬性
        assert.isFalse('name' in a2)
        assert(a !== a2)
        assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
        assert(a.xxx.yyy !== a2.xxx.yyy)
        assert(a.xxx !== a2.xxx)
    })

2.3.問題

上面的代碼的問題:cache每次復(fù)制完一個對象的時候都沒有被清空畦浓,所以下一次深拷貝的時候就會互相影響
解決方法:使用面向?qū)ο螅看螌?shí)例化的時候生成一個新的cache
完整代碼

class DeepCloner {
    constructor () {
        this.cache = []
    }
    clone(source) {
        if (source instanceof Object) {
            let cacheDist = this.findCache(source)
            if (cacheDist) {
                return cacheDist
            } else {
                let deepObj
                if (source instanceof Array) {
                    deepObj = new Array()
                } else if (source instanceof Function) {
                    deepObj = function() {
                        return source.apply(this, arguments)
                    }
                } else if (source instanceof RegExp) {
                    deepObj = new RegExp(source.source, source.flags)
                } else if (source instanceof Date) {
                    deepObj = new Date(source)
                } else {
                    deepObj = new Object()
                }
                this.cache.push([source, deepObj])
                for (let key in source) {
                    if (source.hasOwnProperty(key)) {
                        // 對對象里的每一項進(jìn)行深拷貝并把這一項賦值給新的對象
                        deepObj[key] = this.clone(source[key])
                    }
                }
                return deepObj
            }
        }
        return source
    }
    findCache(source) {
        for (let i = 0; i < this.cache.length; i++) {
            if (this.cache[i][0] === source) {
                return this.cache[i][1]
            }
        }
        return undefined
    }
}

module.exports =  DeepCloner
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逆皮,一起剝皮案震驚了整個濱河市宅粥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌电谣,老刑警劉巖秽梅,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抹蚀,死亡現(xiàn)場離奇詭異,居然都是意外死亡企垦,警方通過查閱死者的電腦和手機(jī)环壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钞诡,“玉大人郑现,你說我怎么就攤上這事∮担” “怎么了接箫?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朵诫。 經(jīng)常有香客問我辛友,道長,這世上最難降的妖魔是什么剪返? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任废累,我火速辦了婚禮,結(jié)果婚禮上脱盲,老公的妹妹穿的比我還像新娘邑滨。我一直安慰自己,他們只是感情好钱反,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布掖看。 她就那樣靜靜地躺著,像睡著了一般诈铛。 火紅的嫁衣襯著肌膚如雪乙各。 梳的紋絲不亂的頭發(fā)上墨礁,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天幢竹,我揣著相機(jī)與錄音,去河邊找鬼恩静。 笑死焕毫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驶乾。 我是一名探鬼主播邑飒,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼级乐!你這毒婦竟也來了疙咸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤风科,失蹤者是張志新(化名)和其女友劉穎撒轮,沒想到半個月后乞旦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡题山,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年兰粉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顶瞳。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡玖姑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出慨菱,到底是詐尸還是另有隱情焰络,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布符喝,位于F島的核電站舔琅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洲劣。R本人自食惡果不足惜备蚓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囱稽。 院中可真熱鬧郊尝,春花似錦、人聲如沸战惊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吞获。三九已至况凉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間各拷,已是汗流浹背刁绒。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烤黍,地道東北人知市。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像速蕊,于是被迫代替她去往敵國和親嫂丙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

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

  • 1. 簡述深拷貝 JavaScript 中有2種變量類型:值類型(基本類型)和引用類型规哲。深拷貝和淺拷貝都是針對引用...
    JaniceZD閱讀 1,778評論 0 1
  • 前置知識: ????說到深淺拷貝首先要了解的知識是數(shù)據(jù)類型跟啤,那么js中會有兩個數(shù)據(jù)類型分別是 基本類型 和 引用類...
    船長___閱讀 730評論 0 7
  • 1、javaScript的變量類型 (1)基本類型:5種基本數(shù)據(jù)類型Undefined、Null隅肥、Boolean关顷、...
    wengjq閱讀 826評論 0 15
  • 1、JS中的變量類型 1)基本類型Number武福、Boolean议双、String、undefined捉片、Null平痰。變量是...
    何大必閱讀 1,079評論 0 1
  • 什么是深拷貝,什么是淺拷貝 說到深淺拷貝伍纫,就不得不提到另外一個知識點(diǎn)宗雇,那就是引用類型和基本類型以及堆和棧的區(qū)別。再...
    jeff_nz閱讀 866評論 0 0