学习数据拷贝及对象深拷贝、浅拷贝

基本数据类型与引用类型的拷贝

在js内存分配中,分为堆内存和栈内存;栈内存存储基本数据类型,引用类型的键(属性名)及地址和代码执行时的环境,而堆内存主要是存放引用类型的内容。这里就可以看出,一个引用类型并不是存储在一个地方,而是分开存储的,而基本数据类型都存储在栈内存中。基本数据类型是按值访问的,所以当复制一个基本类型的时候,得到的结果是完全独立的,与被复制的变量没有任何关系。

(图片来自网络)

let num=2;
let num1=num;
num=3;
console.log(num);    //3
console.log(num1);   // 2

如果复制一个引用类型的时候,其实复制的是一个内存地址,而这个内存地址指向存放在堆内存里的内容的位置。

比如下面的例子,

let boy={
     apple:3
};
let girl=boy;
box.apple=2;
console.log(boy.apple);   //2
console.log(girl.apple);  //2

男孩和女孩是一家人,男孩和朋友说,我有3个苹果,女孩接着就说,我和弟弟一样,也有3个苹果,而且我们的苹果都是放在家里的冰箱里。接着弟弟说,其实我刚刚偷吃了一个苹果,冰箱里其实就只有2个苹果了,这时姐姐也只能说:好吧,现在我也只有2个苹果了。这个例子中,苹果都是存放在家里的冰箱内,也就是说男孩和女孩都是引用的冰箱的地址,如果是单纯的复制地址,那么只要其中一者有变化,另外的也会随之变化。而常见的引用类型有Obejct,Array,Function,Date,RegExp,Error等。ES6还提供了Set和Map。

对于引用类型的复制机制就衍生出来了深拷贝和浅拷贝。

浅拷贝

浅拷贝的定义

创建一个新对象,然后复制原对象里所有的属性值。如果拷贝的属性值是基本类型,那么就是基本类型。基本数据类型是按值引用的,和原属性互不影响。如果拷贝的属性是引用类型,那么还是拷贝引用类型的地址。如果其中有一个改变了这个地址的内容,那么共用这个地址的对象也会受到影响。简单的来说,浅拷贝只是拷贝对象的第一层,如果属性值都是基本类型,那么拷贝后的对象与原对象都是独立的,但是如果属性值里有引用类型,那么引用的还是地址。

以下是javascript提供的一些浅拷贝的方法。

Object.assign

Object.assign() 方法用于将所有可枚举属性(不可枚举的属性不会拷贝)的值从一个或多个源对象复制到目标对象。它将返回目标对象。

语法:Object.assign(target,sources)

target:目标对象;

sources:源对象。

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。**注意:Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象,也就是说不可枚举的属性无法拷贝。**

//对象合并
const obj1= { a: 1, b: 2 };
const obj2= { b: 4, c: 5 };
const obj3 = Object.assign(obj1, obj2);
console.log(obj1);   //{ a: 1, b: 4, c: 5 }
console.log(obj3);  //{ a: 1, b: 4, c: 5 }

实现复制对象

const obj={a:1,b:2,c:{color:"red"}};
const obj1=Object.assign({},obj);
console.log(obj1);   //{a:1,b:2,c:{colro:"blue"}}
obj1.b=3;
obj1.c.color="blue"
console.log(obj1);     //{a:1,b:3,c:{color:"blue"}}
console.log(obj);     //{a:1,b:2,c:{color:"blue"}}

扩展运算符

利用扩展运算符可以在构造字面量对象时,进行克隆或者属性拷贝。

语法:var object={...obj}

const obj={a:1,b:2,c:{color:"red"}};
const obj1={...obj};
console.log(obj1);   //{a:1,b:2,c:{color:"red"}}
obj1.a=5;
obj.c.color="blue";   
console.log(obj1);  //{a:5,b:2,c:{color:"blue"}}
console.log(obj);   //{a:1,b:2,c:{color:"blue"}}

扩展运算符合Object.assign一样,对于属性值是对象的依旧会拷贝起地址,无法拷贝成两个完全独立的对象,但是如果拷贝的对象属性值都是基本类型的值的话,使用扩展运算符会更加方便一些。

