成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript對(duì)象深拷貝/淺拷貝遇到的坑和解決方法

atinosun / 3192人閱讀

摘要:在以上討論和研究結(jié)束后,同學(xué)向我推薦了一個(gè)庫(kù),測(cè)試了一下該庫(kù)存在方法,實(shí)現(xiàn)深拷貝更為完整和精致,前文問(wèn)題均沒(méi)有在該方法內(nèi)被發(fā)現(xiàn),在這里提一波。

如果本文對(duì)您有任何幫助或者您有任何想要提出的意見(jiàn)或問(wèn)題,請(qǐng)?jiān)诒疚南路交貜?fù),誠(chéng)摯歡迎各位參與討論,望各位不吝指教。
原載自己的小博客 JavaScript對(duì)象拷貝遇到的坑和解決方法 | 手柄君的小閣,所以無(wú)恥地算原創(chuàng)吧

近期參與某集訓(xùn),JavaScript,遇到一對(duì)象拷貝問(wèn)題,得到需求:
給一個(gè)對(duì)象,請(qǐng)編寫(xiě)一個(gè)函數(shù),使其可以拷貝一個(gè)對(duì)象,返回這個(gè)拷貝得到的新對(duì)象:
舉例如下:

function clone(obj){
    //DO SOMETHING
    return newObject; //返回拷貝得到的新對(duì)象
}

首先想到解法如下:

> ES6解構(gòu)賦值(淺拷貝):
function clone(obj){
    return {...obj};
}

得到新對(duì)象為原始對(duì)象淺拷貝,即屬性Key一致,值如果是數(shù)或者字符串則值傳遞,否則為地址傳遞,即Value引用和源對(duì)象一致,可根據(jù)下方運(yùn)行測(cè)試:

var a = {a:1, b:2, c:3, d:[0, 1, 2]}
var b = clone(a);
console.log(b.d[1]); //1
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //2

對(duì)復(fù)制后的對(duì)象中包含的數(shù)組或者對(duì)象進(jìn)行編輯,影響了源對(duì)象,這顯然不是我們想要的結(jié)果,但是在對(duì)象內(nèi)不包含數(shù)組或?qū)ο髸r(shí),該方法不失為一個(gè)快速創(chuàng)建對(duì)象拷貝的實(shí)用方法。
在ES6中,Object提供了一個(gè) assign() 方法,也可以實(shí)現(xiàn)相同效果

> ES6 Object.assign()(淺拷貝):
function clone(obj){
    return Object.assign({},obj);
}

運(yùn)行效果和前一種方式基本一致,根據(jù)MDN描述,Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象,允許至少兩個(gè)參數(shù),第一個(gè)參數(shù)為拷貝的目標(biāo)對(duì)象,在方法執(zhí)行結(jié)束后會(huì)被返回,其余參數(shù)將作為拷貝來(lái)源。
前面兩種方法均為淺拷貝,那么對(duì)于對(duì)象內(nèi)包含對(duì)象或數(shù)組的對(duì)象,我們?cè)撛鯓涌截惸兀?br>我們的老師提供了一種方法如下,缺陷稍后再談

> For...in遍歷并遞歸(深拷貝):
function clone(obj) {
    var newobj = obj.constructor === Array ? [] : {};
    if (typeof obj !== "object") {
        return obj;
    } else {
        for (var i in obj) {
            newobj[i] = typeof obj[i] === "object" ? clone(obj[i]) : obj[i];
        }
    }
    return newobj;
}

同樣使用前文中的測(cè)試數(shù)據(jù):

var a = {a:1, b:2, c:3, d:[0, 1, 2]}
var b = clone(a);
console.log(b.d[1]); //1
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //1

可見(jiàn)該方法可以正確地對(duì)對(duì)象進(jìn)行深拷貝,并根據(jù)參數(shù)類型為數(shù)組或?qū)ο筮M(jìn)行進(jìn)行判斷并分別處理,但是該方法有一定缺陷:

1,在存在Symbol類型屬性key時(shí),無(wú)法正確拷貝,可以嘗試以下測(cè)試數(shù)據(jù):

