1.浅复制VS深复制
本文中的复制也能够称为拷贝,在本文中认为复制和拷贝是相同的意思。另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,undefined,string,number和boolean),这些类型的值自己就存储在栈内存中(string类型的实际值仍是存储在堆内存中的,可是js把string当作基本类型来处理 ),不存在引用值的状况。jquery
浅复制和深复制均可以实如今已有对象的基础上再生一份的做用,可是对象的实例是存储在堆内存中而后经过一个引用值去操做对象,由此复制的时候就存在两种状况了:复制引用和复制实例,这也是浅复制和深复制的区别所在。正则表达式
浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操做会互相影响数组
深复制:深复制不是简单的复制引用,而是在堆中从新分配内存,而且把源对象实例的全部属性都进行新建复制,以保证深复制的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是彻底隔离的浏览器
由深复制的定义来看,深复制要求若是源对象存在对象属性,那么须要进行递归复制,从而保证复制的对象与源对象彻底隔离。然而还有一种能够说处在浅复制和深复制的粒度之间,也是jQuery的extend方法在deep参数为false时所谓的“浅复制”,这种复制只进行一个层级的复制:即若是源对象中存在对象属性,那么复制的对象上也会引用相同的对象。这不符合深复制的要求,但又比简单的复制引用的复制粒度有了加深。函数
2. 浅复制
本文认为浅复制就是简单的引用复制,这种状况较很简单,经过以下代码简单理解一下:oop
1this 2spa 3prototype 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; } |