JS之深拷貝探究

JS中的數(shù)據(jù)類型

基礎數(shù)據(jù)類型

number,string,boolean,undefined,null,symbol這種办绝,基本數(shù)據(jù)類型都是直接存儲在內存中的內存棧上的赃梧,數(shù)據(jù)本身的值就是存儲在椗宸空間里面

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

object。存儲在內存棧上的是指針,指向內存堆中的對象本身瓦糟。
基本數(shù)據(jù)類型和引用類型的區(qū)別主要在于基本數(shù)據(jù)類型是分配在棧上的,而引用類型是分配在堆上的

實現(xiàn)方法

序列反序列

JSON.parse(JSON.stringify())的原理就是將源數(shù)據(jù)序列化稱JSON字符串赴蝇,然后再解析JSON字符串菩浙,構造由字符串描述的JavaScript值或對象。當拷貝JSON字符串的時候會開辟一個新的內存地址,從而完全切斷和源數(shù)據(jù)的聯(lián)系劲蜻。

  • 優(yōu)勢:在開發(fā)中只考慮JSON對象(string, number, 對象, 數(shù)組, boolean或 null)的話用這個就可以了陆淀。

  • 存在的問題:

  1. undefined、Symbol和function字段會丟失;
  2. RegExp和Error會變成空對象{};
  3. Date對象會變成字符串;
  4. NaN先嬉、Infinite和-Infinite都會變轉成null;
  5. 如果是由構造函數(shù)實例化出來的函數(shù)轧苫,原型上的constructor也會消失
  6. 如果對包含循環(huán)引用的對象(對象之間相互引用位衩,形成無限循環(huán))執(zhí)行此方法秩仆,會拋出錯誤

lodash的cloneDeep

  • 優(yōu)勢:
  1. 可以拷貝循環(huán)引用(閉環(huán))的對象;
  2. 可以拷貝ES6 引入的大量新的標準對象
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
    arrayTag = '[object Array]',
    boolTag = '[object Boolean]',
    dateTag = '[object Date]',
    errorTag = '[object Error]',
    funcTag = '[object Function]',
    mapTag = '[object Map]',
    numberTag = '[object Number]',
    objectTag = '[object Object]',
    regexpTag = '[object RegExp]',
    setTag = '[object Set]',
    stringTag = '[object String]',
    weakMapTag = '[object WeakMap]';

var arrayBufferTag = '[object ArrayBuffer]',
    float32Tag = '[object Float32Array]',
    float64Tag = '[object Float64Array]',
    int8Tag = '[object Int8Array]',
    int16Tag = '[object Int16Array]',
    int32Tag = '[object Int32Array]',
    uint8Tag = '[object Uint8Array]',
    uint8ClampedTag = '[object Uint8ClampedArray]',
    uint16Tag = '[object Uint16Array]',
    uint32Tag = '[object Uint32Array]';
  • 不足:
  1. 不能拷貝自身不可枚舉類型的屬性(enumerable);
  2. 不能拷貝普通數(shù)組的屬性象浑,除了RegExp.exec()返回的數(shù)組(只能復制出input和index屬性威沫,groups也不能復制);
  3. 不能拷貝BOM對象以及DOM對象(用cloneDeepWith)

迭代遞歸

for...in循環(huán)是 遍歷對象的每一個可枚舉屬性,包括原型鏈上面的可枚舉屬性,

Object.keys()只是遍歷自身的可枚舉屬性,不可以遍歷原型鏈上的可枚舉屬性.

而Object.getOwnPropertyNames()則是遍歷自身所有屬性(不論是否是可枚舉的),不包括原型鏈上面的顶岸。

  • 拷貝原型:Object.create(proto, [propertiesObject])
  • 拷貝不可枚舉屬性:Object.getOwnPropertyDescriptors(obj)
  • 拷貝symbol屬性:Object.getOwnPropertySymbols(obj)
  • 拷貝循環(huán)引用屬性: 借助hash表
function isObject(obj) {
    if ((typeof (obj) == 'object' || typeof (obj) == 'function') && obj !== null) {
        return true;
    }
}

function deepClone(obj, hash = new WeakMap()) {
    if (!isObject(obj)) {
        return obj
    }
    let tempObj;
    switch (obj.constructor) {
        case RegExp:
            tempObj = new RegExp(obj);
            break;
        case Date:
            tempObj = new Date(obj);
            break;
        case Function:
        case Error:
            tempObj = obj;
            break;
        default:
            // 查詢哈希表展鸡,防止循環(huán)屬性(環(huán))
            if (hash.has(obj)) {
                return hash.get(obj);
            }
            // 初始化拷貝對象
            tempObj = Array.isArray(obj) ? [] : {};
            // 獲取源對象所有屬性描述符,獲取到的value屬于淺拷貝來的
            let allDesc = Object.getOwnPropertyDescriptors(obj);

            // 創(chuàng)建對象诵盼,拷貝原型蓄拣,拷貝不可枚舉屬性
            tempObj = Object.create(Object.getPrototypeOf(obj), allDesc);

            // 獲取原對象全部symbol屬性
            let symKeys = Object.getOwnPropertySymbols(obj);
            // 拷貝symbol屬性
            if (symKeys.length > 0) {
                symKeys.forEach(symKey => {
                    if (isObject(obj[symKey])) {
                        hash.set(obj, tempObj);
                        tempObj[symKey] = deepClone(obj[symKey], hash);
                    } else {
                        tempObj[symKey] = obj[symKey];
                    }
                })
            }

            // 拷貝可枚舉屬性
            Object.getOwnPropertyNames(obj).map(key => {
                if (isObject(obj[key])) {
                    hash.set(obj, tempObj);
                    tempObj[key] = deepClone(obj[key], hash);
                } else {
                    tempObj[key] = obj[key];
                }
            })
    }
    return tempObj;
}
let person2 = deepClone(person1);
console.log("person1:", person1);
console.log("person2:", person2);
function Person(name, age) {
    this.name = name;
}
let sym = Symbol('我是一個Symbol');
Person.prototype = {
    constructor: Person,
    shape: 'circle', //設置原型上可枚舉
    [sym]: 'symbol' //設置原型上的 Symol 類型鍵
}

