目录
数据类型介绍
要想了解深浅拷贝,首先得知道有哪些数据类型。在Javascript中,存在两大数据类型:
基本数据类型(Primitive Types)
JavaScript的基本数据类型包括以下六种:
- Number: 用于表示整数或浮点数。例如:
42
、3.14
。 - String: 用于表示文本数据。例如:
"hello"
、'world'
。 - Boolean: 表示逻辑值,只有
true
和false
两种。 - Undefined: 表示变量未定义或未赋值。例如:
let a;
,此时a
为undefined
。 - Null: 表示空值,通常用于明确赋空值的情况。例如:
let b = null;
。 - Symbol(ES6新增):表示唯一且不可变的值,通常用于对象属性的键。例如:
Symbol('id')
。
基本数据类型的特点是不可变,直接存储在栈内存中。
引用数据类型(Reference Types)
引用数据类型主要包括以下两种:
- Object: 用于存储键值对或复杂数据结构。例如:
{ name: "Alice", age: 25 }
。 - Array: 是特殊的对象,用于存储有序数据。例如:
[1, 2, 3]
。 - Function: 也是对象的一种,可以被调用执行。例如:
function() { return "hello"; }
。 - Date、RegExp等内置对象。
引用数据类型的特点是可变,数据存储在堆内存中,变量存储的是指向堆内存的地址。
在编程中,基本数据类型通常不涉及深拷贝或浅拷贝的概念。所以面试常问的深浅拷贝指的是针对引用数据的处理。
浅拷贝
仅复制对象的顶层属性,如果属性是引用类型(如数组、对象),则复制的是引用而非实际对象。因此,修改拷贝后的引用类型属性会影响原对象。
实现浅拷贝的方式有很多,举几个案例:
// Object.assign()
// 使用 Object.assign() 可以快速实现浅拷贝:
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
// 展开运算符(...)
// ES6 的展开运算符也能实现浅拷贝:
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
// 数组的浅拷贝
// 对于数组,可以使用 slice() 或 concat():
const originalArray = [1, 2, { a: 3 }];
const copyArray = originalArray.slice();
注意
引用赋值共享同一个对象,无独立性,不属于浅拷贝
浅拷贝第一层属性独立,嵌套引用类型仍共享
var a = {a:1}
var b = a
深拷贝
会递归复制对象的所有层级,包括引用类型属性,生成一个完全独立的新对象。修改深拷贝后的对象不会影响原对象。
深拷贝开辟一个新的栈,两个对象的属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另外一个
利用JSON的序列化和反序列化实现深拷贝
JSON.parse(JSON.stringify())
优点:简单易用,常见的深拷贝的方法
缺点:无法处理函数、Symbol、undefined等非 JSON 兼容类型,会丢失这些属性
const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));
递归实现深拷贝
优点:可以处理各种类型
缺点:需要手动处理循环引用等问题
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
第三方库lodash的cloneDeep
优点:功能完善,处理了各种边界情况
缺点:需要引入额外库
const _ = require('lodash');
const obj = {a: 1, b: {c: 2}};
const clone = _.cloneDeep(obj);
函数缓存的概念
函数缓存是一种优化技术,通过存储函数计算结果避免重复执行相同输入的计算。适用于计算成本高或输入输出稳定的场景,如递归、API调用或数学运算。
函数缓存,就是将函数运算过的结果进行缓存,本质上就是用空间(缓存存储)换时间(计算过程),常用于缓存数据计算结果和缓存对象
实现方法
实现函数缓存主要依靠闭包、柯里化、高阶函数。详解在其他文章。