JavaScript学习总结之Object的深拷贝和浅拷贝

前言

最近在写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之间相同的属性是直接覆盖的,如果属性值为对象,是不会对对象之间的属性进行合并的

总结

本篇博客主要讲解了数据类型,浅拷贝的实现方式,深拷贝的实现方式,从数据类型的讲解中一步一步引入到关于浅拷贝和深拷贝的实现方式,在这里我们必须学会关于递归实现深拷贝的实现方式,这个有可能在面试的时候会实现手写代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容