「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」
Javascript对象拷贝
JS中的原始值和引用值概念
ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象。
在把一个值赋给变量时,JavaScript 引擎必须确定这个值是原始值还是引用值。JS中有6种原始值:Undefined、Null、Boolean、Number、String 和 Symbol(6种基础类型)。保存原始值的变量是按值(byvalue)访问的,因为我们操作的就是存储在变量中的实际值。
引用值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的
原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。不过,在变量保存了这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法,。比如,看下面的例子:
let person = new Object();
person.name = "Nicholas";
console.log(person.name); // "Nicholas"
原始值不能有属性,尽管尝试给原始值添加属性不会报错。比如:
let name = "Nicholas";
name.age = 27;
console.log(name.age); // undefined
除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置
let num1 = 5;
let num2 = num1;
这里,num1 包含数值 5。当把 num2 初始化为 num1 时,num2 也会得到数值 5。这个值跟存储在 num1 中的 5 是完全独立的,因为它是那个值的副本。这两个变量可以独立使用,互不干扰
在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来
let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); // "Nicholas"
在这个例子中,变量 obj1 保存了一个新对象的实例。然后,这个值被复制到 obj2,此时两个变量都指向了同一个对象。在给 obj1 创建属性 name 并赋值后,通过 obj2 也可以访问这个属性,因为它们都指向同一个对象。下图展示了变量与堆内存中对象之间的关系。
浅拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
- 直接赋值:任何操作都会影响原对象
let obj2 = obj1;
- Object.assign: 拷贝属性值,假如属性值是一个对象的引用,那么也会指向那个引用,就是上文所说的拷贝的是内存地址
let obj2 = Object.assign({},obj1);
-
Array.prototype.slice();
- 提取数组;
- 参数(可选):startIndex,endIndex(与索引值一致);
- 拷贝规则同Object.assign;
let arr2 = arr1.slice(start,end); -
扩展运算符(...);
- 拷贝规则同Object.assign;
let obj2 = {obj1} or [...obj1]; -
lodash(_.clone());
- 拷贝规则同Object.assign;
let obj2 = _.clone(obj1);
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
-
JSON.parse(JSON.stringify());
- 将字符串parse后创建新对象
let obj2=JSON.parse(JSON.stringify(obj1));- 如果这个对象里属性是function,不会拷贝成功,
let obj = { fun: function name(params) { } } console.log(obj);// { fun: [Function: name] } let temp = JSON.parse(JSON.stringify(obj)) console.log(temp);// {}- 如果被拷贝的对象中某个属性的值为undefined,则拷贝之后该属性会丢失
let obj = { a:2, name:undefined } console.log(obj);// {a:2, name: undefined } let temp = JSON.parse(JSON.stringify(obj)) console.log(temp);// {a:2}- 如果被拷贝的对象中有正则表达式,则拷贝之后的对象正则表达式会变成Object,但对象是{}
let obj = { a:2, name:/abc/ } console.log(obj);// { a:2, name: /abc/ } let temp = JSON.parse(JSON.stringify(obj)) console.log(temp);// {a:2, name:{}}- 对象数组中的拷贝也是一样
let obj = [{ name: undefined }] console.log(obj);// [ { name: undefined } ] let temp = JSON.parse(JSON.stringify(obj)) console.log(temp);// [ {} ] -
递归赋值;
function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ //判断是否为自身属性 if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } -
lodash(_.cloneDeep());
let obj2 = _.cloneDeep(obj1);