日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深拷贝的缺点_拷贝?还傻傻分不清深浅?

發(fā)布時間:2024/9/27 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深拷贝的缺点_拷贝?还傻傻分不清深浅? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

「引言」

?

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

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

?

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

?

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

?

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

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

  • 什么是深淺拷貝?他們與賦值有何區(qū)別?
  • 淺拷貝的實現(xiàn)方式有哪些?
  • 深拷貝的實現(xiàn)方式有哪些?

本章節(jié)直接從拷貝開始說起,對于基本數(shù)據(jù)類型,引用數(shù)據(jù)類型之前的區(qū)別,可以看看上面的思維導(dǎo)圖

引用數(shù)據(jù)類型拷貝

我們從以下三個方面來看看這塊的內(nèi)容

  • 賦值
  • 淺拷貝
  • 深拷貝

賦值

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

舉個栗子:

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 的數(shù)據(jù),lucy也會跟著變。這是初學(xué)者(筆者也曾這樣)經(jīng)常犯的一個錯,后來深刻理解了對象內(nèi)存的重要性!改掉了這個惡習(xí)~

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

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

來看一看具體是什么和相關(guān)區(qū)別吧。

「注意:」

  • 對于基本數(shù)據(jù)類型而言,并沒有深淺拷貝的區(qū)別
  • 深淺拷貝都是對于引用數(shù)據(jù)類型而言的
  • 如果我們要賦值對象的所有屬性都不是引用類型時,我們可以使用淺拷貝,遍歷并復(fù)制,最后返回一個對象
  • 「本質(zhì)&使用場景」:都是復(fù)雜對象,就是說對象的屬性還是對象

    淺拷貝

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

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

    來動手實現(xiàn)一個簡單的淺拷貝吧

    //?實現(xiàn)淺拷貝?for??in?
    let?shallowCopy?=?(obj)?=>?{
    ????let?rst?=?{}
    ????for?(let?key?in?obj)?{
    ????????//?只復(fù)制本身擁有的屬性(非繼承過來的屬性)
    ????????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']}

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

    深拷貝

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

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

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

    淺拷貝的實現(xiàn)方式

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

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

    Object.assign()

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

    然后呢,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']}

    可以看出這個和咱們上面實現(xiàn)的那個淺拷貝的結(jié)果是一樣的。

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

    是不是簡簡單單呢~

    擴展運算符(...)

    這個和 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() 方法會返回一個新的數(shù)組對象,但也是一個淺拷貝的方法。

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

    看一個具體的栗子

    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這些會返回一個新的數(shù)組對象方法等,避免造成一些工作開發(fā)者不必要的困擾~

    深拷貝的實現(xiàn)方式

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

    看一種最簡單的深拷貝實現(xiàn)方式

    JSON.parse(JSON.stringify())

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

    就是指純JSON數(shù)據(jù),不包含循環(huán)引用,循環(huán)引用會報錯

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

    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'}

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

    但是也能發(fā)現(xiàn)一些問題

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

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

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

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

    遞歸實現(xiàn)深拷貝

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

    //?遞歸實現(xiàn)深拷貝
    let?deepClone?=?(obj)?=>?{
    ????//?先判斷是數(shù)組還是對象
    ????let?newObj?=?Array.isArray(obj)???[]?:?{}
    ????if?(isObject(obj))?{
    ????????for?(let?key?in?obj)?{
    ????????????if?(obj.hasOwnProperty(key))?{
    ????????????????if?(isObject(obj[key]))?{
    ????????????????????//?遞歸調(diào)用每一層
    ????????????????????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?}

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

  • 循環(huán)檢測的問題
  • 拷貝一個Symbol類型的值又該怎么解決?
  • 如何解決遞歸爆棧的問題
  • 哈希表

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

    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)?//?設(shè)置哈希表值
    ????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)

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

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

    最后總結(jié)一下

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

    寫在最后

    ?

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

    ?

    總結(jié)

    以上是生活随笔為你收集整理的深拷贝的缺点_拷贝?还傻傻分不清深浅?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。