一 首先了解JavaScript中的基本数据类型
基本数据类型:String,Number,Boolean,Null,Undefined
引用数据类型:Araay,Date,RegExp,Function
二 基本数据类型和引用数据类型的区别?
(1)它们保存的位置不同:基本数据保存在栈中,引用数据类型保存在堆内存中。JS对引用数据的操作其实操作对象的引用而不是实际的对象,也就是指向实际对象的内存地址,如果obj1拷贝了obj2,那么这两个对象指向了同一堆内存对象,具体就是吧obj1栈内存中的引用地址复制了一份给obj2,所以他们指向了同一个堆内存对象。
那为什么基本数据类型要存在栈内存中,而引用数据要存在堆内存中?
(1)堆比栈大,栈的查找速度比堆块。
(2)基本数据类型比较的稳定,相对的话占用的内存比较小
(3)引用数据类型一般都是动态的,而且可能是无限大,引用的值也经常改变,所以不能放在栈中,这样会降低查找的速度,因此放在变量栈中的值应该是指向该对象再堆内存中的地址,地址的大小的固定的,所以吧他存在栈中对变量的性能没有影响。
JS一般在访问存在堆内存的对象的时候是不能直接访问的,所以在访问对象的时候,要先获取改对象再堆内存中的地址,在根据改地址去访问该对象中的值。
(2)基本数据类型可以使用typeof可以返回基本数据类型,但是Null会返回object,所以Null表示一个空对象指针;引用数据类型使用typeof会返回object,所以引用数据类型要用instanceof来检测引用数据的类型。
(3)定义引用数据类型需要使用new操作符,后面在跟一个构造函数来创建,或者使用对象字面量表示法创建对象。
使用new操作符创建对象
var obj1 = new Object();obj1.a = 1;
使用对象字面量表示法创建对象
var obj1 = { a: 1, b: 2}
基本数据类型 name和value值都是存储在栈中
当b=a的时候
栈内存开辟了一个新内存出来,所以在修改a的值的时候不会影响到b的值
引用数据类型-name是存在栈中,value存在堆内存中,但是栈内存会提供一个引用地址指向该对象在堆内存中的值
当b=a拷贝时,其实复制的是a的引用地址,并不是堆内存中的值
当你修改a里面的值的时候,由于a与b指向的是同一个地址所以b也就受到了影响,这就是浅拷贝。
如果在堆内存中开辟了一个新的内存地址专门存在b的值的话,那就达到了深拷贝的效果了。
三 什么是深拷贝和浅拷贝
首先深拷贝和浅拷贝都只针对于引用类型的数据;浅拷贝只复制指向某个对象的指针,并不复制对象的本身,新原对象还是共享同一块内存,但是深拷贝会创造一个一模一样的新对象出来,新对象跟原对象不再共享同一块内存地址,修改新对象也不会影响到原对象。
区别:浅拷贝只复制对象的第一层属性,深拷贝可以对对象的属性进行递归复制。
四 实现深拷贝
1、json对象的parse和stringfy
function deepClone (obj) { let _obj = JSON.stringify(obj) let objClone = JSON.parse(_obj) return objClone} let a = [0,1,2,3,4]let b = deepClone(a) a[0] = 1 console.log(a) //[1,1,2,3,4]console.log(b) //[0,1,2,3,4]
2、递归复制所有层级属性
function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { if (obj1.hasOwnProperty(i)) { // 如果子属性为引用数据类型,递归复制 if (obj1[i] && typeof obj1[i] === "object") { obj2[i] = deepCopy(obj1[i]); } else { // 如果是基本数据类型,只是简单的复制 obj2[i] = obj1[i]; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 3 alert(obj2.c.d); // 4
缺陷:当遇到两个互相引用的对象,会出现死循环的情况,为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环;
function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { var prop = obj1[i]; // 避免相互引用造成死循环,如obj1.a=obj if (prop == obj1) { continue; } if (obj1.hasOwnProperty(i)) { // 如果子属性为引用数据类型,递归复制 if (prop && typeof prop === "object") { obj2[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, obj2[i]); // 递归调用 } else { // 如果是基本数据类型,只是简单的复制 obj2[i] = prop; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 3 alert(obj2.c.d); // 4
// Object.create实现深拷贝1,但也只能拷贝一层function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { var prop = obj1[i]; // 避免相互引用造成死循环,如obj1.a=obj if (prop == obj1) { continue; } if (obj1.hasOwnProperty(i)) { // 如果子属性为引用数据类型,递归复制 if (prop && typeof prop === "object") { obj2[i] = (prop.constructor === Array) ? [] : Object.create(prop); } else { // 如果是基本数据类型,只是简单的复制 obj2[i] = prop; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 3 alert(obj2.c.d); // 4
// Object实现拷贝2,浅拷贝var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = Object.create(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 4 alert(obj2.c.d); // 4
3、jquery的extends方法
$.extend([deep ], target, object1 [, objectN ])
deep表示是否深拷贝,为true为深拷贝;为false,为浅拷贝。
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。Object类型 第一个以及第N个被合并的对象。
let a = [0,1,[2,3],4]let b = $.extend(true, [], a)a[0] = 1a[2][0] = 1 // [1,1,[1,3],4]b // [0,1,[2,3],4]
4、lodash函数库实现深拷贝
let result = _.cloneDeep(test)
5、Reflect法
// 代理法function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一个对象!') } let isArray = Array.isArray(obj) let cloneObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(cloneObj).forEach(key => { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return cloneObj}
6、用slice实现对数组的深拷贝
// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝// 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝var arr1 = ["1","2","3"]; var arr2 = arr1.slice(0);arr2[1] = "9";console.log("数组的原始值:" + arr1 );console.log("数组的新值:" + arr2 );
7.用concat实现对数组的深拷贝
// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝var arr1 = ["1","2","3"];var arr2 = arr1.concat();arr2[1] = "9";console.log("数组的原始值:" + arr1 );console.log("数组的新值:" + arr2 );// 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝var arr1 = [{a:1},{b:2},{c:3}];var arr2 = arr1.concat();arr2[0].a = "9";console.log("数组的原始值:" + arr1[0].a ); // 数组的原始值:9console.log("数组的新值:" + arr2[0].a ); // 数组的新值:9
五 实现浅拷贝
for···in只循环第一层
// 只复制第一层的浅拷贝function simpleCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; for (let i in obj1) { obj2[i] = obj1[i]; } return obj2;}var obj1 = { a: 1, b: 2, c: { d: 3 }}var obj2 = simpleCopy(obj1);obj2.a = 3;obj2.c.d = 4;alert(obj1.a); // 1alert(obj2.a); // 3alert(obj1.c.d); // 4alert(obj2.c.d); // 4
Object.assign方法
var obj = { a: 1, b: 2}var obj1 = Object.assign(obj);obj1.a = 3;console.log(obj.a) // 3
直接用=赋值
let a=[0,1,2,3,4], b=a;console.log(a===b);a[0]=1;console.log(a,b);