2019独角兽企业重金招聘Python工程师标准>>>

1.浅复制VS深复制

本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思。另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,undefined,string,number和boolean),这些类型的值本身就存储在栈内存中(string类型的实际值还是存储在堆内存中的,但是js把string当做基本类型来处理 ),不存在引用值的情况。

浅复制和深复制都可以实现在已有对象的基础上再生一份的作用,但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此复制的时候就存在两种情况了:复制引用和复制实例,这也是浅复制和深复制的区别所在。

浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响

深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复制的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的

由深复制的定义来看,深复制要求如果源对象存在对象属性,那么需要进行递归复制,从而保证复制的对象与源对象完全隔离。然而还有一种可以说处在浅复制和深复制的粒度之间,也是jQuery的extend方法在deep参数为false时所谓的“浅复制”,这种复制只进行一个层级的复制:即如果源对象中存在对象属性,那么复制的对象上也会引用相同的对象。这不符合深复制的要求,但又比简单的复制引用的复制粒度有了加深。

2. 浅复制

本文认为浅复制就是简单的引用复制,这种情况较很简单,通过如下代码简单理解一下:

1

2

3

4

5

6

7

var src = {

       name:"src"

   }

   //复制一份src对象的应用

   var target = src;

   target.name = "target";

   console.log(src.name);   //输出target

target对象只是src对象的引用值的复制,因此target的改变也会影响src。

3. 深复制

深复制的情况比较复杂一些,我们先从一些比较简单的情况说起:

3.1 Array的slice和concat方法

Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用了,因此这两个方法并不是真正的深复制,通过以下代码进行理解:

1

2

3

4

5

6

7

var array = [1,2,3];

var array_shallow = array;

var array_concat = array.concat();

var array_slice = array.slice(0);

console.log(array === array_shallow);   //true

console.log(array === array_slice);     //false

console.log(array === array_concat);    //false

可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。

1

2

3

4

5

6

7

8

9

10

11

var array = [1, [1,2,3], {name:"array"}];

var array_concat = array.concat();

var array_slice = array.slice(0);

//改变array_concat中数组元素的值

array_concat[1][0] = 5;

console.log(array[1]);    //[5,2,3]

console.log(array_slice[1]);  //[5,2,3]

//改变array_slice中对象元素的值

array_slice[2].name = "array_slice";

console.log(array[2].name);   //array_slice

console.log(array_concat[2].name); //array_slice

通过代码的输出可以看出concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用

3.2 JSON对象的parse和stringify

JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

var source = {

    name:"source",

    child:{

        name:"child"

    }

}

var target = JSON.parse(JSON.stringify(source));

//改变target的name属性

target.name = "target";

console.log(source.name);   //source

console.log(target.name);   //target

//改变target的child

target.child.name = "target child";

console.log(source.child.name);  //child

console.log(target.child.name);  //target child

从代码的输出可以看出,复制后的target与source是完全隔离的,二者不会相互影响。

这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理

3.3 jQuery中的extend复制方法

jQuery中的extend方法可以用来扩展对象,这个方法可以传入一个参数:deep(true or false),表示是否执行深复制(如果是深复制则会执行递归复制),我们首先看一下jquery中的源码(1.9.1)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

