JavaScript之深拷貝和淺拷貝

前言

工做中會常常遇到操做數組、對象的狀況,你確定會將原數組、對象進行‘備份’
當真正對其操做時發現備份的也發生改變,此時你一臉懵逼,到時是爲啥,不是已經備份了麼,怎麼備份的數組、對象也會發生變化。
若是你對拷貝原理理解的不透徹,此文或許能提供一點幫助。javascript

javascript數據類型

基本數據類型

stringnumbernullundefinedbooleansymbol(ES6新增) 變量值存放在棧內存中,可直接訪問和修改變量的值
基本數據類型不存在拷貝,比如如說你沒法修改數值1的值java

引用類型

Object Function RegExp Math Date 值爲對象,存放在堆內存中
在棧內存中變量保存的是一個指針,指向對應在堆內存中的地址。
當訪問引用類型的時候,要先從棧中取出該對象的地址指針,而後再從堆內存中取得所需的數據git

淺拷貝

爲何備份的數組對象也會發生變化,這裏就涉及到你用的‘備份’實際上是一種淺拷貝github

簡單的引用拷貝

var a = [1,2,3,4];
var b = a;
a[0] = 0;
console.log(a,b);複製代碼


能夠看到數組a直接賦值給bab引用的實際上是一個對象地址,只要地址值發生變化,ab棧內存指針指向的堆地址也會發生變化,這種引用拷貝只是新增了一個變量棧內存的指針,意義不大數組

數組的concat、slice,對象的assign拷貝

一樣的例子瀏覽器

var a = [1,2,3,4];
var b = a.concat();
a[0] = 0;
console.log(a,b);複製代碼


此時數組a[0]值變成0b數組依然保持不變,有同窗就問了,這不就是深拷貝嗎。bash

對,也不對, Array.prototype.sliceArray.prototype.concat 看似深拷貝,其實質上仍是淺拷貝函數

var a = [1,2,[3,4],{name:'ccy'}];
var b = a.concat();
a[3].name = 'hs';
console.log(a[3],b[3]);複製代碼

 

當數組a中包含對象時, Array.prototype.sliceArray.prototype.cancat 拷貝出來數組中的對象仍是共享同一內存地址,因此本質上歸屬淺拷貝
測試

Object.assign 原理也是同樣的(對於對象的屬性都爲基本類型能夠當成深拷貝)優化

var a = {age:18,name:'ccy',info:{address:'wuhan',interest:'playCards'}};
var b = Object.assign(a);
a.info.address = 'shenzhen';
console.log(a.info,b.info);複製代碼


那怎樣才能對對象進行深拷貝呢,請扶好眼鏡。

本身寫一個深拷貝函數

var clone = function(obj){
        var construct = Object.prototype.toString.call(obj).slice(8,-1);
        var res;
        if(construct === 'Array'){
            res = [];
        }else if(construct === 'Object'){
            res = {}
        }
        for(var item in obj){
            if(typeof obj[item] === 'object'){
                res[item] = clone(obj[item]);
            }else{
                res[item] = obj[item];
            }
        }
        return res;
    };複製代碼

乍一看好像能處理對象的屬性爲對象的問題,能夠循環遍歷直至屬性爲基本類型;

可是仔細想,若是遇到對象的屬性存在相互引用的話會出現死循環的狀況。能夠再加一次判斷,對象的屬性若是引用對象指針則跳出當前循環。

深拷貝

深拷貝是能夠完美的解決淺拷貝的弊端,從新開闢一塊地址,深拷貝出來的屬性的基本類型值都是相同的。

JSON內置對象深拷貝

JSON 對象是ES5中引入的新的類型(支持的瀏覽器爲IE8+),JSON對象parse方法能夠將JSON字符串反序列化成JS對象,stringify方法能夠將JS對象序列化成JSON字符串,藉助這兩個方法,也能夠實現對象的深拷貝

var a = {age:1,name:'ccy',info:{address:'wuhan',interest:'playCards'}};
var b = JSON.parse(JSON.stringify(a));
a.info.address = 'shenzhen';
console.log(a.info,b.info);複製代碼


 JSON 可處理通常的對象進行深拷貝,可是不能處理函數、正則等對象

自定義深拷貝函數

咱們能夠對自定義的拷貝函數再進行優化 

var clone = function(obj){
        function getType(obj){
            return Object.prototype.toString.call(obj).slice(8,-1);
        }
        function getReg(a){
            var c = a.lastIndexOf('/');
            var reg = a.substring(1,c);
            var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', '\w': '\\w', '\s': '\\s', '\d': '\\d'};
            for(var i in escMap){
                if(reg.indexOf(i)){
                    reg.replace(i,escMap[i]);
                }
            }
            var attr = a.substring(c+1);
            return new RegExp(reg, attr);
        }
        var construct = getType(obj);
        var res;
        if(construct === 'Array'){
            res = [];
        }else if(construct === 'Object'){
            res = {}
        }
        for(var item in obj){
            if(obj[item] === obj) continue;//存在引用則跳出當前循環
            if(getType(obj[item]) === 'Function'){
                res[item] = new Function("return "+obj[item].toString())();
            }else if(getType(obj[item]) === 'RegExp'){
                res[item] = getReg(obj[item].toString());
            }else if(getType(obj[item]) === 'Object'){
                res[item] = clone(obj[item]);
            }else{
                res[item] = obj[item];
            }
        }
        return res;
    };複製代碼


基本能夠實現函數、正則對象的深拷貝,在本地只作了簡單的測試,若是存在問題,請及時評論指出。

固然像函數庫 lodash_.cloneDeepJQuery$.extend都實現了深拷貝,有興趣的同窗可自行看下源碼。

參考資料

developer.mozilla.org/zh-CN/searc…

github.com/wengjq/Blog…

相關文章
相關標籤/搜索