1. 相关知识点
1.1 基本类型与引用类型分类
基本类型:number,string,boolean,null,undefined,symbol及BigInt(任意精度整数)七类;
引用类型:对象、数组、函数等;
1.2 JS中变量的存储方式
栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的名值和引用类型的名(地址);
堆:动态分配的内存,大小不定,也不会自动释放,里面存放引用类型的值;
1.2.1 基本类型
let a = 1;
当你b = a复制时,栈内存会新开辟一个内存
当你此时修改a=2,对b并不会造成影响;
let a=1,b=a;
虽然b不受a影响,但这也算不上深拷贝。因为深拷贝本身只针对较为复杂的object类型数据。
1.2.2 引用数据类型
let a = [0,1,2,3,4];
当 b = a 进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值;
当a[0] = 1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,
这就是浅拷贝。
要是在堆内存中也开辟一个新的内存专门为b存放值(就像基本类型那样),就达到深拷贝的效果了。
2. 实现浅拷贝的方法
2.1 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: { c1: 3 } } var obj2 = simpleCopy(obj1); obj2.a = 3; obj2.c.c1 = 4; console.log(obj1.a,obj2.a); // 1 3 console.log(obj1.c.c1,obj2.c.c1); // 4 4
2.2 Object.assign方法
var obj1 = { a: 1, b: 2, c: { c1: 3 } } var obj2 = Object.assign({},obj1); obj2.a = 3; obj2.c.c1 = 4 console.log(obj1.a,obj2.a) // 1 3 console.log(obj1.c.c1,obj2.c.c1); // 4 4
2.3 直接用 = 赋值
let a = [0,1,2,3,4], b = a; console.log(a===b); // true a[0]=1; console.log(a,b); // [1,1,2,3,4] [1,1,2,3,4]
3. 实现深拷贝的方法
3.1 采用递归去拷贝所有层级属性
function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj === "object"){ for(key in obj){ if(obj.hasOwnProperty(key)){ // 判断ojb子元素是否为对象,如果是,递归复制 if(obj[key] && typeof obj[key] === "object"){ objClone[key] = deepClone(obj[key]); }else{ // 如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a = [1,2,3,4] let b = deepClone(a); a[0]=2; console.log(a,b); // [2,2,3,4] [1,2,3,4] let obj1 = { a: 1, b: 2, c: { c1: 3 } } let obj2 = deepClone(obj1) obj2.c.c1 = 4 console.log(obj1,obj2); // {a:1,b:2,c:{c1:3}} {a:1,b:2,c:{c1:4}}
3.2 通过JSON对象来实现深拷贝
注意: 无法实现对象中方法的深拷贝
function deepClone(obj) { var _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone; } let a = [1,2,3,4] let b = deepClone(a); a[0]=2; console.log(a,b); // [2,2,3,4] [1,2,3,4] let obj1 = { a: 1, b: 2, c: { c1: 3, c2:function(){ // 无法实现对对象中方法的深拷贝 console.log('深拷贝') } } } let obj2 = deepClone(obj1) obj2.c.c1 = 4 console.log(obj1,obj2); // {a:1,b:2,c:{c1:3,c2:f()}} {a:1,b:2,c:{c1:4}}
3.3 通过 jQuery 中的 extend 方法实现深拷贝
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,true为深拷贝,false为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上;
object1 objectN(可选), Object类型 第一个以及第N个被合并的对象;
var array1 = [1,2,3,4]; var newArray = $.extend(true,[],array1); // true为深拷贝,false为浅拷贝
3.4 lodash函数库 实现深拷贝
let result = _.cloneDeep(test)
3.5 Reflect法
function isObject(val) { return val != null && typeof val === 'object' && Array.isArray(val) === false; } function deepClone(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 } let a = [1,2,3,4] let b = deepClone(a); a[0]=2; console.log(a,b); // [2,2,3,4] [1,2,3,4] let obj1 = { a: 1, b: 2, c: { c1: 3, c2:function(){ console.log('深拷贝') } } } let obj2 = deepClone(obj1) obj2.c.c1 = 4 console.log(obj1,obj2); // {a:1,b:2,c:{c1:3,c2:f}} {a:1,b:2,c:{c1:4,c2:f}}
3.6 手动实现深拷贝
let obj1 = { a: 1, b: 2 } let obj2 = { a: obj1.a, b: obj1.b } obj2.a = 3; alert(obj1.a); // 1 alert(obj2.a); // 3
3.7 Object.assign 方法
注意:
如果对象的 value 是基本类型,可用 Object.assign 来实现深拷贝
// 对象的 value 是基本类型 var obj = { a: 1, b: 2, } var obj1 = Object.assign({}, obj); // obj赋值给一个空{} obj1.a = 3; console.log(obj.a) // 1
3.8 slice 实现对数组的深拷贝
注意:
当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝;
当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝;
// 当数组里面的值是基本数据类型,比如 String,Number,Boolean 时,属于深拷贝 // 当数组里面的值是引用数据类型,比如 Object,Array 时,属于浅拷贝 var arr1 = ["1","2","3"]; var arr2 = arr1.slice(0); arr2[1] = "9"; console.log(arr1); // ["1","2","3"] console.log(arr2); // ["1","9","3"]
3.9 concat 实现对数组的深拷贝
注意:
当数组里面的值是基本数据类型,比如 String,Number,Boolean 时,属于深拷贝;
当数组里面的值是引用数据类型,比如 Object,Array 时,属于浅拷贝;
// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝; // 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝; var arr1 = ["1","2","3"]; var arr2 = arr1.concat(); arr2[1] = "9"; console.log(arr1); // ["1","2","3"] console.log(arr2); // ["1","9","3"] var arr1 = [{a:1},{b:2},{c:3}]; var arr2 = arr1.concat(); arr2[0].a = "9"; console.log(arr1[0].a); // 9 console.log(arr2[0].a); // 9
3.10 通过 var newObj = Object.create(oldObj)
注意: 无法实现对对象中方法的深拷贝;
function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // 避免相互引用对象导致死循环,如 initalObj.a = initalObj 的情况 if(prop === obj) { continue; } if (typeof prop === 'object') { obj[i] = (prop.constructor === Array) ? [] : Object.create(prop); } else { obj[i] = prop; } } return obj; } let obj1 = { a: 1, b: 2, c: { c1: 3, c2:function(){ console.log('深拷贝') } } } let obj2 = deepClone(obj1) obj2.c.c1 = 4 console.log(obj1,obj2); // {a:1,b:2,c:{c1:3,c2:f}} {a:1,b:2,c:{c1:4}}
3.11 使用 扩展运算符 实现深拷贝
注意:
当value是基本数据类型,比如 String,Number,Boolean 时,属于深拷贝;
当value是引用类型的值,比如 Object,Array,属于浅拷贝;
let obj1 = { a: 1, b: 2, c: { c1: 3, c2:function(){ console.log('深拷贝') } } } let obj2 = {...obj1} // 此时拷贝了 {c1:3} 的引用地址 obj2.c.c1 = 4 // 改变对象里面的值 console.log(obj1,obj2); // {a:1,b:2,c:{c1:4,c2:f}} {a:1,b:2,c:{c1:4,c2:f}} let obj3 = {...obj1} obj3.c = {c1:5} // 改变引用的对象,实际改变了引用对象的地址 console.log(obj1,obj3); // {a:1,b:2,c:{c1:4,c2:f}} {a:1,b:2,c:{c1:5}}