jQuery.extend = jQuery.fn.extend = function() {

    var options, name, src, copy, copyIsArray, clone,

        target = arguments[0] || {},

        i = 1,

        length = arguments.length,

        deep = false;

    // Handle a deep copy situation

    if typeof target === "boolean" ) {

        deep = target;

        target = arguments[1] || {};

        // skip the boolean and the target

        i = 2;

    }

    // Handle case when target is a string or something (possible in deep copy)

    if typeof target !== "object" && !jQuery.isFunction(target) ) {

        target = {};

    }

    // extend jQuery itself if only one argument is passed

    if ( length === i ) {

        target = this;

        --i;

    }

    for ( ; i < length; i++ ) {

        // Only deal with non-null/undefined values

        if ( (options = arguments[ i ]) != null ) {

            // Extend the base object

            for ( name in options ) {

                src = target[ name ];

                copy = options[ name ];

                // Prevent never-ending loop

                if ( target === copy ) {

                    continue;

                }

                // Recurse if we're merging plain objects or arrays

                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {

                    if ( copyIsArray ) {

                        copyIsArray = false;

                        clone = src && jQuery.isArray(src) ? src : [];

                    else {

                        clone = src && jQuery.isPlainObject(src) ? src : {};

                    }

                    // Never move original objects, clone them

                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don't bring in undefined values

                else if ( copy !== undefined ) {

                    target[ name ] = copy;

                }

            }

        }

    }

    // Return the modified object

    return target;

};

这个方法是jQuery中重要的基础方法之一,可以用来扩展jQuery对象及其原型,也是我们编写jQuery插件的关键方法,事实上这个方法基本的思路就是如果碰到array或者object的属性,那么就执行递归复制,这也导致对于Date,Function等引用类型,jQuery的extend也无法支持。下面我们大致分析一下这个方法:

(1)第1-6行定义了一些局部变量,这些局部变量将在以后用到,这种将函数中可能用到的局部变量先统一定义好的方式也就是“单var”模式

(2)第9-13行用来修正deep参数,jQuery的这个方法是将deep作为第一个参数传递的,因此这里就判断了第一个参数是不是boolean类型,如果是,那么就调整target和i值,i值表示第一个source对象的索引

(3)第17-19行修正了target对象,如果target的typeof操作符返回的不是对象,也不是函数,那么说明target传入的是一个基本类型,因此需要修正为一个空的对象字面量{}

(4)第22-25行来处理只传入了一个参数的情况,这个方法在传入一个参数的情况下为扩展jQuery对象或者其原型对象

(5)从27行开始使用for in去遍历source对象列表,因为extend方法是可以传入多个source对象,取出每一个source对象,然后再嵌套一个for in循环,去遍历某个source对象的属性

(6)第32行分别取出了target的当前属性和source的当前属性,35-38行的主要作用在于防止深度遍历时的死循环。然而如果source对象本身存在循环引用的话,extend方法依然会报堆栈溢出的错误

(7)第41行的if用来处理深复制的情况,如果传入的deep参数为true,并且当前的source属性值是plainObject(使用对象字面量创建的对象或new Object()创建的对象)或数组,则需要进行递归深复制

(8)第42-48根据copy的类型是plainObject还是Array,对src进行处理:如果copy是数组,那么src如果不是数组,就改写为一个空数组;如果copy是chainObject,那么src如果不是chainObject,就改写为{}

(9)如果41行的if条件不成立,那么直接把target的src属性用copy覆盖

jQuery的extend方法使用基本的递归思路实现了深度复制,但是这个方法也无法处理source对象内部循环引用的问题,同时对于Date、Function等类型的值也没有实现真正的深度复制,但是这些类型的值在重新定义时一般都是直接覆盖,所以也不会对源对象造成影响,因此一定程度上也符合深复制的条件

3.4 自己实现一个copy方法

根据以上的思路,自己实现一个copy,可以传入deep参数表示是否执行深复制:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

//util作为判断变量具体类型的辅助模块

   var util = (function(){

       var class2type = {};

       ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date"].forEach(function(item){

           class2type["[object "+ item + "]"] = item.toLowerCase();

       })

       function isType(obj, type){

           return getType(obj) === type;

       }

       function getType(obj){

           return class2type[Object.prototype.toString.call(obj)] || "object";

       }

       return {

           isType:isType,

           getType:getType

       }

   })();

   function copy(obj,deep){

        //如果obj不是对象,那么直接返回值就可以了

       if(obj === null || typeof obj !== "object"){

           return obj;

       }

    //定义需要的局部变脸,根据obj的类型来调整target的类型

       var i, target = util.isType(obj,"array") ? [] : {},value,valueType;

       for(i in obj){

           value = obj[i];

           valueType = util.getType(value);

       //只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制

           if(deep && (valueType === "array" || valueType === "object")){

               target[i] = copy(value);

           }else{

               target[i] = value;

           }

       }

       return target;

   }

转载于:https://my.oschina.net/bothyan/blog/1926169