//設置原型上不可枚舉
Object.defineProperty(Person.prototype, 'year', {
    value: 55,
    enumerable: false
});

var person1 = new Person("Lily");
// 設置自身子對象
person1.obj = {
    name: '我是一個對象',
    id: 1,
    object: {
        name: '子對象',
        id: '20181215',
        subObject: {
            name: '子對象的子對象',
            id: '18:37'
        }
    }
};
//設置自身不可枚舉
Object.defineProperty(person1, 'year2', {
    value: {
        name: 'year2',
        id: '2117'
    },
    enumerable: false,
    writable: true,
    configurable: true,
});

//設置自身 Symol 類型鍵
let sym2 = Symbol('我是一個Symbol');
let sym3 = Symbol('我是一個Symbol');
person1[sym2] = {
    name: 'symbol2',
    id: 'id123',
    [sym3]: '內嵌symbol'
};
// 設置自身循環(huán)引用
person1.loopObj = person1;

let person2 = deepClone(person1);
// 修改person1的symbole屬性
Object.getOwnPropertySymbols(person1).forEach(symKey => {
    person1[symKey] = '新的symbol值'
})

// ----------------------------------------------
// 修改person1的不可枚舉屬性
Object.defineProperty(person1, 'year2', {
    value: '新的不可枚舉值',
});
// 或者
// person1.year2 = '新的不可枚舉值'
console.log("person1.year2:", person1.year2);
// ----------------------------------------------

console.log("person1:", person1);
console.log("person2:", person2);
console.log("Person.prototype.isPrototypeOf(person2):", Person.prototype.isPrototypeOf(person2));
console.log("person1.year2===person2.year2:", person1.year2 === person2.year2)
console.log("person1[sym2]===person2[sym2]:", person1[sym2] === person2[sym2])
console.log("person1.obj.object.subObject===person2.obj.object.subObject:", person1.obj.object.subObject ===
    person2.obj.object.subObject)

效率比較:

var x = {};
for (var i = 0; i < 1000; i++) {
    x[i] = {};
    for (var j = 0; j < 1000; j++) {
        x[i][j] = Math.random();
    }
}

var start = Date.now();
var y = clone(x);
console.log(Date.now() - start);
深復制方法 JSON.parse _.cloneDeep deepClone
耗時 654 213 893

總結:總的來說沒有一個方法是放諸四海而皆準的拗小,建議使用 lodash的cloneDeep重罪,但是對于小程序來說還是大了點,所以我平常開發(fā)小程序就是用JSON.parse(JSON.stringify())哀九。

參考

  1. Javascript繼承機制的設計思想
  2. JavaScript之對象序列化詳解
  3. javaScript中淺拷貝和深拷貝的實現(xiàn)
  4. 深入剖析 JavaScript 的深復制
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末剿配,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阅束,更是在濱河造成了極大的恐慌呼胚,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件息裸,死亡現(xiàn)場離奇詭異蝇更,居然都是意外死亡,警方通過查閱死者的電腦和手機呼盆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門年扩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人访圃,你說我怎么就攤上這事厨幻。” “怎么了腿时?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵况脆,是天一觀的道長。 經常有香客問我批糟,道長格了,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任跃赚,我火速辦了婚禮笆搓,結果婚禮上性湿,老公的妹妹穿的比我還像新娘。我一直安慰自己满败,他們只是感情好肤频,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著算墨,像睡著了一般宵荒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上净嘀,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天报咳,我揣著相機與錄音,去河邊找鬼挖藏。 笑死暑刃,一個胖子當著我的面吹牛,可吹牛的內容都是我干的膜眠。 我是一名探鬼主播岩臣,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宵膨!你這毒婦竟也來了架谎?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤辟躏,失蹤者是張志新(化名)和其女友劉穎谷扣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捎琐,經...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡会涎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瑞凑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片在塔。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拨黔,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情绰沥,我是刑警寧澤篱蝇,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站徽曲,受9級特大地震影響零截,放射性物質發(fā)生泄漏。R本人自食惡果不足惜秃臣,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一涧衙、第九天 我趴在偏房一處隱蔽的房頂上張望哪工。 院中可真熱鬧,春花似錦弧哎、人聲如沸雁比。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽偎捎。三九已至,卻和暖如春序攘,著一層夾襖步出監(jiān)牢的瞬間茴她,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工程奠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丈牢,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓瞄沙,卻偏偏與公主長得像己沛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子帕识,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內容