- js的数据类型:
基本数据类型
和引用数据类型
。
基本数据类型:名值存储在栈内存中;
引用数据类型:名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。 - 目前基本数据类型有:
Boolean、Null、Undefined、Number、String、Symbol
,引用数据类型有:Object、Array、Function、RegExp、Date
等 - 深拷贝与浅拷贝的概念只存在于引用数据类型
浅拷贝就只是复制对象的引用(堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化,原对象也会发生变化。
只有深拷贝才是真正地对对象的拷贝。
二.Js自带的深拷贝方法
1、Array
- slice()、concat、Array.from()、... 操作符:只能实现一维数组的深拷贝
var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();
console.log(arr1); //[1, 2, [3, 4]]
console.log(arr2); //[1, 2, [3, 4]]
arr2[0] = 2
arr2[2][1] = 5;
console.log(arr1); //[1, 2, [3, 5]]
console.log(arr2); //[2, 2, [3, 5]]
2、Object
- Object.assign():只能实现一维对象的深拷贝
var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 2, y: 2}
var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 2, y: {m: 2}}
- JSON.parse(JSON.stringify(obj)):可实现多维对象的深拷贝,但会忽略
undefined、任意的函数、symbol 值
var obj1 = {
x: 1,
y: {
m: 1
},
a:undefined,
b:function(a,b){
return a+b
},
c:Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}, a: undefined, b: ƒ, c: Symbol(foo)}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}, a: undefined, b: ƒ, c: Symbol(foo)}
console.log(obj2) //{x: 2, y: {m: 2}}
注:进行JSON.stringify()
序列化的过程中,undefined、任意的函数以及 symbol 值
,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
由上面可知,JS 提供的自有方法并不能彻底解决Array、Object的深拷贝问题,因此我们应该自己实现。
深拷贝函数最终版(支持基本数据类型、原型链、RegExp、Date类型)
function deepClone(obj, parent = null){
let result; // 最后的返回结果
let _parent = parent; // 防止循环引用
while(_parent){
if(_parent.originalParent === obj){
return _parent.currentParent;
}
_parent = _parent.parent;
}
if(obj && typeof obj === "object"){ // 返回引用数据类型(null已被判断条件排除))
if(obj instanceof RegExp){ // RegExp类型
result = new RegExp(obj.source, obj.flags)
}else if(obj instanceof Date){ // Date类型
result = new Date(obj.getTime());
}else{
if(obj instanceof Array){ // Array类型
result = []
}else{ // Object类型,继承原型链
let proto = Object.getPrototypeOf(obj);
result = Object.create(proto);
}
for(let key in obj){ // Array类型 与 Object类型 的深拷贝
if(obj.hasOwnProperty(key)){
if(obj[key] && typeof obj[key] === "object"){
result[key] = deepClone(obj[key],{
originalParent: obj,
currentParent: result,
parent: parent
});
}else{
result[key] = obj[key];
}
}
}
}
}else{ // 返回基本数据类型与Function类型,因为Function不需要深拷贝
return obj
}
return result;
}
var arr=[1,2,3,4];
var arr1=deepClone(arr);
arr1[1]=999
console.log(arr)
console.log(arr1)
注:Function类型的深拷贝:
- bind():使用
fn.bind()
可将函数进行深拷贝,但因为this指针指向问题而不能使用; - eval(fn.toString()):只支持箭头函数,普通函数
function fn(){}
则不适用; - new Function(arg1,arg2,...,function_body):需将参数与函数体提取出来;
PS:一般也不需要深拷贝Function。