javascript學習總結之對象的深拷貝和淺拷貝

前言

最近在寫ES6的文章的時候發現重複遇到關於javascript深拷貝和淺拷貝的問題,而後查找了一些資料,根據資料和本身的理解作了如下筆記,畢竟javascript關於深拷貝和淺拷貝的問題在一些面試的時候有些面試官可能會進行提問,一塊兒來看看吧!javascript

數據類型

在瞭解淺拷貝和深拷貝以前,咱們先回顧一下javascript中的數據類型,由於在講淺拷貝和深拷貝的時候就是就是對原始數據類型(基本數據類型)和對象數據類型(引用數據類型)的拷貝html

在javascript中,咱們將數據類型分爲兩種,原始數據類型(基本數據類型)和對象類型(引用數據類型)java

基本數據類型

基本數據類型的值是按值訪問的,基本數據類型的值是不可變的面試

常見的基本數據類型:Number,String,Boolean,Undefined,Nulljson

引用數據類型

引用類型的值是按引用訪問的,引用類型的值是動態可變的數據結構

常見的引用類型:Object,Function,Array函數

因爲數據類型的訪問方式不一樣,它們的比較方式也是不同的,咱們來看一下下面的示例post

(1)基本數據類型和引用數據類型的比較

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>深拷貝和淺拷貝入門</title>
    </head>
    <body>
        <script type="text/javascript">
            var a=100;
            var b=100;
            console.log(a===b);//true
            var c={a:1,b:2};
            var d={a:1,b:2};
            console.log(c===d);//false
        </script>
    </body>
</html>

總結學習

  • 基本數據類型的比較是值的比較,因此在示例中a===b爲true
  • 引用類型的比較是引用地址的比較,因此在示例c===d爲false,由於c和d的地址不一樣

鑑於綜上兩點咱們大概知道所謂的淺拷貝和深拷貝可能就是對於值的拷貝和引用的拷貝(基本數據類型都是對值的拷貝),在這裏主要講解關於引用類型的拷貝測試

淺拷貝

淺拷貝是對象共用一個內存地址,對象的變化相互影響。好比常見的賦值引用就是淺拷貝

(1)簡單對象的淺拷貝

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>對象的淺拷貝</title>
    </head>
    <body>
        <script type="text/javascript">
            var obj1={name:'cat'};
            var obj2=obj1;
            obj2.name='dog';
            console.log(obj1);//{name:'dog'}
            console.log(obj2);//{name:'dog'}
        </script>
    </body>
</html>

咱們發現當咱們改變obj2的值的時候obj1的值也會發生改變,這裏到底發生了什麼,請看圖解

當咱們將obj2的值賦值給obj1的時候,僅僅只是將obj2的地址給了obj1而不是obj1從新在內存中開闢空間,因此obj1的地址和obj2的地址指向相同,改變obj2的時候obj1也會發生改變。

(2)使用循環實現淺拷貝

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>使用循環實現淺拷貝</title>
    </head>
    <body>
        <script type="text/javascript">
            var person={
                name:'tt',
                age:18,
                friends:['aa','bb','cc']
            }
            function shallowCopy(source){
                if(!source||typeof source!=='object'){
                    throw new Error('error');
                }
                var targetObj=source.constructor===Array?[]:{};
                for(var keys in source){
                    if(source.hasOwnProperty(keys)){
                        targetObj[keys]=source[keys]
                    }
                }
                return targetObj;
            }
            var p1=shallowCopy(person);
            console.log(p1);//{name:'tt',age:18,friends:['aa','bb','cc']}
        </script>
    </body>
</html>

在上面的代碼中,咱們建立了shallowCopy函數,它接收一個參數也就是被拷貝的對象,步驟分別是

(1):首先建立了一個對象
(2):而後for...in循環傳進去的對象爲了不循環到原型上面會被遍歷到的屬性,使用 hasOwnProperty 限制循環只在對象自身,將被拷貝對象的每個屬性和值添加到建立的對象當中
(3):最後返回這個對象

那麼看到這裏,咱們發現p1拿到了和person同樣的對象,那麼p1=person又有什麼區別了,咱們看下下面的示例

(3)簡單對象的淺拷貝

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>簡單對象的淺拷貝</title>
    </head>
    <body>
        <script type="text/javascript">
            var person={
                name:'tt',
                age:18,
                friends:['oo','cc','yy']
            }
            function shallowCopy(source){
                if(!source||typeof source!=='object'){
                    throw new Error('error');
                }
                var targetObj=source.constructor===Array?[]:{};
                for(var keys in source){
                    if(source.hasOwnProperty(keys)){
                        targetObj[keys]=source[keys]
                    }
                }
                return targetObj;
            }
            var p1=shallowCopy(person);
            var p2=person;
            //這個時候咱們修改person的數據
            person.name='tadpole';
            person.age=19;
            person.friends.push('tt');
            console.log(p2.name);//tadpole
            console.log(p2.age);//19
            console.log(p2.friends);//['oo','cc','yy','tt']
            console.log(p1.name);//tt
            console.log(p1.age);//18
            console.log(p1.friends);//['oo','cc','yy','tt']
        </script>
    </body>
</html>

上面建立了一個新變量p2,將person的值賦值給p2,而後比較這兩個值

  和原數據是否指向同一對象 第一層數據爲基本數據類型 原數據中包含子對象
賦值 改變會使原數據一同改變 改變會使原數據一同改變
淺拷貝 改變不會使原數據一同改變 改變不會是原數據一同改變

 

