1. 原始值 (Primitive Values)
JavaScript有7种原始数据类型:
// 原始值类型
let str = "hello"; // string
let num = 42; // number
let bool = true; // boolean
let n = null; // null
let u = undefined; // undefined
let sym = Symbol('id'); // symbol
let big = 123n; // bigint
原始值特点:
- 存储在栈内存中
- 按值传递/赋值
- 不可变 (immutable)
- 不能添加属性
2. 引用值 (Reference Values)
除了原始值之外的都是引用值:
// 引用值类型
let obj = {}; // Object
let arr = []; // Array
let func = function(){}; // Function
let date = new Date(); // Date
let reg = /pattern/; // RegExp
// ... 等等
引用值特点:
- 存储在堆内存中,变量存储的是内存地址
- 按引用传递/赋值
- 可变 (mutable)
- 可以添加属性
3. 原始值 - 永远是深拷贝
原始值在赋值/传递时,总是复制值本身,不存在浅拷贝问题:
// 原始值拷贝
let a = 10;
let b = a; // b得到a值的完整副本
a = 20;
console.log(a); // 20
console.log(b); // 10,完全独立
let str1 = "hello";
let str2 = str1; // str2得到字符串的副本
str1 = "world";
console.log(str1); // "world"
console.log(str2); // "hello",不受影响
// 原始值作为参数传递
function changeValue(x) {
x = 100;
return x;
}
let num = 50;
let result = changeValue(num);
console.log(num); // 50,原值不变
console.log(result); // 100
4. 引用值 - 默认浅拷贝
引用值在直接赋值时是浅拷贝,只复制引用地址:
// 浅拷贝示例
let obj1 = {
name: "张三",
age: 25,
hobbies: ["读书", "游泳"],
address: {
city: "北京",
district: "朝阳区"
}
};
// 直接赋值 = 浅拷贝
let obj2 = obj1;
obj2.name = "李四";
console.log(obj1.name); // "李四" - 原对象被影响!
// 使用扩展运算符 = 浅拷贝
let obj3 = { ...obj1 };
obj3.name = "王五";
console.log(obj1.name); // "李四" - 第一层属性独立
// 但嵌套对象仍然共享引用
obj3.address.city = "上海";
console.log(obj1.address.city); // "上海" - 嵌套对象被影响!
obj3.hobbies.push("跑步");
console.log(obj1.hobbies); // ["读书", "游泳", "跑步"] - 数组被影响!
5. 常见的浅拷贝方法
let original = {
a: 1,
b: { c: 2 },
d: [3, 4]
};
// 方法1: 扩展运算符
let copy1 = { ...original };
// 方法2: Object.assign()
let copy2 = Object.assign({}, original);
// 方法3: 数组的slice()
let arr = [1, { a: 2 }, [3, 4]];
let arrCopy = arr.slice();
// 这些都是浅拷贝,嵌套对象仍然共享引用
copy1.b.c = 999;
console.log(original.b.c); // 999 - 原对象被影响
arrCopy[1].a = 888;
console.log(arr[1].a); // 888 - 原数组被影响
6. 深拷贝的实现
为了避免浅拷贝问题,需要实现深拷贝:
// 方法1: JSON序列化(有限制)
function deepCopyJSON(obj) {
return JSON.parse(JSON.stringify(obj));
}
// 局限性:
let objWithFunction = {
name: "张三",
sayHello: function() { console.log("Hello"); }, // 函数会丢失
birthday: new Date(), // Date变成字符串
reg: /pattern/, // 正则变成空对象
undef: undefined // undefined会丢失
};
let copy = deepCopyJSON(objWithFunction);
console.log(copy);
// { name: "张三", birthday: "2024-01-01T00:00:00.000Z", reg: {} }
// 方法2: 递归深拷贝
function deepCopy(obj) {
// 处理原始值
if (obj === null || typeof obj !== "object") {
return obj;
}
// 处理Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// 处理Array
if (Array.isArray(obj)) {
return obj.map(item => deepCopy(item));
}
// 处理Object
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepCopy(obj[key]);
}
}
return cloned;
}
// 测试深拷贝
let original2 = {
name: "张三",
info: {
age: 25,
hobbies: ["读书", "游泳"]
}
};
let deepCopied = deepCopy(original2);
deepCopied.info.age = 30;
deepCopied.info.hobbies.push("跑步");
console.log(original2.info.age); // 25 - 原对象不受影响
console.log(original2.info.hobbies); // ["读书", "游泳"] - 原数组不受影响
7. 实际开发中的注意事项
// 函数参数传递
function processUser(user) {
// 错误做法:直接修改传入的对象
user.processed = true; // 会影响原对象!
// 正确做法:先深拷贝再修改
const userCopy = deepCopy(user);
userCopy.processed = true;
return userCopy;
}
// React/Vue中的状态更新
// 错误做法
state.users.push(newUser); // 直接修改原数组
// 正确做法
setState({
users: [...state.users, newUser] // 创建新数组
});
// 或者
setState({
user: { ...state.user, name: "新名字" } // 创建新对象
});
8. 总结对比
| 特性 | 原始值 | 引用值 |
|---|---|---|
| 拷贝方式 | 始终深拷贝 | 默认浅拷贝 |
| 独立性 | 完全独立 | 可能共享引用 |
| 修改影响 | 不影响原值 | 浅拷贝会影响原值 |
| 内存占用 | 较少 | 较多(如果深拷贝) |