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

前言

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

數據類型

在瞭解淺拷貝和深拷貝之前,我們先回顧一下javascript中的數據類型,因爲在講淺拷貝和深拷貝的時候就是就是對原始數據類型(基本數據類型)和對象數據類型(引用數據類型)的拷貝
在javascript中,我們將數據類型分爲兩種,原始數據類型(基本數據類型)和對象類型(引用數據類型)

基本數據類型

基本數據類型的值是按值訪問的,基本數據類型的值是不可變的
常見的基本數據類型:Number,String,Boolean,Undefined,Null

引用數據類型

引用類型的值是按引用訪問的,引用類型的值是動態可變的
常見的引用類型:Object,Function,Array
由於數據類型的訪問方式不同,它們的比較方式也是不一樣的,我們來看一下下面的示例

(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的值也會發生改變,這裏到底發生了什麼,請看圖解
image
當我們將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()方法實現深拷貝

<!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之間相同的屬性是直接覆蓋的,如果屬性值爲對象,是不會對對象之間的屬性進行合併的

總結

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

發佈了22 篇原創文章 · 獲贊 17 · 訪問量 1780
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章