START
- 番茄我又又又来写点啥啦。
- 最近敲代码,总是遇到需要修改数据,在前端展示。但是最后并不想修改原数据的情况。所以,需要涉及到数据的拷贝了,数据拷贝又分深拷贝,浅拷贝。所以写一篇文章记录一下。
- 本文仅个人的想法,能力有限,参考即可,后续对这个有更深入的理解了,再补充。
- 啰嗦一句,深拷贝浅拷贝 和 深克隆浅克隆,其实都差不多一个意思啦,看个人习惯如何称呼。
JS 的数据类型
在说深浅克隆之前,我想提一下JS的数据类型。
-
JS数据类型,可以大致分为两种:基本数据类型 和 引用数据类型。
-
基本数据类型:
- Undefined
- Null
- Boolean
- Number
- String
- 引用数据类型:
- object
- 当然 ES6 引入了一种新的数据类型 Symbol (这里暂时不讨论 Symbol)
为什么要这样区分数据类型呢?
- 因为基本数据类型数据,保存在栈内存
- 引用类型保存在堆内存中
那么为什么要分两种保存方式呢?
- 根本原因是在于保存在栈内存的必须是大小固定的数据,引用的类型的大小不固定,只能保存在堆内存中。但是我们可以将引用类型的地址存储在栈内存中。
-
文字不好理解的话,举个例子:
// 1.定义一个基本数据类型--数字类型的变量 var a = 1 // 定义一个引用类型的变量 var obj = { name: "lazy_tomato" }
-
上面代码执行后,在内存中其实就是这样的。
堆栈内存说明.png
知道js变量的保存方式有什么用呢?
- 看一看简单的题目就知道了。
题目一
var a = 1
var b = a
console.log('变量a为:', a, "变量b为:", b); //变量a为: 1 变量b为: 1
a = 88
console.log('变量a为:', a, "变量b为:", b); //变量a为: 88 变量b为: 1
题目二
var a = {
name: '张三',
age: 14
}
var b = a
console.log('变量a为:', a, "变量b为:", b);
//变量a为: { name: '张三', age: 14 } 变量b为: { name: '张三', age: 14 }
a.name = 'lazy_tomato'
console.log('变量a为:', a, "变量b为:", b);
//变量a为: { name: 'lazy_tomato', age: 14 } 变量b为: { name: 'lazy_tomato', age: 14 }
-
题目一中,为什么我修改a,不会修改b ?
-
题目二中,为什么我修改a,会同时修改b ?
正是因为题目二中,我们
var b = a
做的操作就是:声明一个变量b,b栈内存中存储的地址,和a一样,都指向
{
name: '张三',
age: 14
}所以
a.name = 'lazy_tomato'
执行后 a指向堆内存中为:{
name: 'lazy_tomato',
age: 14
}但是 b栈内存中存储的地址也指向这个对象,所以 修改a,会同时修改b
浅拷贝
-
浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
看不懂定义?那就简单点说: 就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
-
题目二,其实就是属于浅拷贝,修改了变量a,变量b也跟着改变。
浅拷贝实现方式
-
可以通过简单的赋值实现
类似上面的例子
-
利用 Object.assign()实现
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = { a: { a: "hello", b: 21 } };
var newObj = Object.assign({}, obj);
console.log("obj变量为:", obj, "newObj: ", newObj)
//obj变量为: { a: { a: 'hello', b: 21 } } newObj: { a: { a: 'hello', b: 21 } }
newObj.a.a = "lazy_tomato";
console.log("obj变量为:", obj, "newObj: ", newObj)
//obj变量为: { a: { a: 'lazy_tomato', b: 21 } } newObj: { a: { a: 'lazy_tomato', b: 21 } }
注意:当object只有一层的时候,是深拷贝,例如如下:
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
console.log(obj1, obj2);
//{ a: 10, b: 20, c: 30 } { a: 10, b: 20, c: 30 }
obj1.b = 100;
console.log(obj1, obj2);
//{ a: 10, b: 100, c: 30 } { a: 10, b: 20, c: 30 }
深拷贝
- 深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
深拷贝实现方式
1、对象只有一层的话可以使用上面的:Object.assign()函数
2、转成 JSON 再转回来
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1, obj2) //{ body: { a: 10 } } { body: { a: 10 } }
obj1.body.a = 20;
console.log(obj1, obj2) //{ body: { a: 20 } } { body: { a: 10 } }
缺点
- 拷贝对象的值,如果存在函数、undefined、symbol,在经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失;
- 没办法拷贝不可枚举的属性,也不像for in 那样可以拷贝原型上的数据;
- 拷贝Date引用类型会变成字符串
- 拷贝RegExp 、Error 引用类型会变成 空对象
- 对象中含有NaN,Infinity和-Infinity,则序列化的结果会变成null
3、递归拷贝
- 写个函数实现递归拷贝
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i];
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
参考的相关网站博客
END
- 写着写着,发现自己还有好多不懂的呀,o(╥﹏╥)o,挂出的相关博客都很棒,推荐看看。加油啦~~ 后续对js更加精通了,再来补充这篇文章。
- 个人记录,参考即可。
网友评论