Array.prototype.slice

slice是数组中的一个方法,表示拷贝一个数组,该方法可接受2个参数,slice(begin,end),begin是必需的,表示索引开始从几个开始复制,end不是必写的。表示结束的位置,包括结束的位置。

var arr = new Array(6)
arr[0] = "George";
arr[1] = "John";
arr[2] = "Thomas";
arr[3] = "Thomas";
arr[4] = "Adrew";
arr[5] = "Martin";
console.log(arr.slice(1))  //["John","Thomas","Adrew","Martin"]
console.log(typeof arr)    //object
console.log(arr)     //["George","John","Thomas","Adrew","Martin"]

从上面打印出来的结果来看,slice处理的是一个对象,但其实也不全是对象,对于这种对象我们更愿意称之为类数组,“array like object”。什么是类数组这里不细说了,简单就是key值数字,而且这个对象里面有一个length属性。而Array.prototype.slice可以将一个类数组转化为一个数组。ES6也提供了一个方法Array.from(),可以将一个类数组转为数组。注意:slice不会改变原数组,表示复制(拷贝),所以可以看做是浅拷贝。而splice也是数组的一个方法,这个方式是用来删除组数的,返回删除后的组数,是会修改原数组的。

Array.prototype.concat

concat()方法用于合并两个或多个数组的。有点类似Object.assign,但该方法是合并对象的。而concat方法合并后返回一个新数组,是不会更改原数组的,而相同的数组并不会被覆盖,可以理解为两个数组拼接为一个数组;而Object.assign合并对象,对于同名的键,其值是会被合并覆盖的。

深拷贝

深拷贝就是在拷贝数据的时候,件数据的所有引用结构都拷贝一份。简单的说就是,在内存中存在两个数据结构完全相同又相互独立的数据,将引用类型进行复制,而不是只是复制其引用地址。

一个简单的深拷贝

var obj={
    a:{
        b:1
    },
    c:2
}
var obj1={};
obj1.a={};
obj1.a.b=obj.a.b;
obj1.c=obj.c;
console.log(obj1);    //{a:{b:1},c:2}
obj.a.b="color";
console.log(obj);     //{a:{b:"color"},c:2}
console.log(obj1);    //{a:{b:1},c:2}

JSON.stringify

Json.stringify()方法是将一个javascript值(对象、数组)转化为一个json字符串。

语法 :JSON.stringify(value,replacer)

value:必填,要序列化转化为字符串的值,也就是目标值

replacer:可选,对于序列的目标增添条件,该参数可是一个函数或是一个数组。

let obj1 = {
    a:1,
    b:[1,2,3]
}
let str=JSON.stringify(obj1);
let obj2=JSON.parse(str);
console.log(str);    //"{"a":1,"b":[1,2,3]}"
console.log(obj2);   //{a:1,b:[1,2,3]}
obj1.b=[1,2,3,4];
console.log(obj1);   //{a:1,b:[1,2,3,4]}
console.log(obj2);   //{a:1,b:[1,2,3]}

该方法就是利用JSON对象的stringify方法和parse方法,把一个对象转化为字符串,字符串是基本类型,所以会拷贝出一个独立的值,然后在把字符串转化为一个对象。这样就拷贝出一个全新的对象了。这也是实现深拷贝常用的方法。需要注意的是:对于不可枚举的属性以及symbol属性都不可序列化,使用JSON拷贝还有一个缺点,对象里的函数无法被拷贝,原型链里的属性无法被拷贝。详情参考MDN

第三方库实现深拷贝

1.lodash

2.jQuery

递归实现深拷贝

递归实现深拷贝可参考简单深拷贝的例子

function deepClone(obj){
    var object={};
    for(key in obj){
        if(typeof obj[key]==="object"){
            object[key]=deepClone[obb[key]]
        }else{
            object[key]=obj[key]
        }
    }
    return object;
}

但是该方法只是针对对象类型的对象进行深拷贝,对于第二次是Array,Date,symbol等都是不能实现深拷贝的。

陈健的个人博客,记录生活所见所感、学习笔记。专注于Web前端_SEO教程_读书心得。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

返回主页看更多
狠狠的抽打博主 支付宝 扫一扫