var sym = Symbol();
var a = {a:1, b:2, c:3, d:[0, 1, 2], [sym]:"symValue"}
var b = clone(a);
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //1
console.log(a[sym]); //"symValue"
console.log(b[sym]); //undefined

可以發(fā)現(xiàn)拷貝得到的對(duì)象b,不存在Symbol類型對(duì)象為屬性名的屬性。
那么可以發(fā)現(xiàn),問(wèn)題主要出在For...in遍歷屬性無(wú)法獲得Symbol類型Key導(dǎo)致,那么有什么方法可以遍歷到這些呢?
在ES6中Reflect包含的靜態(tài)方法ownKeys() 可以獲取到這些key,根據(jù)MDN描述,這個(gè)方法獲取到的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

那么使用ES6解構(gòu)賦值和Reflect.ownKeys() 組合使用,改寫(xiě)上文函數(shù),得到:

> ES6解構(gòu)賦值 & Reflect.ownKeys() 遍歷并遞歸(深拷貝):
function clone(obj) {
    var newobj = obj.constructor === Array ? [...obj] : {...obj};
    if (typeof obj !== "object") {
        return obj;
    } else {
        Reflect.ownKeys(newobj).forEach(i => {
            newobj[i] = typeof obj[i] === "object" ? clone(obj[i]) : obj[i];
        });
    }
    return newobj;
}

運(yùn)行相同的測(cè)試語(yǔ)句:

var sym = Symbol();
var a = {a:1, b:2, c:3, d:[0, 1, 2], [sym]:"symValue"}
var b = clone(a);
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //1
console.log(a[sym]); //"symValue"
console.log(b[sym]); //"symValue"
b[sym] = "newValue";
console.log(a[sym]); //"symValue"
console.log(b[sym]); //"newValue"

可以發(fā)現(xiàn)Symbol類型的key也被正確拷貝并賦值了,但是該方法依然有一定問(wèn)題,如下:

2,在對(duì)象內(nèi)部存在環(huán)時(shí),堆棧溢出,嘗試運(yùn)行以下測(cè)試語(yǔ)句:

var a = { info: "a", arr: [0, 1, 2] };
var b = { data: a, info: "b", arr: [3, 4, 5] };
a.data = b;
var c = clone(a); //Error: Maximum call stack size exceeded. 報(bào)錯(cuò):堆棧溢出

解決這個(gè)的方法稍后再講,但目前來(lái)看已有的兩種深拷貝方法足夠平時(shí)使用,接下來(lái)正好提一下,ES5.1中包含的JSON對(duì)象,使用該對(duì)象亦可對(duì)對(duì)象進(jìn)行深拷貝,會(huì)遇到的問(wèn)題和第一種深拷貝方式一樣,無(wú)法記錄Symbol為屬性名的屬性,另外只能包含能用JSON字符串表示的數(shù)據(jù)類型,實(shí)現(xiàn)代碼如下:

