对于引用数据类型,细分可以分为以下三个方面
- 赋值
- 浅拷贝
- 深拷贝
目录
赋值:
只是改变指针的指向,例如,引用数据类型的赋值是对象保存在栈种的赋值,这样的化两个变量就都指向同一个对象,因此彼此之间的操作互有影响。
举个例子:
var a = {};
var b = a;
a.name = "ls";
console.log(a.name); // "ls"
console.log(b.name); // "ls"
b.age = 22;
console.log(b.age); // 22
console.log(a.age); // 22
console.log(a==b) //true
这种情况会导致a 和 b指向相同的数据,对其中一个修改的话,会影响到另外一个。实际开发的时候,这会让人很难受。
怎样才能让他们会不影响呢? 一个简单的方法就是复制一份a 变量的数据,这就是拷贝。
所以根据拷贝的层次不同可以分成浅拷贝和深拷贝,浅拷贝只进行一层拷贝,深拷贝则进行无限层次的拷贝。
浅拷贝
先实现一个浅拷贝:
let shallowClone = source => {
let target = {}
for(let i in source){
if ( source.hasOwnProperty(i) ){ //obj.hasOwnProperty,返回值是一个布尔值,即是否是obj的属性(原型上的是false)
target[i] = source[i];
}
}
return target;
}
let demo ={
b:{
c:{}
}
}
let demo2 = shallowClone(demo);
let demo3 = demo;
console.log(demo3 === demo) //true
console.log(demo2.b.c === demo.b.c) //true
console.log(demo2.b === demo.b) //true
console.log(demo2 === demo) //false
demo3 = demo 这样赋值,是地址赋值,demo3 、demo指向同一个对象,这不是我么想要的,我们看一下shallowClone函数,只是个浅拷贝的实现方式,那么demo2变量应该是实现了一个副本,demo2变量是在堆中开了一个新的内存,所以指向其他对象,demo2 === demo.b 为 true说明这就是浅拷贝的效果,简单拷贝了一层。
Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象。
举个例子:
let demo = {
name:"lsbook",
book:{
title:"Do you know JS",
price:"45"
}
}
let clone_demo = Object.assign({},demo);
console.log(clone_demo);
demo.name = "new name"
demo.book.price = "100"
console.log(clone_demo.name,clone_demo.book.price) // lsbook 100
修改上面的代码demo变量之后,对象clone_demo基本属性没有改变,但是修改demo对象中书引用属性时,对象clone_demo相应位置属性值也发生改变
展开运算符...
与Object.assign()效果相同
let demo = {
name:"lsbook",
book:{
title:"Do you know JS",
price:"45"
}
}
let clone_demo = {...demo}
console.log(clone_demo);
demo.name = "new name"
demo.book.price = "100"
console.log(clone_demo.name,clone_demo.book.price) // lsbook 100
Array.prototype.slice()
slice() 方法返回一个新的数组对象,该对象由begin和end决定的原先的浅拷贝。原数组不会被改变。
let a = [0,"1",[2,3]];
let b =a.slice(1);
console.log(b) //["1",[2,3]]
a[1] = "4";
a[2][0] = 5;
console.log(a); // [0,"4", [5,3]]
console.log(b); // ["1",[2,3]]
深拷贝
当对象和它引用的对象一起复制时即发生深拷贝。拷贝前后两个对象互不影响。
JSON.parse(JSON.stringify (obj))
let dome = {
name:"lsbook"
book:{
title:"Do you know JS",
price:45
}
}
let clone_demo = JSON.parse(JSON.stringify(demo));
console.log(clone_demo);
demo.name = "new name"
demo.book.price = 100
console.log(clone_demo.name,clone_demo.book.price); // ls 45
在demo改变之后对clone_demo完全没有影响,这就是深拷贝。
值得注意的是:
- 会忽略undefined Symbol
- 不能序列化函数
- 不能解决循环引用的对象
- 不能正确处理new Date()
- 不能处理正则
对于undefined symbol 函数 三种情况会直接忽略
let demo = {
name : "ls",
h1 : undefined,
h2 : Symbol("ls"),
h3 : function () {},
}
let clone_demo = JSON.parse(JSON,stringify(demo));
console.dir(clone_demo) // {name : "ls"}
循环引用情况下会报错
let obj = {
a : 1,
b : {
c : 2,
d : 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let obj2 = JSON.parse(JSON.stringify(obj)); // Uncaught TypeError: Converting circular structure to JSON
new Date 情况下,转换结果不正确
new Date();
// Wed Jul 01 2020 16:19:07 GMT+0800 (中国标准时间) {}
JSON.stringify(new Date())
// ""2020-07-01T08:19:19.860Z""
JSON.parse(JSON.stringify(new Date()))
// "2020-07-01T08:19:35.569Z"
解决方法 将时间转换成字符串
let date = (new Date()).valueOf();
// 1593591638596
JSON.stringify(date);
// "1593591638596"
JSON.parse(JSON.stringify(date));
// 1593591638596
正则情况下
let demo = {
name:"ls",
a: /'123'/
}
console.log(demo) // {name: "ls", a: /'123'/}
let clone_demo = JSON.parse(JSON.stringify(obj));
console.log(clone_demo);
// {name: "ls", a: {}}
实现一个简单的深拷贝:
浅拷贝+递归的方法。浅拷贝的时候判断是不是对象,是对象就递归。
let shallowClone = source =>{
let target = {};
for (let key in source){
if(Object.prototype.hasOwnProperty.call(source, key)){
target[key] = typeof source[key] === 'object'? shallowClone(source[key]) : source[key];
}
}
return target
}
let demo = {
name : "ls",
book : {
title : 'Do you know JS',
price : "45"
}
}
let clone_demo = shallowClone(demo);
console.log(clone_demo);
demo.name = "qqq"
demo.book.price = "50"
console.log(clone_demo.name,clone_demo.book.price) // ls 45
上述代码还有问题的
- 没有考虑并发症写法
- 对对象的判断不够严谨,因为 typeof null === object
- 没有对预先设定的参数校正,可能会有null应返回null而不是{}
首先需要一个兼容的合并和判断 null 方法的函数
let isObject = obj => typeof obj === 'object' && object != null
把它加到我们的代码中
let isObject = obj => typeof obj === 'object' && obj != null;
let shallowClone = source =>{
if (!isObject(source)) return source;
let target = Array.isArray(source) ? []:{};
for (let key in source){
if(Object.prototype.hasOwnProperty.call(source, key)){
target[key] = isObject(source[key])? shallowClone(source[key]) : source[key];
}
}
return target
}
let demo = {
name : "ls",
book : {
title : 'Do you know JS',
price : "45"
},
h1 : null,
h2 : undefined,
h3 : [1,2,3]
}
let clone_demo = shallowClone(demo);
console.log(clone_demo);
demo.name = "qqq"
demo.book.price = "50"
demo.h3[1] = "55"
console.log(clone_demo.name,clone_demo.book.price) // ls 45
console.log(clone_demo)
我们试着去优化JSON.parse(JSON.stringify(obj))循环引用引发异常的问题。
let isObject = obj=>typeof obj ==='object' && obj !=null;
let shallowClone = (source,hash = new WeakMap()) =>{
if (!isObject(source)) return source;
if(hash.has(source)) return hash.get(source); //检测,查哈希表
let target = Array.isArray?[]:{};
hash.set(source,target) //设置哈希表
for (let key in source){
if(Object.prototype.hasOwnProperty.call(source,key)){
target[key] = isObject(source[key])? shallowClone(source[key],hash) : source[key] //传入哈希表
}
}
return target
}
let obj = {
a : 1,
b : {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let clone_obj = shallowClone(obj)
console.log(clone_obj);
总结
- | 和原数据是否指向相同 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
---|---|---|---|
赋值 | 是 | 改变原数据共同改变 | 改变原数据共同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变原数据共同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |