js 在原生中并不具备深拷贝的功能,甚至不具备拷贝功能,大多是使用其他函数模拟效果,所以这里进行深入探讨。
现状
js 中有
简单类型:1. 字符串 2. 数值 3. 布尔值 4. null 5. undefined
引用类型:对象(object)
同时,引用类型又分为几个特殊型:1. Object 2. Array 3. RegExp 4. Date 5. Function
以及单体内置对象:1. Global 2. Math
简单类型的拷贝很简单,因为数据储存在栈内存,拷贝实际就是内存的复制,并用新变量承载;
1 2 3 4 5 6 7 8 9 10 11
| var s1 = 'string'; var s2 = s1; s2; // 'string'
var n1 = 1; var n2 = n1; n2; // 1
var b1 = false; var b2 = b1; b2; // false;
|
引用类型则要复杂一些,因为 js 的内存分为 栈内存 和 堆内存 ,引用类型实际内容是储存在堆内存中,声明一个新变量使用等号仅仅是指向了同一片堆内存,所以会发生:
1 2 3 4 5 6
| var o1 = {a: 1} var o2 = o1; o1.a = 2; // 改变对象的值 // o2 数值也变了 o2; // {a: 2}
|
看上去,改变 o1 的值,o2 的值也跟着变,但实际上,o1 和 o2 都指向同一片堆内存,就和我叫我的弟弟 傻康,但我妈叫他 亲宝贝,实际两个名字代表的是同一个人。
那要如何按预想中复制变量呢?
深拷贝
事实上引用类型的拷贝有深浅两种,浅拷贝指的是只拷贝引用类型的第一层,也就是,如果引用类型的内部变量指向了另一个引用类型,那内部变量指向的引用没有被拷贝,就称为浅拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 数组 var arr1 = [1,2,{a: 1}]; var arr2 = arr1.slice(0, arr1.length); // 切片切整个,并原数组不变
// var arr2 = arr1.concat(); // 数组同样可以使用 concat 方法,数组也不变
// 数组浅拷贝另一种方法就是 es6 中的 扩展运算符(...) var arr2 = [...arr1];
arr2; // [1,2,{a: 1}] arr1[2].a = 2; arr1; // [1,2,{a: 2}] arr2; // [1,2,{a: 2}] // 数组内部引用类型跟着改了 ,所以是浅拷贝
|
使用 slice 方法切片切整个达到复制目的,并原数组不变;
数组同样可以使用 concat 方法,concat 方法连接接数组,不传参数时,就只返回自己,原数组也不变,达到复制目的
1 2 3 4 5 6
| // 对象 var obj1 = {a: 1, b: 2} var obj2 = Object.assign(obj1); obj2; // {a: 1, b: 2} obj1.a = 3; obj2; // {a: 3, b: 2}
|
显然对象使用 Object.assign 方法不能开辟新内存;
想要无脑浅拷贝,可以使用 Json 对象的转换方法
1 2 3 4 5 6
| var obj1 = {a: 1, b: 2} var obj2 = JSON.parse(JSON.stringify(obj1)) obj2; // {a: 1, b: 2} obj1.a = 3; obj1; // {a: 3, b: 2} obj2; // {a: 1, b: 2}
|
引用类型的深拷贝
那想要内部的引用类型也拷贝怎么办,递归遍历他
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
| var a = [1,2,{c: 1}]
var b = deepCopy(a);
console.log(a); // [1,2,{c: 1}] console.log(b); // [1,2,{c: 1}] a[2].c = 2; console.log(a); // [1,2,{c: 2}] console.log(b); // [1,2,{c: 1}]
function deepCopy (obj) { var newObj; if (typeof obj === 'object') { if (Object.prototype.toString.call(obj) === '[object Object]') { newObj = {} } else if (Object.prototype.toString.call(obj) === '[object Array]'){ newObj = [] } for(var i in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj === 'object') { newObj[i] = deepCopy(obj[i]); } else { newObj[i] = obj[i]; } } } } else { newObj = obj; } return newObj; }
|
显然就可以了。
另外在 jquery 中,框架已经帮我们实现了相关函数:extend
1 2 3 4 5
| var a = [1,2,{c: 1}] var b = $.extend(true, {}, a); // 第一参数就是是否开启深拷贝 a[2].c = 3; console.log(a); // [1,2,{c: 2}] console.log(b); // [1,2,{c: 1}]
|
dom 元素的拷贝
dom 元素的复制比想象中还要简单,早在 DOM 2.0 就加入了 cloneNode 方法,第一个参数设置为 true,就可以拷贝元素的子元素。
1 2 3 4 5
| var _body = document.body.cloneNode(); _body; // <body></body>
var _body = document.body.cloneNode(true); _body; // <body>...</body>
|
但并没有复制元素“已经绑定的事件”,万能的 jq 同样封装了 clone 方法,第一个参数传入 布尔值,就可以复制事件了。
1 2 3 4 5
| var _body = $(body).clone(); _body[0]; // _body; // <body>...</body>
var _body = $(body).clone(true); _body.onclick(); // 执行点击事件
|