也来谈一谈js的浅复制和深复制相关推荐

  1. 深度解析javascript中的浅复制和深复制

    在谈javascript的浅复制和深复制之前,我们有必要在来讨论下js的数据类型.我们都知道有Number,Boolean,String,Null,Undefined,Object五种类型.而Obje ...

  2. Java中Cloneable接口的浅复制与深复制

    Java中的深拷贝(深复制)和浅拷贝(浅复制) 深拷贝(深复制)和浅拷贝(浅复制)是两个比较通用的概念,尤其在C++语言中,若不弄懂,则会在delete的时候出问题,但是我们在这幸好用的是Java.虽 ...

  3. java自我复制_原型模式--自我复制(结合Java浅复制与深复制)

    原型模式,字面上的理解,以原型为标杆的模式. 原型模式其实就是从一个对象再创建另外一个可定制对象,而且不需知道任何创建的细节. 我们可以用原型示例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. ...

  4. android 浅复制与深复制

    今天,简单讲讲  android 浅复制与深复制的内容. 一.前言 任何变成语言中,其实都有浅拷贝和深拷贝的概念,Java 中也不例外.在对一个现有的对象进行拷贝操作的时候,是有浅拷贝和深拷贝之分 ...

  5. JAVA中浅复制与深复制

    來源:http://coolmist.javaeye.com/blog/127455 1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引 ...

  6. python浅复制与深复制_Python中的浅复制与深复制

    python浅复制与深复制 In python, the assignment operator does not copy the objects, instead, they create bin ...

  7. 详解Python中的浅复制与深复制

    列表对象的copy()方法返回列表的浅复制.所谓浅复制,是指生产一个新的列表,并且把原列表中所有元素的引用都复制到新列表中.如果原列表中只包含整数.实数.复数等基本类型或元组.字符串这样的不可变类型, ...

  8. java引用复制_Java中引用的浅复制和深复制

    Java中除了基本类型int,char,double等的赋值是按照值传递之外,其余的类型和对象都是按照引用进行传递的. 下面来看一个关于引用的例子. package referenceCopy; // ...

  9. C++ 浅复制、深复制详解

    文章目录 浅复制 深复制 有关浅复制与深复制的定义为:对类进行复制的时候按位复制,即把一个对象各数据成员的值原样复制到目标对象中.当类中涉及到指针类型数据成员的时候,往往就会产生指针悬挂问题. 浅复制 ...

最新文章

  1. 欢乐拼图发红包微信小程序开发过程实录成品展示
  2. 【错误记录】Android Studio Logcat 报错 ( read: unexpected EOF! )
  3. javascript DOM 遍历
  4. 苹果笔记本电脑亮度无法调节_苹果更新笔记本加量还降价,教育优惠全面开启!...
  5. 怎么样获取浏览器“该页无法显示”这类的异常 - -
  6. NG-ZORRO 7.3.0 发布,Ant Design 的 Angular 实现
  7. Spring的junit4测试集成
  8. css3 HTML5 效果
  9. [论文评析] ArXiv,2021, Focal Self Attention技术分析
  10. 组策略 禁止用户策略下发到指定计算机,使用组策略禁止域中计算机安装特定设备...
  11. git里面的文件怎么删不掉_git如何删除已经提交的文件夹
  12. 2021蓝桥杯预选赛题解
  13. Oracle数据库迁移到AWS云的方案
  14. node mysql timeout_Error: Handshake inactivity timeout in Node.js MYSQL module
  15. 网页前端大作业主界面(Html+CSS+JS+Axios)
  16. 冒泡排序、插入排序、选择排序、希尔排序、堆排序、归并排序等常用排序算法的比较
  17. 【OpenGL ES】二维图形绘制
  18. Nginx配置静态访问txt文件(微信校验文件)
  19. 模仿新浪微博雷达搜索动画效果
  20. ImportError: libpython3.8.so.1.0: cannot open shared object file: No such file 【Docker 容器root用户方法总结】

热门文章

  1. 数据引用Data References
  2. 如何在套接字IO操作上设置超时机制
  3. MySQL主从复制中关于AUTO_INCREMENT的奇怪问题
  4. 如何实现复杂FPGA设计的时序收敛
  5. 和我一起打造个简单搜索之ElasticSearch入门
  6. 学习日记0904并发编程socketserver模块 进程理论
  7. Linux下的两个经典宏定义【转】
  8. 野哥点评了Facebook、Amazon、Google、微软和苹果
  9. 关于Parse库的配置问题
  10. python 对一个函数执行速度控制的演示