深拷貝

深拷貝是將對象放到一個新的內存中,兩個對象的改變不會相互影響或者你能夠理解爲淺拷貝因爲只是複製一層對象的屬性,當遇到有子對象的狀況時,子對象就會互相影響。因此,深拷貝是對對象以及對象的全部子對象進行拷貝

(1)遞歸調用淺拷貝實現深拷貝

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>遞歸實現深拷貝</title>
    </head>
    <body>
        <script type="text/javascript">
            var obj1={
                name:'cat',
                show:function(){
                    console.log('名稱:'+this.name);
                }
            }
            var obj2=deepClone(obj1);
            obj2.name='pig';
            obj1.show();//cat
            obj2.show();//pig
            function deepClone(obj){
                var objClone=Array.isArray(obj)?[]:{};
                if(obj&&typeof obj==='object'){
                    for(key in obj){
                        if(obj.hasOwnProperty(key)){
                            //判斷obj子元素是否爲對象,若是是,遞歸複製
                            if(obj[key]&&typeof obj[key]==='object'){
                                objClone[key]=deepClone(obj[key])
                            }else{
                                //若是不是,簡單複製
                                objClone[key]=obj[key]
                            }    
                        }
                    }
                }
                return objClone;
            }
            
        </script>
    </body>
</html>

對於深拷貝的對象,改變源對象不會對獲得的對象有影響。只是在拷貝的過程當中源對象的方法丟失了,這是由於在序列化 JavaScript 對象時,全部函數和原型成員會被有意忽略

(2)利用 JSON 對象中的 parse 和 stringify實現深拷貝

JOSN 對象中的 stringify 能夠把一個 js 對象序列化爲一個 JSON 字符串,parse 能夠把 JSON 字符串反序列化爲一個 js 對象,經過這兩個方法,也能夠實現對象的深拷貝

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>利用 JSON 對象中的 parse 和 stringify</title>
    </head>
    <body>
        <script type="text/javascript">
            var obj1={
                name:'cat',
                show:function(){
                    console.log(this.name);
                }
            }
            var obj2=JSON.parse(JSON.stringify(obj1));
            obj2.name='dog';
            console.log(obj1.name);//cat
            console.log(obj2.name);//dog
            obj1.show();//cat
            obj2.show();//TypeError: obj2.show is not a function
        </script>
    </body>
</html>

注意:JSON.parse()和JSON.stringify()能正確處理的對象只有Number、String、Array等可以被json表示的數據結構,所以函數這種不能被json表示的類型將不能被正確處理,通過轉換以後,function丟失了,所以JSON.parse()和JSON.stringify()仍是須要謹慎使用

(3)使用Object.assgin()方法實現深拷貝

這種方法我在javascript學習總結之Object.assign()方法詳解有過講解,可是我在看資料的時候有發現了一點點的問題,因此在這裏補充一下

定義:Object.assign() 方法用於將全部可枚舉的屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>使用Object.assgin()方法</title>
    </head>
    <body>
        <script type="text/javascript">
            let srcObj = {'name': 'lilei', 'age': '20'};
            let copyObj2 = Object.assign({}, srcObj, {'age': '21'});
            copyObj2.age = '23';
            console.log(srcObj);//{name:'lilei',age:20}
            console.log(copyObj2);//{name:'lilei',age:23}
        </script>
    </body>
</html>

看起來好像是深拷貝了,那其實這裏let copyObj2 = Object.assign({}, srcObj, {'age': '21'}); 咱們把srcObj 給了一個新的空對象。一樣目標對象爲 {},咱們再來測試下

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>使用Object.assgin()方法</title>
    </head>
    <body>
        <script type="text/javascript">
            let srcObj = {'name': 'lilei', 'age': '20'};
            let copyObj2 = Object.assign({}, srcObj, {'age': '21'});
            copyObj2.age = '23';
            console.log(srcObj);//{name:'lilei',age:20}
            console.log(copyObj2);//{name:'lilei',age:23}
            srcObj = {'name': '', grade: {'chi': '50', 'eng': '50'} };
            copyObj2 = Object.assign({}, srcObj);
            copyObj2.name = '';
            copyObj2.grade.chi = '60';
            console.log(srcObj);//{name:'紅',grade:{chi:60,eng:50}}
        </script>
    </body>
</html>

從例子中能夠看出,改變複製對象的name 和 grade.chi ,源對象的name沒有變化,可是grade.chi卻被改變了。所以咱們能夠看出Object.assign()拷貝的只是屬性值,假如源對象的屬性值是一個指向對象的引用,它也只拷貝那個引用值。 
也就是說,對於Object.assign()而言, 若是對象的屬性值爲簡單類型(string, number),經過Object.assign({},srcObj);獲得的新對象爲‘深拷貝’;若是屬性值爲對象或其它引用類型,那對於這個對象而言實際上是淺拷貝的。這是Object.assign()特別值得注意的地方,補充一句,Object.assig({},src1,src2) 對於scr1和src2之間相同的屬性是直接覆蓋的,若是屬性值爲對象,是不會對對象之間的屬性進行合併的。

總結

本篇博客主要講解了數據類型,淺拷貝的實現方式,深拷貝的實現方式,從數據類型的講解中一步一步引入到關於淺拷貝和深拷貝的實現方式,在這裏咱們必須學會關於遞歸實現深拷貝的實現方式,這個有可能在面試的時候會實現手寫代碼。

相關文章
相關標籤/搜索