深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的,都是只拷贝对象自身的属性,不对原型属性进行拷贝。
深拷贝和浅拷贝的拷贝层级问题
1)浅拷贝是拷贝一层,深层的数据复制的是内存地址
2)深拷贝是每一层的数据都会拷贝出来,两个对象是完全独立的
赋值和浅拷贝的区别
1)赋值:两个变量保存的是同一个对象的内存地址,指向同一个存储空间,无论通过哪个变量对对象进行改变,通过另一个变量查找到的对象也是改变的
2)浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是内存地址(引用类型),拷贝的是内存地址
浅拷贝的实现
1)Object和Array都能使用的方式:for/in、展开语法
1.1)for/in方式
function shallowCloning(obj) {
// 只拷贝引用数据类型
if (typeof obj !== "object") return;
let newObj = null;
if(obj !== null){
//根据obj的类型判断是新建一个数组还是一个对象
newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj自身的属性才进行拷贝
for (const key in obj) {
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];
}
}
}
return newObj;
}
1.2)展开语法
function shallowCloning(obj) {
// 只拷贝引用数据类型
if (typeof obj !== "object") return;
let newObj = null;
if(obj !== null){
//根据obj的类型判断是展开一个数组还是展开一个对象
newObj = obj instanceof Array ? [...obj] : { ...obj };
}
return newObj;
}
2)Array可以使用的方式:slice方法、concat方法
2.1)slice方法
从数组中截取部分元素组合成新的数组(不会改变原数组),第一个参数:开始截取的位置,第二个参数:结束截取的位置(不包括结束位置),不传递第二个参数时,截取到数组的最后元素
let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.slice(1, 3)); // [1,2]
不设置参数时,会获取所有元素
let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.slice()); //[0, 1, 2, 3, 4, 5, 6]
2.2)concat方法
concat方法用于连接两个或多个数组,会将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不会改变,元素是值类型的是复制操作,如果是引用类型还是指向同一对象。
let array = ["hdcms", "houdunren"];
let hd = [1, 2];
let cms = [3, 4];
console.log(array.concat(hd, cms)); //["hdcms", "houdunren", 1, 2, 3, 4]
console.log(array);//["hdcms", "houdunren"]
3)Object可以使用的方式:Object.assign方法
Object.assign(target,…sources)
//target:目标对象
//source:任意多个源对象
//返回值:目标对象会被返回
该方法可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
let user = {
name: '后盾人'
};
let hd = {
stu: Object.assign({}, user)
};
hd.stu.name = 'hdcms';
console.log(user.name);//后盾人
Object.assign方法可以处理数组,但是会把数组视为对象。
下面代码中,Object.assign()把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。
console.log(Object.assign([1, 2, 3], [4, 5]));//[4,5,3]
深拷贝的实现
1)JSON.parse(JSON.stringify())
原理是:用JSON.stringify()方法将js对象序列化为一个JSON字符串,JSON.parse()方法可以将JSON字符串反序列化为一个js对象。
注意:可以实现数组或对象深拷贝,但是不能处理函数,因为JSON.stringify()方法不能接收函数。
1.1)JSON.stringify()
JSON.stringify()方法具体使用参见MDN网站
语法:
JSON.stringify(value,replacer,space)
参数说明:
value:必需,要转换的JavaScript值(通常为对象或数组,要求最好用双引号包裹属性)
replacer:可选。用于转换结果的函数(传入参数key和value)或者数组。
space:可选。文本添加缩进、空格和换行符。
返回值:包含JSON文本的字符串。
1.2)JSON.parse()
JSON.parse()方法具体使用参见MDN网站
语法:JSON.parse(text,reviver)
参数说明:
text:必需,一个有效的JSON字符串
reviver:可选。一个用以在返回之前对所得到的对象执行变换(操作)的函数,将为对象的每个成员调用此函数,并传入每个成员的键和值,使用返回值而不是原始值。
2)自己编写函数(递归)
function deepCloning(obj) {
if (typeof obj !== "object") return;
let newObj = null;
if (obj !== null) {
newObj = obj instanceof Array ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === "object" ? deepCloning(obj[key]) : obj[key];
}
}
}
return newObj;
}
3)函数库lodash
该函数库提供_.cloneDeep()方法来做深拷贝(这一点,本人还没有实践过,看别人的博客中写的,先总结下来)
最最最通用版(考虑了循环引用的问题)
function clone(parent) {
const allParents = [];
const allChildren = [];
function _clone(parent) {
if (parent == null) {
return null;
}
if (typeof parent !== "object") {//如果不是对象直接返回
return parent;
}
const child = parent instanceof Array ? [] : {};
const index = allParents.indexOf(parent);
if (index !== -1) {
return allChildren[index];
}
allParents.push(parent);
allChildren.push(child);
//child保存的是parent对象深拷贝出来的新对象的内存地址,
// 这个内存地址是一直不变的,变的是这块内存中保存的内容
for (const key in parent) {
if (parent.hasOwnProperty(key)) {
const value = parent[key];
child[key] = _clone(value);
}
}
return child;
}
return _clone(parent);
}
let obj = {
site: "baidu.com",
protocol: [{
p1: "http",
p2: "https"
}]
}
obj.self = obj;
let newobj = clone(obj);
console.log(obj);
console.log(newobj);