国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

atinosun / 3162人閱讀

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

如果本文對您有任何幫助或者您有任何想要提出的意見或問題,請在本文下方回復(fù),誠摯歡迎各位參與討論,望各位不吝指教。
原載自己的小博客 JavaScript對象拷貝遇到的坑和解決方法 | 手柄君的小閣,所以無恥地算原創(chuàng)吧

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

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

首先想到解法如下:

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

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

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

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

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

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

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

同樣使用前文中的測試數(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

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

1,在存在Symbol類型屬性key時,無法正確拷貝,可以嘗試以下測試數(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)拷貝得到的對象b,不存在Symbol類型對象為屬性名的屬性。
那么可以發(fā)現(xiàn),問題主要出在For...in遍歷屬性無法獲得Symbol類型Key導(dǎo)致,那么有什么方法可以遍歷到這些呢?
在ES6中Reflect包含的靜態(tài)方法ownKeys() 可以獲取到這些key,根據(jù)MDN描述,這個方法獲取到的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

那么使用ES6解構(gòu)賦值和Reflect.ownKeys() 組合使用,改寫上文函數(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)行相同的測試語句:

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也被正確拷貝并賦值了,但是該方法依然有一定問題,如下:

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

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. 報錯:堆棧溢出

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

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

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

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

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

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

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)對象。

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

如果本文對您有任何幫助或者您有任何想要提出的意見或問題,請在本文下方回復(fù),誠摯歡迎各位參與討論,望各位不吝指教。
本文原載于https://www.bysb.net/3113.html

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/107470.html

相關(guān)文章

  • JavaScript拷貝拷貝

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

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

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

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

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

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

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

    caige 評論0 收藏0
  • JavaScript中的拷貝拷貝

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

    546669204 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<