> JSON對(duì)象轉(zhuǎn)義(深拷貝):
function clone(obj) {
    return JSON.parse(JSON.stringify(obj);
}

JSON.stringify() 首先將對(duì)象序列化為字符串,再由JSON.parse() 反序列化為對(duì)象,形成新的對(duì)象。
回到前面提到的問(wèn)題2,如果對(duì)象內(nèi)包含環(huán),怎么辦,我的實(shí)現(xiàn)思路為使用兩個(gè)對(duì)象作為類似HashMap,記錄源對(duì)象的結(jié)構(gòu),并在每層遍歷前檢查對(duì)象是否已經(jīng)被拷貝過(guò),如果是則重新指向到拷貝好的對(duì)象,防止無(wú)限遞歸。實(shí)現(xiàn)代碼如下(配有注釋):

> Map記錄并遞歸(深拷貝)

/**
 * 深拷貝(包括Symbol)
 * @param {Object} obj
 */
function clone(obj) {
    const map = {}; //空對(duì)象,記錄源對(duì)象
    const mapCopy = {}; //空對(duì)象,記錄拷貝對(duì)象
    /**
     * 在theThis對(duì)象中,查找e對(duì)象的key,如果找不到,返回false
     * @param {Object} e 要查找的對(duì)象
     * @param {Object} theThis 在該對(duì)象內(nèi)查找
     * @returns {symbol | boolean}
     */
    function indexOfFun(e, theThis) {
        let re = false;
        for (const key of Reflect.ownKeys(theThis)) {
            if (e === theThis[key]) {
                re = key;
                break;
            }
        }
        return re;
    }
    /**
     * 在Map對(duì)象中,查找e對(duì)象的key
     * @param {Object} e 
     */
    const indexOfMap = e => indexOfFun(e, map);
    /**
     * 在Map中記錄obj對(duì)象內(nèi)所有對(duì)象的地址
     * @param {Object} obj 要被記錄的對(duì)象
     */
    function bindMap(obj) {
        map[Symbol()] = obj;
        Reflect.ownKeys(obj).forEach(key => {
            //當(dāng)屬性類型為Object且還沒(méi)被記錄過(guò)
            if (typeof obj[key] === "object" && !indexOfMap(obj[key])) {
                bindMap(obj[key]); //記錄這個(gè)對(duì)象
            }
        });
    }
    bindMap(obj);
    /**
     * 拷貝對(duì)象
     * @param {Object} obj 要被拷貝的對(duì)象
     */
    function copyObj(obj) {
        let re;//用作返回
        if (Array.isArray(obj)) {
            re = [...obj]; //當(dāng)obj為數(shù)組
        } else {
            re = { ...obj }; //當(dāng)obj為對(duì)象
        }
        mapCopy[indexOfMap(obj)] = re; //記錄新對(duì)象的地址
        Reflect.ownKeys(re).forEach(key => { //遍歷新對(duì)象屬性
            if (typeof re[key] === "object") { //當(dāng)屬性類型為Object
                if (mapCopy[indexOfMap(re[key])]) { //當(dāng)屬性已經(jīng)被拷貝過(guò)
                    re[key] = mapCopy[indexOfMap(re[key])]; //修改屬性指向到先前拷貝好的對(duì)象
                } else {//當(dāng)屬性還沒(méi)有被拷貝
                    re[key] = copyObj(re[key]); //拷貝這個(gè)對(duì)象,并將屬性指向新對(duì)象
                }
            }
        });
        return re; //返回拷貝的新對(duì)象
    }
    return copyObj(obj); //執(zhí)行拷貝并返回
}

運(yùn)行前面的測(cè)試語(yǔ)句:

var a = { info: "a", arr: [0, 1, 2] };
var b = { data: a, info: "b", arr: [3, 4, 5] };
a.data = b;

var c = clone(a);
c.info = "c";
c.data.info = "d";
console.log(a.info); //"a"
console.log(a.data.info); //"b"
console.log(c.info); //"c"
console.log(c.data.info); //"d"

得到該函數(shù)可以正確地拷貝帶環(huán)對(duì)象。

在以上討論和研究結(jié)束后,同學(xué)向我推薦了一個(gè)庫(kù) lodash,測(cè)試了一下該庫(kù)存在 _.cloneDeep() 方法,實(shí)現(xiàn)深拷貝更為完整和精致,前文問(wèn)題均沒(méi)有在該方法內(nèi)被發(fā)現(xiàn),在這里提一波。

如果本文對(duì)您有任何幫助或者您有任何想要提出的意見(jiàn)或問(wèn)題,請(qǐng)?jiān)诒疚南路交貜?fù),誠(chéng)摯歡迎各位參與討論,望各位不吝指教。
本文原載于https://www.bysb.net/3113.html

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107470.html

