在 JavaScript 中,理解浅拷贝和深拷贝是编写高效代码的基础。尤其在处理引用类型时,正确的拷贝方法能够避免意想不到的副作用。本文将详细讨论浅拷贝和深拷贝的概念、常用方法以及它们的实现原理,并通过具体的代码示例帮助你更好地掌握这些知识。
拷贝的基本概念
在讨论拷贝之前,需要明确对象和基本类型的区别。JavaScript 中的基本类型(如number
、string
、boolean
、undefined
、null
、symbol
、bigint
)是按值传递的,而引用类型(如object
、array
、function
)是按引用传递的。
栗子🌰:
javascript
let a = 1;
let b = a;
a = 2;
console.log(b); // 1
let obj1 = { age: 18 };
let obj2 = obj1; // 引用赋值
obj1.age = 20;
console.log(obj2.age); // 20
在上述例子中,基本类型 a
和 b
是独立的,而引用类型 obj1
和 obj2
共享同一个地址,因此对 obj1
的修改会影响到 obj2
。所以拷贝通常我们只讨论在引用类型上的拷贝。
浅拷贝
浅拷贝是创建一个新对象,这个新对象有着原对象属性值的精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,因此新旧对象的引用类型属性仍然指向同一个地址,即拷贝后的对象受到原对象的影响。
常用方法:
Object.create(obj1)
Object.assign({}, obj1)
[].concat(arr1)
[...arr1]
arr1.slice(0)
arr1.toReversed().reverse()
以上方法都会返回一个新对象,只要把他们赋值到你想拷贝到的新对象上,就能模拟出浅拷贝的效果
浅拷贝的原理:
浅拷贝的实现可以通过遍历对象的属性,然后赋值给新对象来实现。但注意还要借助hasOwnProperty
规避原对象隐式具有的属性,因为拷贝我们一般是不拷贝原对象上隐式属性的。
对于
object.hasOwnProperty(propertyName)
如果对象自身拥有该属性,则返回true
。 如果对象自身没有该属性(即使在原型链上存在),则返回false
。
手搓浅拷贝
知道了这些,我们就可以自己手搓一个方法来实现浅拷贝。
javascript
function shallowCopy(obj) {
let obj1 = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
obj1[key] = obj[key];
}
}
return obj1;
}
let obj = { a: 1, b: { n: 2 } };
console.log(shallowCopy(obj)); // { a: 1, b: { n: 2 } }
有人可能会问为什么不用obj.key
来取对象上的属性,因为在这里key
是一个变量,如果用obj.key
的话,key
会被当成一个字符串,这样取到的就是对象上属性名为'key'
的对应的值。所以这里要用obj[key]
。
深拷贝
深拷贝是指完全拷贝一个对象及其所有嵌套对象,拷贝后的新对象与原对象完全独立,互不影响。
常用方法:
JSON.parse(JSON.stringify(obj))
structuredClone(obj)
JSON.parse(JSON.stringify(obj)) :
javascript
let obj = {
a: 1,
b: { n: 2 },
c: 'cc',
d: true,
e: undefined,
f: null,
g: function(){},
h: Symbol(1),
// i: 123n // BigInt 无法处理
};
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj); // { a: 1, b: { n: 2 }, c: 'cc', d: true, f: null }
这种方法简单易用,但有一些限制:
- 无法处理
BigInt
类型。 - 无法拷贝
undefined
、函数和Symbol
。 - 无法处理循环引用。
structuredClone() :
javascript
let obj = { a: 1, b: { n: 2 } };
const newObj = structuredClone(obj);
console.log(newObj); // { a: 1, b: { n: 2 } }
structuredClone
是一个新方法,能够处理大多数复杂的对象结构,但仍无法拷贝 Symbol
和函数,且有些浏览器可能不兼容。
深拷贝的原理:
深拷贝需要递归地拷贝对象的每一层属性,确保所有嵌套对象都能正确地深拷贝,即如果对象属性的值也是一个对象,则创建一个新对象,只能进行原始值的传递,而不是引用传递,不和原对象共用同一个地址。
手搓深拷贝:
javascript
function deepCopy(obj){
let newObj = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(obj[key] instanceof Object){
newObj[key] = deepCopy(obj[key])
}else{
newObj[key] = obj[key]
}
}
}
return newObj
}
let obj = { a: 1, b: { n: 2 } };
let obj2 = deepCopy(obj);
obj.b.n = 1;
console.log(obj2.b.n); // 2
总结
浅拷贝和深拷贝是JavaScript开发中非常重要的概念。浅拷贝适用于对象层级较浅的情况,而深拷贝则适用于需要完全复制对象的所有层级的情况。在实际开发中,应根据具体需求选择合适的拷贝方法,以确保代码的正确性和性能。
通过本文的讲解,希望你能够深入理解浅拷贝和深拷贝的原理,并在实际开发中灵活运用这些知识。