也来谈一谈js的浅复制和深复制

1.浅复制VS深复制

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

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

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

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

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

2. 浅复制

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

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

    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返回的不同的数组实例,这与直接的引用复制是不同的。

    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字符串,借助这两个方法,也可以实现对象的深复制。

    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)

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参数表示是否执行深复制:

 //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;
    }

  

  
时间: 2024-10-15 23:05:47

也来谈一谈js的浅复制和深复制的相关文章

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

原文:深度解析javascript中的浅复制和深复制 在谈javascript的浅复制和深复制之前,我们有必要在来讨论下js的数据类型.我们都知道有Number,Boolean,String,Null,Undefined,Object五种类型.而Object又包含Function,Array和Object自身.前面的五种类型叫做基本类型,而Object是引用类型.可能有人就要问,为什么要分基本类型和引用类型呢?后面你就会明白的. 我们首先来看看浅复制和深复制的简洁定义: 深复制:直接将数据复制给

原型模式——浅复制与深复制

原型模式涉及一个浅复制和深复制的概念.原型模式可以简单理解为“复制”,但这个复制不是代码的复制.对同一个类,我们可以实例化new三次来“复制”,但如果在初始化的时候构造函数的执行很长,多次实例化就显得效率很低效了.那我们能否只实例化一次,然后“复制”呢? Test test1 = new Test(); Test test2 = test1; Test test3 = test1; 这样写吗?注意这是引用的复制,这实际上还是只有test1一个实例,test2.test3只是复制了其引用而已,如果

python基础之浅复制与深复制

关于列表.字典.元组的浅复制和深复制,数字和字符串没有浅复制和深复制一说.下面我直接用代码来体现: 1 import copy 2 3 names = ['Cahill','Teenglan','Eric','Peggie','Aalto','Baal','Sadie', 4 'Gage','Hagan','Jack','Kaley','Mabel','Lacy','Nadine','Pace','Amy'] 5 6 #浅复制 7 names2 = names.copy() 8 names2[1

JAVA浅复制与深复制

1.浅复制与深复制概念 ⑴浅复制(浅克隆)     多个变量指向一个对象    被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵深复制(深克隆)     每个变量指向一个对象,同时对象内包含对象,能复制内部对象    被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量.那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象.换言之,深复制把要复

转载:python中的copy模块(浅复制和深复制)

主要是介绍python中的copy模块. copy模块包括创建复合对象(包括列表.元组.字典和用户定义对象的实例)的深浅复制的函数. ########copy(x)########创建新的复合对象并通过引用复制x的成员来创建x的浅复制.更加深层次说,它复制了对象,但对于对象中的元素,依然使用引用.对于内置类型,此函数并不经常使用.而是使用诸如list(x), dict(x), set(x)等调用方式来创建x的浅复制,要知道像这样直接使用类型名显然比使用copy()快很多.但是它们达到的效果是一样

c++中浅复制与深复制

在C++中经常会遇到有关类对象的浅复制与深复制的问题,也是容易出错的地方. 查找了相关资料,有关浅复制与深复制的定义为:对类进行复制的时候按位复制,即把一个对象各数据成员的值原样复制到目标对象中.当类中涉及到指针类型数据成员的时候,往往就会产生指针悬挂问题. class A{ public: int* a; }; A a1; A b1=a1; b1=a1执行的是浅复制,此时a1.a和b1.a指向的是同一个内存地址,如果在析构函数里面有对内存的释放.就会出现内存访问异常.因为一块内存空间会被释放两

java基础-浅复制与深复制的理解

浅复制与深复制在很多编程语言中都有出现,那么什么是浅复制,什么是深复制呢? 要区分浅复制与深复制,首先我们要明确什么是复制,怎样才算是复制.复制的例子在生活中也随处可见,如复印一份文档,复制一段文字等.我们可以发现,复制操作后可以得到两份相同的东西,即复制由一变为二了.下面来看一个例子: public class User{ private int age; public int getAge(){ return age; } } User user1 = new User(); User us

iOS 浅赋值、深复制、完全复制的知识点梳理验证(附加归档解档)

写于前: 在之前转载的一片文章中,文中对浅复制和深复制进行了详细的解读,同时还提到了深复制(one-level-deep copy).完全复制(true copy)的概念,并指出iOS开发中的深复制是单层深赋值,本文将对这几个概念进行验证梳理. (单层和完全概念区分:例如多层数组只实现一层内容拷贝,其他层为指针拷贝成为单层深复制:若多层内容都实现拷贝称为完全赋值) 程序中用到的几点概念补充 (1) 浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制. 深复制(

黑马程序员--浅析浅复制和深复制的本质

@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css); 实例浅析oc中的浅复制和深复制的本质 代码段1: #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSMutable