1. 基本类型与引用类型的本质区别
代码示例:
javascript
// 基本类型(按值传递)
let a = 1;
let b = a; // b = 1(复制值)
a = 2; // 修改a的值
console.log(b); // 输出1(b不受影响)
// 引用类型(按引用传递)
let obj = { a: 1 };
let obj2 = obj; // obj2 指向同一内存地址
obj.a = 2; // 修改原对象
console.log(obj2.a); // 输出2(共享内存)
原理:
- 基本类型(数字、字符串等)存储在栈内存中,赋值时直接复制值。
- 引用类型 (对象、数组等)的变量存储的是内存地址,赋值时复制地址而非实际数据。
总结 :
修改基本类型变量不影响其他变量,而修改引用类型对象会影响所有指向该地址的变量。
2. 浅拷贝的陷阱:对象嵌套问题
代码示例:
javascript
const obj = {
a: 1,
b: { n: 2 } // 嵌套对象
};
const copy = Object.assign({}, obj); // 浅拷贝
obj.a = 10; // 修改原对象的第一层属性
obj.b.n = 20; // 修改嵌套对象属性
console.log(copy);
// 输出 { a: 1, b: { n: 20 } }
关键点:
Object.assign
只会复制对象的第一层属性。- 嵌套对象(如
b
)的地址被复制,新旧对象共享同一子对象。
结论 :
浅拷贝不适用于嵌套结构的对象,子对象修改会相互影响。
3. 深拷贝的常见方法对比
代码示例 1:JSON 序列化法
javascript
const obj = {
name: "John",
date: new Date(), // Date对象
fn: () => console.log(1), // 函数
sym: Symbol("key"), // Symbol
ref: null // 循环引用占位符
};
obj.ref = obj; // 循环引用
const copy = JSON.parse(JSON.stringify(obj));
// 输出:{ name: "John", date: "2024-05-15T...", ref: null }
// 丢失:函数、Symbol,Date转为字符串
代码示例 2:structuredClone
javascript
const user = {
like: { game: "Chess" },
tags: new Set(["A", "B"]) // 支持复杂类型
};
const copy = structuredClone(user);
user.like.game = "Guitar";
console.log(copy.like.game); // "Chess"(完全独立)
console.log(copy.tags instanceof Set); // true(保留类型)
对比总结:
方法 | 优点 | 缺点 |
---|---|---|
JSON 方法 | 简单快速 | 丢失函数、Symbol、循环引用报错 |
structuredClone | 支持复杂类型、循环引用 | 不兼容旧浏览器(如 IE) |
4. 原型链与静态方法的本质
代码示例:
javascript
function Person() {}
// 实例方法(挂载原型)
Person.prototype.run = function() {
console.log("跑步");
};
// 静态方法(挂载构造函数)
Person.say = function() {
console.log("你好");
};
const p = new Person();
p.run(); // 输出 "跑步"(通过原型链调用)
Person.say(); // 输出 "你好"(直接调用构造函数方法)
p.say(); // 报错(实例无法访问静态方法)
原理:
- 原型方法 :所有实例共享,通过
prototype
定义。 - 静态方法 :属于构造函数本身,用于工具函数或类级别操作。
总结 :
静态方法通过类名调用,实例方法通过对象调用,二者作用域不同。
5. 数组去重的正确实现
代码示例:
javascript
const arr = [1, 2, 2, 3, {n: 4}, {n: 4}];
// 正确去重方法(支持对象)
const unique = (arr) => {
const seen = new Map(); // 记录唯一值
return arr.filter(item => {
const key = typeof item + JSON.stringify(item);
return seen.has(key) ? false : seen.set(key, true);
});
};
console.log(unique(arr)); // [1, 2, 3, {n:4}, {n:4}]
关键点:
- 对象无法直接比较(
{n:4} !== {n:4}
),需序列化为字符串。 - 使用
Map
存储唯一标识,避免重复。
总结 :
去重需根据数据类型选择合适策略,对象需特殊处理。
6. 原型链属性检测的误区
代码示例:
javascript
Object.prototype.sharedProp = "全局属性"; // 修改原型链
const obj = { ownProp: "自有属性" };
console.log(obj.hasOwnProperty("sharedProp")); // false(不检查原型链)
console.log("sharedProp" in obj); // true(in检查原型链)
// 正确遍历对象属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 过滤原型链属性
console.log(key); // 仅输出 "ownProp"
}
}
原理:
hasOwnProperty
仅检查对象自身属性。for...in
会遍历原型链上的可枚举属性。
总结 :
遍历对象时需用hasOwnProperty
过滤原型链属性,避免意外行为。
总结与实践建议
-
拷贝选择
- 简单对象用浅拷贝(
Object.assign
、展开运算符)。 - 嵌套对象用深拷贝(
structuredClone
或手动递归)。
- 简单对象用浅拷贝(
-
原型链注意点
- 实例方法定义在
prototype
,静态方法直接挂载构造函数。 - 使用
hasOwnProperty
区分自有属性和继承属性。
- 实例方法定义在
-
数组操作
- 去重要考虑对象和特殊值的唯一性判断。
- 使用
slice
、concat
等方法实现数组浅拷贝。
-
兼容性处理
- 旧项目用
JSON
深拷贝时需规避特殊类型。 - 现代项目优先使用
structuredClone
。
- 旧项目用
理解这些核心机制,可避免代码中的隐蔽问题,提升开发效率。