对象的引用赋值

js中一旦遇到引用数据类型,就会开辟一块堆内存,将引用数据类型的值进行储存,并给这块堆内存分配一个16进制地址。

1
2
3
4
const info = {name:"forward",age:24};
const obj = info;
obj.age=23;
console.log(info.age);//23

引用赋值

浅拷贝

  • 浅拷贝:将第一层的数据数据完全拷贝过来,如果有引用类型,引用类型指向的是一个16进制地址,也会拷贝过来(即:拷贝的引用类型和原数据指向同一个内存地址)
  • (对象)如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址
  • (数组)如果数组元素(arr[index])是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化
  • 即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
  • 常见的浅拷贝:
    • Object.assign()
    • concat()
    • slice() 返回一个新的数组对象;原始数组不会被改变。
    • …拓展运算符
    • Lodash库 _.clone(value)

例如:

1
2
3
4
5
6
7
//Object.assign()对象的浅拷贝
const info = { name: "forward", age: 24 };
//把info对象中的所有属性拷贝一份,放到第一个对象{}里面;若第一个对象{}有相同的属性,则会被后面的替换掉;
const obj = Object.assign({},info);
console.log(obj);//{name: 'forward', age: 24}
obj.age=23;
console.log(info.age);//24

对象浅拷贝

将上述代码进行简单修改,加一个对象参数;这个对象参数会开辟一块堆内存(16进制地址);浅拷贝会将第一层的数据数据完全拷贝过来,引用类型指向的是一个16进制地址,也会拷贝过来(即:拷贝的引用类型指向同一个内存地址);

1
2
3
4
5
//Object.assign()对象的浅拷贝
const info = { name: "forward", age: 24,friend:{name:"zhangsan",age:23} };
const obj = Object.assign({},info);
obj.friend.name="lisi";
console.log(info.friend.name);//lisi

浅拷贝

Lodash JS库

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库;用_.clone(xxx)实现浅拷贝;详见:Lodash 中文文档

1
2
3
4
const objects = [{ 'a': 1 }, { 'b': 2 }];
const shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

数组的浅拷贝

1
2
3
4
5
6
7
const arr = ['old', 1, true, null, undefined];
const arr2 = arr.concat();//concat()
const arr3 = arr.slice();//slice()
const arr4 = [...arr];//...拓展运算符
arr2[0]="new";
console.log(arr);
console.log(arr2);

深拷贝

  • 深拷贝是指完全生成了一个新的对象,里面所有的东西都是新的,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。

JSON.parse(JSON.string(xxx))

1
2
3
4
const info = { name: "forward", age: 24,friend:{name:"zhangsan",age:23} };
const obj = JSON.parse(JSON.stringify(info))
obj.friend.name="lisi";
console.log(info.friend.name);//zhangsan

此方法缺点:JSON.stringify(..) 在对象中遇到 undefined 、 function 和 symbol 时会自动将其忽略, 在 数组中则会返回 null (以保证单元位置不变)。详见:JSON.stringify 深拷贝的弊端

1
2
3
4
console.log(JSON.stringify(undefined));//undefined
console.log(JSON.stringify( function(){}));//undefined
console.log(JSON.stringify( [1,undefined,function(){},4]));//[1,null,null,4]
console.log(JSON.stringify({ undefined,a:2, b:function(){}}));//{"a":2}

Lodash JS库

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库;用_.cloneDeep(xxx)实现深拷贝;详见:Lodash 中文文档

1
2
3
4
5
const objects = [{ 'a': 1 }, { 'b': 2 }];

const deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

浅拷贝+递归

见下文手写浅(深)拷贝

structuredClone()

在 JS 中深拷贝一个对象,我们一般会使用 lodash 的 deepClone 等方法。现在一个新的原生函数可以完美胜任这个任务:
structuredClone();这个函数几乎完美适配所有类型,甚至包括: Error,Date,Blob 等等。更棒的是而且现在主流的浏览器都兼容这个函数;

1
2
3
4
5
6
7
8
const obj = {
bar: ["ES6", "NEW API", "DeepClone"]
}
const obj2 = structuredClone(obj);
obj.bar.push("add");
obj2.bar.pop();
console.log(obj.bar); //  ['ES6', 'NEW API', 'DeepClone', 'add']
console.log(obj2.bar); //  ['ES6', 'NEW API']

手写浅(深)拷贝

object.hasOwnProperty(propertyName)

  • 用来检测属性是否为对象的私有属性,如果是,返回true,否则返回false; 参数propertyName指要检测的属性名;
  • 所以说不会检测原型链上的公有属性;
  • 说简单点,它能帮你指向你当前循环的对象,而过滤掉原型链上其它对象,因为在工作中我们很难保证其他人是否会修改原型链,这样做会更为保险;
  • hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(处理对象属性而不遍历原型链);
  • Object.hasOwn()如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false备注: Object.hasOwn() 旨在取代 Object.hasOwnProperty()

object.hasOwnProperty(propertyName)

1
2
3
4
5
6
7
8
9
10
11
12
const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1'));
// Expected output: true

console.log(object1.hasOwnProperty('toString'));
// Expected output: false

console.log(object1.hasOwnProperty('hasOwnProperty'));
// Expected output: false

Object.hasOwn()

1
2
3
4
5
6
7
8
9
10
11
12
const object1 = {
prop: 'exists'
};

console.log(Object.hasOwn(object1, 'prop'));
// Expected output: true

console.log(Object.hasOwn(object1, 'toString'));
// Expected output: false

console.log(Object.hasOwn(object1, 'undeclaredPropertyValue'));
// Expected output: false

浅拷贝

遍历对象,然后把属性和属性值都放在一个新的对象不就好了~

1
2
3
4
5
6
7
8
9
10
11
12
13
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}

深拷贝

那如何实现一个深拷贝呢?说起来也好简单,我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数不就好了~

1
2
3
4
5
6
7
8
9
10
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}

总结

和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变会使原数据一同改变 改变会使原数据一同改变

参考文献

JS中对象深拷贝:structuredClone()

Object.assign()的使用

JavaScript专题之深浅拷贝

详细解析赋值、浅拷贝和深拷贝的区别

hasOwnProperty() 方法详解