相關(guān)文章

  • JavaScript拷貝拷貝

    摘要:引用數(shù)據(jù)類型是存放在堆內(nèi)存中的,變量實(shí)際上是一個(gè)存放在棧內(nèi)存的指針,這個(gè)指針指向堆內(nèi)存中的地址。棧和堆的區(qū)別其實(shí)淺拷貝和深拷貝的主要區(qū)別就是數(shù)據(jù)在內(nèi)存中的存儲(chǔ)類型不同。這里,對(duì)存在子對(duì)象的對(duì)象進(jìn)行拷貝的時(shí)候,就是深拷貝了。 數(shù)據(jù)類型 在開(kāi)始拷貝之前,我們從JavaScript的數(shù)據(jù)類型和內(nèi)存存放地址講起。數(shù)據(jù)類型分為基本數(shù)據(jù)類型 和引用數(shù)據(jù)類型 基本數(shù)據(jù)類型主要包括undefin...

    娣辯孩 評(píng)論0 收藏0
  • JavaScript拷貝實(shí)現(xiàn)的方法

    摘要:相信人很多學(xué)習(xí)的過(guò)程中都踩了深拷貝和淺拷貝的坑,深拷貝和淺拷貝的區(qū)別我就不再贅述了,今天我來(lái)寫(xiě)一下我自己實(shí)現(xiàn)深拷貝的各種方法。中的深拷貝也是用類似方法實(shí)現(xiàn)。 相信人很多學(xué)習(xí)js的過(guò)程中都踩了深拷貝和淺拷貝的坑,深拷貝和淺拷貝的區(qū)別我就不再贅述了,今天我來(lái)寫(xiě)一下我自己實(shí)現(xiàn)深拷貝的各種方法。 比較簡(jiǎn)單的拷貝方式可以借用瀏覽器的Json對(duì)象去實(shí)現(xiàn),先把對(duì)象轉(zhuǎn)化為json字符串,在解析回對(duì)...

    Vicky 評(píng)論0 收藏0
  • 記錄一下最近在學(xué)的拷貝

    摘要:兩者享有相同的引用。深拷貝這個(gè)問(wèn)題通??梢酝ㄟ^(guò)來(lái)解決。深淺拷貝也可以使用的方法,注意使用合并返回值 前言 最近寫(xiě)代碼經(jīng)常用到深淺拷貝,從一開(kāi)始的悶頭使用漸漸想要深究其理,這篇文章記錄一下我的認(rèn)為,有所不足,恭請(qǐng)指正 我們可以先看看一個(gè)常遇到的一個(gè)小問(wèn)題 let a = { age:1 } let b = a a.age = 2 console.log(b.age) //2 ...

    cpupro 評(píng)論0 收藏0
  • 「前端面試題系列9」拷貝拷貝的含義、區(qū)別及實(shí)現(xiàn)(文末有崗位內(nèi)推哦~)

    摘要:深拷貝與淺拷貝的出現(xiàn),就與這兩個(gè)數(shù)據(jù)類型有關(guān)。這時(shí),就需要用淺拷貝來(lái)實(shí)現(xiàn)了。數(shù)據(jù)一但過(guò)多,就會(huì)有遞歸爆棧的風(fēng)險(xiǎn)。這個(gè)方法是在解決遞歸爆棧問(wèn)題的基礎(chǔ)上,加以改進(jìn)解決循環(huán)引用的問(wèn)題。但如果你并不想保持引用,那就改用用于解決遞歸爆棧即可。 前言 這是前端面試題系列的第 9 篇,你可能錯(cuò)過(guò)了前面的篇章,可以在這里找到: 數(shù)組去重(10 種濃縮版) JavaScript 中的事件機(jī)制(從原生到...

    caige 評(píng)論0 收藏0
  • JavaScript中的拷貝拷貝

    摘要:所以,深拷貝是對(duì)對(duì)象以及對(duì)象的所有子對(duì)象進(jìn)行拷貝實(shí)現(xiàn)方式就是遞歸調(diào)用淺拷貝對(duì)于深拷貝的對(duì)象,改變?cè)磳?duì)象不會(huì)對(duì)得到的對(duì)象有影響。 為什么會(huì)有淺拷貝與深拷貝什么是淺拷貝與深拷貝如何實(shí)現(xiàn)淺拷貝與深拷貝好了,問(wèn)題出來(lái)了,那么下面就讓我們帶著這幾個(gè)問(wèn)題去探究一下吧! 如果文章中有出現(xiàn)紕漏、錯(cuò)誤之處,還請(qǐng)看到的小伙伴多多指教,先行謝過(guò) 以下↓ 數(shù)據(jù)類型在開(kāi)始了解 淺拷貝 與 深拷貝 之前,讓我們先...

    546669204 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<