当前位置: 首页 > news >正文

JavaScript手录08-对象

在 JavaScript 中,对象(Object)是一种复杂数据类型,用于存储键值对(key-value pairs)的集合,是实现复杂功能的核心数据结构之一。

画板

一、前置知识:JavaScript中的数据类型

在JavaScript中,数据类型分为基本数据类型和引用数据类型。

基本数据类型

基本数据类型是不可变的简单数据值,直接存储在栈内存中。JavaScript中有7种基本数据类型。

JavaScript中的基本数据类型
  1. Number:数字类型,包括整数、浮点数、NaN、Infinity等
let num = 42;
let float = 2.22223;
let notANum = '1' / '2a'; // NaN
let infi = 1/0; // Infinity
  1. String:字符串
let str1 = 'hello';
let str2 = "world";
let str3 = `${str1} ${str2} !` // hello world !
  1. Boolean:布尔值 true 或者 false
let isDone = true
let hasError = false
let isBig = 2 > 1 // true
  1. null:空值
let emptyValue = null
  1. undefined:未定义,变量声明后未赋值时的默认值
let unde;
console.log(unde); // undefined
  1. Symbol:ES6新增,用于创建唯一标识符(即使描述相同,值也不相同)
let sym1 = Symbol('id');
let sym2 = Symbol('id');
console.log(sym1 === sym2); // false
  1. bigInt:ES6新增,用于表示超出Number范围的大整数(结尾加n)
let bigNum = 1234567890123456789012345678901234567890n;
基本数据类型的特性
  • 存储方式:值直接存储在栈内存中,变量指向具体的值。
  • 不可变性:无法直接修改原始值。对基本数据类型的任何修改都会创建新值。
  • 赋值行为:赋值时传递的是值的副本。(值传递)
  • 比较方式:直接比较值是否相等。

引用数据类型

引用数据类型是复杂数据结构,存储在堆内存中,变量仅指向堆内存的引用地址。

JavaScript中的引用数据类型
  1. Object:对象,键值对集合。
let obj = {name: 'Amy',age: 18
}
  1. Array:数组,有序数据集合。
let arr = [1,2,3,4,5]
  1. Function:函数,可执行代码块。
function fn(arg) {console.log(arg)
}
fn('test!')
  1. 其他内置对象:例如Date对象、RegExp对象、Map、Set等
const arr = [1, 2, 3];
arr.push(4); // 直接修改原数组
console.log(arr); // [1, 2, 3, 4]
引用数据类型的特性
  • 存储方式:值存储在堆内存中,变量存储的是指向该值的引用地址(存储在栈内存中)。
  • 可变性:可以直接修改对象的属性或内容。(不会创建新对象)
  • 赋值行为:赋值时传递的是引用地址的副本(引用传递),多个变量指向同一个对象
  • 比较方式:比较的是引用地址是否相同。(即使内容相同,不同对象也不相等。)

区别与联系

特性基本数据类型引用数据类型
存储位置栈内存(直接存值)堆内存(存值)+栈内存(存引用)
赋值方式复制值(值传递)复制引用地址(引用传递)
比较方式比较值是否相等比较引用地址是否相同
可变性不可变(修改会创建新值)可变(直接修改原对象)
占用内存固定(根据类型)不固定(动态分配)
类型举例Number、String、Boolean等Object、Array、Function等

数据类型的判断

typeof 运算符

适合判断基本数据类型。

typeof 42;        // "number"
typeof "hello";   // "string"
typeof true;      // "boolean"
typeof undefined; // "undefined"
typeof Symbol();  // "symbol"
typeof 123n;      // "bigint"
typeof null;      // "object"(历史遗留问题)
typeof {};        // "object"
typeof [];        // "object"(数组本质是对象)
typeof (() => {}); // "function"(函数特殊处理)
instanceof 运算符

适合判断引用数据类型。

[] instanceof Array;    // true
{} instanceof Object;   // true
(() => {}) instanceof Function; // true
Object.prototype.toString.call()

最准确的判断方式。

Object.prototype.toString.call(42);      // "[object Number]"
Object.prototype.toString.call([]);      // "[object Array]"
Object.prototype.toString.call(null);    // "[object Null]"
Array.isArray()

用于判断数组。

let arr = [1,2,3]
Array.isArray(arr); // true

二、对象

概念

对象由属性(property)和方法(method)组成:

  • 属性:描述对象的特征(键值对中的值可以是任意数据类型)
  • 方法:描述对象的行为(值为函数的属性)
// 一个简单的对象
const person = {name: "Alice",  // 属性age: 30,        // 属性sayHello: function() {  // 方法console.log(`Hello, I'm ${this.name}`);}
};

对象的创建方式

对象字面量(最常用)

使用 {} 直接定义对象,简洁直观:

const obj = {key1: value1,key2: value2,// ...
};
构造函数
  • 使用 new Object() 创建:
const obj = new Object();
obj.name = "Bob";
obj.age = 25;
  • 自定义构造函数(用于创建同一类型的多个对象):
function Person(name, age) {this.name = name;this.age = age;this.sayHi = function() {console.log(`Hi, I'm ${this.name}`);};
}const person1 = new Person("Charlie", 28);
const person2 = new Person("Diana", 22);
Object.create()

基于现有对象创建新对象(继承原型):

const parent = { type: "human" };
const child = Object.create(parent);
child.name = "Eve";
console.log(child.type); // "human"(继承自parent)

对象属性的操作

访问属性
  • 点语法:obj.property
  • 方括号语法:obj["property"](适合属性名含特殊字符或动态获取时)
const car = { brand: "Tesla", "model year": 2023 };
console.log(car.brand);        // "Tesla"
console.log(car["model year"]); // 2023(必须用方括号)// 动态属性名
const prop = "brand";
console.log(car[prop]); // "Tesla"
修改/添加属性

直接赋值即可(存在则修改,不存在则添加):

const phone = { brand: "Apple" };
phone.brand = "Samsung"; // 修改现有属性
phone.price = 5999;      // 添加新属性
删除属性

使用 delete 关键字:

const book = { title: "JS Guide", author: "John" };
delete book.author;
console.log(book.author); // undefined
检查属性是否存在

使用 in 运算符:

const fruit = { name: "apple", color: "red" };
console.log("name" in fruit);  // true
console.log("size" in fruit);  // false

对象的特殊属性

可枚举性(enumerable)

控制属性是否能被 for...in 循环遍历或 Object.keys() 获取:

const obj = { a: 1 };
Object.defineProperty(obj, "b", {value: 2,enumerable: false // 不可枚举
});console.log(Object.keys(obj)); // ["a"](只包含可枚举属性)
可配置性(configurable)

控制属性是否能被删除或修改特性:

const obj = { a: 1 };
Object.defineProperty(obj, "a", {configurable: false // 不可配置
});
delete obj.a; // 无效,无法删除
可写性(writable)

控制属性是否能被重新赋值:

const obj = { a: 1 };
Object.defineProperty(obj, "a", {writable: false // 只读
});
obj.a = 2; // 无效,无法修改

对象的常用方法

对象的遍历方法

用于获取对象的键、值、或者键值对,适用于需要遍历对象属性的场景。

  1. Object.keys(obj)
  • **功能:**返回一个包含对象自身可枚举属性的键名数组。(不包括继承的属性和不可枚举的属性)
  • 示例
let obj = {name: 'Amy',age: 18,gender: '女'
}
console.log(Object.keys(obj)); // ['name', 'age', 'gender']
  1. Object.values(obj)
  • 功能:返回一个包含对象自身可枚举属性的值数组。
  • 示例
let obj = {name: 'Alice',age: 25,gender: 'female'
}
console.log(Object.values(obj)); // ["Alice", 25, "female"]
  1. Object.entries(obj)
  • 功能:返回一个包含对象自身可枚举属性的键值对数组(每个元素是 <font style="color:rgb(0, 0, 0);">[key, value]</font> 形式)
  • 示例
let obj = {name: 'Alice',age: 25,gender: 'female'
}
console.log(Object.entries(obj)); 
// [["name", "Alice"], ["age", 25], ["gender", "female"]]
  • 应用:常用于将对象转换为Map或者遍历键值对。
// 转换为 Map
const map = new Map(Object.entries(obj));
  1. for…in循环
  • 功能:遍历对象自身及继承的可枚举属性(需配合 <font style="color:rgb(0, 0, 0);">hasOwnProperty</font> 过滤继承属性)
  • 示例
for (const key in obj) {if (obj.hasOwnProperty(key)) { // 只处理自身属性console.log(`${key}: ${obj[key]}`);}
}
对象的拷贝与合并方法
  1. Object.assign(target, …source)
  • 功能:将一个或多个源对象的可枚举属性复制到目标对象,返回目标对象(浅拷贝)
  • 特点:
    • 同名属性会被后面的源对象覆盖
    • 只拷贝自身可枚举属性
    • 属于浅拷贝(嵌套对象仍然共享引用)
  • 示例
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { a: 3, c: 4 };Object.assign(target, source1, source2);
console.log(target); // { a: 3, b: 2, c: 4 }(a被覆盖)
  • **应用:**常用于对象合并或创建对象副本。
// 创建浅拷贝
const copy = Object.assign({}, obj);
  1. 扩展运算符( … )
  • 功能:ES6 新增,用于对象的浅拷贝和合并(效果类似 <font style="color:rgb(0, 0, 0);">Object.assign</font>
  • 示例
// 浅拷贝
const copy = { ...obj };// 合并对象
const merged = { ...source1, ...source2 }; // { b: 2, a: 3, c: 4 }
对象属性的检查方法
  1. obj.hasOwnProperty(key)
  • 功能:判断某个属性是否为对象自身的属性(非继承)
  • 示例
const obj = { a: 1 };
console.log(obj.hasOwnProperty("a")); // true
console.log(obj.hasOwnProperty("toString")); // false(toString是继承属性)
  1. in 运算符
  • 功能:判断属性是否存在于对象中(包括继承的属性)
  • 示例
console.log("a" in obj); // true
console.log("toString" in obj); // true(继承自Object.prototype)
  1. Object.prototype.hasOwnProperty.call(obj, key)
  • 功能:安全地检查属性是否为自身属性(避免对象本身重写了 <font style="color:rgb(0, 0, 0);">hasOwnProperty</font> 方法)
  • 示例
const obj = { hasOwnProperty: () => false, a: 1 };
// 直接调用会出错,因为obj重写了hasOwnProperty
console.log(Object.prototype.hasOwnProperty.call(obj, "a")); // true
对象的冻结与密封方法
  1. Object.freeze(obj)
  • 功能:冻结对象,使其无法被修改.
  • 被冻结的对象
    • 不能添加新属性
    • 不能删除现有属性
    • 不能修改属性值
    • 不能修改属性的特性(如可枚举性)
  • 示例
const obj = { a: 1 };
Object.freeze(obj);obj.a = 2; // 无效(严格模式下会报错)
delete obj.a; // 无效
obj.b = 3; // 无效
console.log(obj); // { a: 1 }
  1. Object.seal(obj)
  • 功能:密封对象。
  • 与冻结的区别
    • 不能添加属性
    • 不能删除属性
    • 可以修改现有属性的值
  • 示例
const obj = { a: 1 };
Object.seal(obj);obj.a = 2; // 有效
delete obj.a; // 无效
obj.b = 3; // 无效
console.log(obj); // { a: 2 }
  1. 检查对象状态
  • <font style="color:rgb(0, 0, 0);">Object.isFrozen(obj)</font>:判断对象是否被冻结
  • <font style="color:rgb(0, 0, 0);">Object.isSealed(obj)</font>:判断对象是否被密封
  • <font style="color:rgb(0, 0, 0);">Object.isExtensible(obj)</font>:判断对象是否可扩展(能否添加新属性)
对象的原型操作方法

用于操作对象的原型链。

  1. Object.create(proto[, propertiesObject])
  • 功能:创建一个新对象,其原型指向指定的 <font style="color:rgb(0, 0, 0);">proto</font>
  • 示例
console.log(Object.getPrototypeOf(child) === parent); // true
  1. Object.getPrototypeOf(obj)
  • 功能:获取对象的原型(等同于 <font style="color:rgb(0, 0, 0);">obj.__proto__</font>,但更推荐使用)
  • 示例
const obj = { a: 1 };
Object.seal(obj);obj.a = 2; // 有效
delete obj.a; // 无效
obj.b = 3; // 无效
console.log(obj); // { a: 2 }
  1. Object.setPrototypeOf(obj, proto)
  • 功能:设置对象的原型(不推荐频繁使用,会影响性能)
  • 示例:
const newProto = { greet: () => "Hi" };
Object.setPrototypeOf(child, newProto);
console.log(child.greet()); // "Hi"
其他实用方法
  1. Object.defineProperty(obj, prop, descriptor)
  • 功能:精确设置对象属性的特性(如可写性、可枚举性等)
  • 示例
const obj = {};
Object.defineProperty(obj, "id", {value: 123,writable: false,      // 只读enumerable: false,    // 不可枚举configurable: false   // 不可删除/修改特性
});
  1. Object.is(value1, value2)
  • 功能:判断两个值是否严格相等(比 <font style="color:rgb(0, 0, 0);">===</font> 更严格,处理了特殊情况)
  • 示例
console.log(Object.is(NaN, NaN)); // true(=== 会返回false)
console.log(Object.is(+0, -0)); // false(=== 会返回true)

对象的原型与继承

JavaScript 基于原型(prototype)实现继承:

  • 每个对象都有 __proto__ 属性,指向其原型对象
  • 原型对象的属性和方法可被所有实例共享
// 构造函数
function Animal(name) {this.name = name;
}// 在原型上定义方法(所有实例共享)
Animal.prototype.eat = function() {console.log(`${this.name} is eating`);
};const dog = new Animal("Buddy");
dog.eat(); // "Buddy is eating"
console.log(dog.__proto__ === Animal.prototype); // true

注意事项

  1. 对象是引用类型:赋值时传递的是引用,而非值本身
const a = { x: 1 };
const b = a;
b.x = 2;
console.log(a.x); // 2(a和b指向同一对象)
  1. 属性名自动转换:对象的键总是字符串或 Symbol 类型
const obj = { 123: "number", true: "boolean" };
console.log(obj["123"]); // "number"(数字键被转为字符串)
  1. ES6 增强特性:支持计算属性名、简写属性、方法简写等
const key = "name";
const obj = {[key]: "Frank", // 计算属性名age: 30,sayHi() { ... } // 方法简写
};

三、深复制与浅复制

在 JavaScript 中,复制数据时需要区分浅复制(Shallow Copy)和深复制(Deep Copy),这两种复制方式的核心区别在于是否递归复制嵌套对象的内容。

浅复制(Shallow Copy)

浅复制只复制对象的顶层属性,对于嵌套的引用类型属性,仅复制其引用地址(原对象和复制对象会共享嵌套对象)。

适用场景
  • 复制简单对象(无嵌套引用类型)
  • 性能优先,不需要完全隔离嵌套对象时
实现方式
(1)对象扩展运算符 { ...obj }
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };// 顶层属性独立
shallowCopy.a = 100;
console.log(original.a); // 1(不受影响)// 嵌套对象共享引用
shallowCopy.b.c = 200;
console.log(original.b.c); // 200(原对象被修改)
(2)Object.assign() 方法
const shallowCopy = Object.assign({}, original);
// 特性同上:顶层属性独立,嵌套对象共享
(3)数组浅复制(slice()[...arr]
const arrOriginal = [1, [2, 3]];
const arrShallowCopy1 = arrOriginal.slice();
const arrShallowCopy2 = [...arrOriginal];// 顶层元素独立
arrShallowCopy1[0] = 100;
console.log(arrOriginal[0]); // 1(不受影响)// 嵌套数组共享引用
arrShallowCopy2[1][0] = 200;
console.log(arrOriginal[1][0]); // 200(原数组被修改)

深复制(Deep Copy)

深复制会递归复制对象的所有层级,包括嵌套的引用类型属性,最终得到一个与原对象完全独立的副本(两者修改互不影响)。

适用场景
  • 复制复杂嵌套对象(如多层级的对象/数组)
  • 需要完全隔离原对象和复制对象时(修改副本不影响原对象)
实现方式
(1)JSON.parse(JSON.stringify())(简单场景)

利用 JSON 序列化与反序列化实现深复制(有局限性):

const original = { a: 1, b: { c: 2 }, d: [3, 4] };
const deepCopy = JSON.parse(JSON.stringify(original));// 嵌套对象完全独立
deepCopy.b.c = 200;
deepCopy.d[0] = 300;
console.log(original.b.c); // 2(不受影响)
console.log(original.d[0]); // 3(不受影响)

局限性

  • 不能复制函数、Symbol、正则表达式等特殊类型
  • 不能处理循环引用(对象引用自身会报错)
  • 会丢失 undefinedNaNInfinity 等特殊值的精度
(2)递归实现深复制(通用方案)

手动编写递归函数,处理所有数据类型:

/*** 深复制函数 - 递归复制所有层级的属性* @param {any} value - 需要复制的值* @returns {any} 复制后的新值*/
function deepCopy(value) {// 1. 处理基本数据类型(直接返回值)if (typeof value !== 'object' || value === null) {return value;}// 2. 处理日期对象if (value instanceof Date) {return new Date(value);}// 3. 处理正则表达式if (value instanceof RegExp) {return new RegExp(value.source, value.flags);}// 4. 处理数组(创建新数组并递归复制元素)if (Array.isArray(value)) {return value.map(item => deepCopy(item));}// 5. 处理普通对象(创建新对象并递归复制属性)const copy = {};for (const key in value) {if (value.hasOwnProperty(key)) {copy[key] = deepCopy(value[key]);}}return copy;
}// 测试
const original = {a: 1,b: { c: 2 },d: [3, [4, 5]],e: new Date(),f: /pattern/g
};const copied = deepCopy(original);
copied.b.c = 200;
copied.d[1][0] = 400;console.log(original.b.c); // 2(不受影响)
console.log(original.d[1][0]); // 4(不受影响)
console.log(copied.e instanceof Date); // true(类型保留)
console.log(copied.f.flags); // "g"(正则属性保留)

浅复制与深复制的核心区别

特性浅复制深复制
复制层级仅复制顶层属性递归复制所有层级(包括嵌套对象)
嵌套对象关联性共享引用(修改会相互影响)完全独立(修改互不影响)
性能效率高(无需递归)效率较低(递归处理所有层级)
适用场景简单对象、无嵌套引用类型复杂嵌套对象、需要完全隔离
实现复杂度简单(内置方法即可)复杂(需处理多种数据类型)

使用建议

  1. 优先使用浅复制:当对象结构简单(无嵌套引用类型)时,浅复制更高效。
  2. **谨慎使用 **JSON.parse(JSON.stringify()):适合纯 JSON 数据(无函数、正则等特殊类型),不适合复杂对象。
  3. 复杂场景用递归深复制:需自己实现或使用成熟库(如 Lodash 的 _.cloneDeep())。
  4. 注意循环引用:若对象存在循环引用(如 obj.self = obj),需在深复制函数中特殊处理(如用 WeakMap 记录已复制对象)。

练习-对象与数组嵌套数据处理

练习1:扁平化嵌套数组

需求:将多层嵌套的数组转换为一维数组(如 [1, [2, [3, 4], 5]][1, 2, 3, 4, 5])。
提示:使用递归或 Array.flat() 方法(注意 flat(depth) 的参数控制深度)。

// 实现一个通用的数组扁平化函数
function flattenArray(arr) {// 你的代码
}// 测试用例
const nestedArr = [1, [2, [3, [4]], 5], 6];
console.log(flattenArray(nestedArr)); // [1, 2, 3, 4, 5, 6]

练习2:深层查找对象属性

需求:在嵌套对象中查找指定属性的值(如在 { a: { b: { c: 100 } } } 中查找 c 的值)。
提示:使用递归遍历对象的所有属性,遇到对象则继续深入。

// 在嵌套对象中查找指定属性
function findDeepProperty(obj, targetKey) {// 你的代码
}// 测试用例
const nestedObj = {x: 10,y: {z: 20,w: {v: 30,u: { t: 40 }}}
};
console.log(findDeepProperty(nestedObj, 't')); // 40
console.log(findDeepProperty(nestedObj, 'none')); // undefined

练习3:修改嵌套数组中的值

需求:将嵌套数组中所有偶数乘以2(如 [1, [2, [3, 4], 5]][1, [4, [3, 8], 5]])。
提示:递归遍历数组,遇到数组则继续处理,遇到数字则判断是否为偶数。

// 处理嵌套数组中的偶数
function processNestedEvenNumbers(arr) {// 你的代码
}// 测试用例
const testArr = [1, [2, [3, 4], 5], 6];
console.log(processNestedEvenNumbers(testArr)); // [1, [4, [3, 8], 5], 12]

练习4:统计嵌套对象中某属性的总和

需求:计算嵌套对象中所有 value 属性的总和(支持数组嵌套)。
示例数据

const data = {value: 10,children: [{ value: 20, children: [{ value: 30 }] },{ value: 40 }]
};
// 预期结果:10 + 20 + 30 + 40 = 100
// 计算所有value的总和
function sumNestedValues(data) {// 你的代码
}// 测试用例
console.log(sumNestedValues(data)); // 100

练习5:将嵌套数据转换为树形结构

需求:将扁平数组转换为嵌套树形结构(如菜单数据)。
示例数据

const flatData = [{ id: 1, name: '菜单1', parentId: 0 },{ id: 2, name: '菜单1-1', parentId: 1 },{ id: 3, name: '菜单1-2', parentId: 1 },{ id: 4, name: '菜单2', parentId: 0 },{ id: 5, name: '菜单2-1', parentId: 4 }
];
// 预期结果:
// [
//   { id: 1, name: '菜单1', children: [...] },
//   { id: 4, name: '菜单2', children: [...] }
// ]
// 扁平数组转树形结构
function buildTree(flatData) {// 你的代码
}// 测试用例
console.log(buildTree(flatData));

练习6:深层克隆带循环引用的对象

需求:实现一个深克隆函数,能处理循环引用(如 obj.self = obj)。
提示:使用 WeakMap 存储已克隆的对象,避免无限递归。

// 处理循环引用的深克隆
function deepCloneWithCycle(obj, map = new WeakMap()) {// 你的代码
}// 测试用例
const obj = { name: 'test' };
obj.self = obj; // 循环引用
const cloned = deepCloneWithCycle(obj);
console.log(cloned.self === cloned); // true(克隆后仍保持循环引用)

练习7:过滤嵌套数组中的元素

需求:过滤出嵌套数组中所有大于10的数字(保留原数组结构)。
示例[5, [15, [8, 20], 3], 12][[15, [, 20]], 12]

// 过滤嵌套数组中大于10的元素
function filterNestedNumbers(arr) {// 你的代码
}// 测试用例
const input = [5, [15, [8, 20], 3], 12];
console.log(filterNestedNumbers(input)); // [[15, [, 20]], 12]

参考答案

练习1:扁平化嵌套数组

// 方法1:递归实现
function flattenArray(arr) {return arr.reduce((acc, item) => {// 如果是数组则递归扁平化,否则直接添加到结果return acc.concat(Array.isArray(item) ? flattenArray(item) : item);}, []);
}// 方法2:使用ES6的flat方法(参数Infinity表示无限深度)
// function flattenArray(arr) {
//   return arr.flat(Infinity);
// }// 测试用例
const nestedArr = [1, [2, [3, [4]], 5], 6];
console.log(flattenArray(nestedArr)); // [1, 2, 3, 4, 5, 6]

练习2:深层查找对象属性

function findDeepProperty(obj, targetKey) {// 遍历当前对象的所有键for (const key in obj) {// 找到目标键,返回对应值if (key === targetKey) {return obj[key];}// 如果当前值是对象且非null,递归查找if (typeof obj[key] === 'object' && obj[key] !== null) {const result = findDeepProperty(obj[key], targetKey);// 找到结果后直接返回,停止递归if (result !== undefined) {return result;}}}// 未找到返回undefinedreturn undefined;
}// 测试用例
const nestedObj = {x: 10,y: {z: 20,w: {v: 30,u: { t: 40 }}}
};
console.log(findDeepProperty(nestedObj, 't')); // 40
console.log(findDeepProperty(nestedObj, 'none')); // undefined

练习3:修改嵌套数组中的值

function processNestedEvenNumbers(arr) {// 遍历数组元素,递归处理return arr.map(item => {// 如果是数组,递归处理子数组if (Array.isArray(item)) {return processNestedEvenNumbers(item);}// 如果是偶数,乘以2;否则保持原值return typeof item === 'number' && item % 2 === 0 ? item * 2 : item;});
}// 测试用例
const testArr = [1, [2, [3, 4], 5], 6];
console.log(processNestedEvenNumbers(testArr)); // [1, [4, [3, 8], 5], 12]

练习4:统计嵌套对象中某属性的总和

function sumNestedValues(data) {let total = 0;// 如果当前对象有value属性,累加if (data.hasOwnProperty('value')) {total += data.value;}// 如果有children数组,递归处理每个子元素if (data.children && Array.isArray(data.children)) {data.children.forEach(child => {total += sumNestedValues(child);});}return total;
}// 测试用例
const data = {value: 10,children: [{ value: 20, children: [{ value: 30 }] },{ value: 40 }]
};
console.log(sumNestedValues(data)); // 100

练习5:将嵌套数据转换为树形结构

function buildTree(flatData) {// 1. 用Map存储所有节点,方便通过id快速查找const nodeMap = new Map();flatData.forEach(item => {// 为每个节点初始化children数组nodeMap.set(item.id, { ...item, children: [] });});// 2. 构建树形结构const tree = [];flatData.forEach(item => {const currentNode = nodeMap.get(item.id);if (item.parentId === 0) {// parentId为0的是根节点,直接加入树tree.push(currentNode);} else {// 找到父节点,将当前节点加入父节点的childrenconst parentNode = nodeMap.get(item.parentId);if (parentNode) {parentNode.children.push(currentNode);}}});return tree;
}// 测试用例
const flatData = [{ id: 1, name: '菜单1', parentId: 0 },{ id: 2, name: '菜单1-1', parentId: 1 },{ id: 3, name: '菜单1-2', parentId: 1 },{ id: 4, name: '菜单2', parentId: 0 },{ id: 5, name: '菜单2-1', parentId: 4 }
];
console.log(buildTree(flatData));

练习6:深层克隆带循环引用的对象

function deepCloneWithCycle(obj, map = new WeakMap()) {// 处理基本数据类型和nullif (obj === null || typeof obj !== 'object') {return obj;}// 处理日期对象if (obj instanceof Date) {return new Date(obj);}// 处理正则对象if (obj instanceof RegExp) {return new RegExp(obj.source, obj.flags);}// 检查是否已克隆过(解决循环引用)if (map.has(obj)) {return map.get(obj);}// 处理数组和对象const clone = Array.isArray(obj) ? [] : {};// 存储克隆关系,避免循环引用导致的无限递归map.set(obj, clone);// 递归克隆属性Reflect.ownKeys(obj).forEach(key => {clone[key] = deepCloneWithCycle(obj[key], map);});return clone;
}// 测试用例
const obj = { name: 'test' };
obj.self = obj; // 循环引用
const cloned = deepCloneWithCycle(obj);
console.log(cloned.self === cloned); // true(保持循环引用)
console.log(cloned.name === obj.name); // true(属性值一致)

练习7:过滤嵌套数组中的元素

function filterNestedNumbers(arr) {return arr.reduce((acc, item) => {// 处理嵌套数组if (Array.isArray(item)) {const filteredChild = filterNestedNumbers(item);// 即使子数组过滤后为空,也保留数组结构acc.push(filteredChild);}// 处理数字:只保留大于10的else if (typeof item === 'number' && item > 10) {acc.push(item);}// 非数字或不满足条件的数字不加入结果(相当于过滤)return acc;}, []);
}// 测试用例
const input = [5, [15, [8, 20], 3], 12];
console.log(filterNestedNumbers(input)); // [[15, [ , 20]], 12]

练习-深复制与浅复制

练习1:识别浅复制的局限性

需求:创建一个包含嵌套对象的原始对象,使用浅复制方法复制,然后修改复制对象的嵌套属性,观察原始对象是否受影响。

// 1. 创建原始对象(包含嵌套对象)
const original = {name: "测试",details: {age: 20,score: 90}
};// 2. 使用浅复制方法(如扩展运算符)
const shallowCopy = { ...original };// 3. 修改复制对象的顶层属性
shallowCopy.name = "浅复制修改";// 4. 修改复制对象的嵌套属性
shallowCopy.details.age = 30;// 5. 观察原始对象的变化
console.log("原始对象name:", original.name); // 预期:"测试"(不受影响)
console.log("原始对象details.age:", original.details.age); // 预期:30(受影响)

问题:为什么原始对象的嵌套属性会被修改?
答案:浅复制只复制顶层属性,嵌套对象仍然共享引用,所以修改复制对象的嵌套属性会影响原始对象。

练习2:实现深复制解决嵌套问题

需求:对练习1的原始对象进行深复制,修改复制对象的嵌套属性,确保原始对象不受影响。

// 1. 实现深复制函数
function deepCopy(obj) {if (obj === null || typeof obj !== 'object') {return obj;}const copy = Array.isArray(obj) ? [] : {};for (const key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepCopy(obj[key]);}}return copy;
}// 2. 使用深复制
const deepCopied = deepCopy(original);// 3. 修改深复制对象的嵌套属性
deepCopied.details.age = 40;// 4. 观察原始对象
console.log("原始对象details.age:", original.details.age); // 预期:30(不受影响)
console.log("深复制对象details.age:", deepCopied.details.age); // 预期:40

练习3:对比不同复制方式的效果

需求:用多种方式复制同一对象,比较它们对嵌套数组的影响。

const data = {id: 1,items: [10, 20, 30]
};// 1. 浅复制 - Object.assign
const copy1 = Object.assign({}, data);// 2. 浅复制 - 扩展运算符
const copy2 = { ...data };// 3. 深复制 - 自定义函数
const copy3 = deepCopy(data);// 修改所有复制对象的嵌套数组
copy1.items.push(40);
copy2.items.push(50);
copy3.items.push(60);// 观察结果
console.log("原始对象items:", data.items); // 预期:[10, 20, 30, 40, 50]
console.log("copy1 items:", copy1.items);   // 预期:[10, 20, 30, 40, 50]
console.log("copy2 items:", copy2.items);   // 预期:[10, 20, 30, 40, 50]
console.log("copy3 items:", copy3.items);   // 预期:[10, 20, 30, 60]

练习4:处理特殊类型的深复制

需求:尝试复制包含函数、日期、正则等特殊类型的对象,观察普通深复制方法的局限性。

const specialObj = {func: () => console.log("函数"),date: new Date(2023, 0, 1),regex: /test/g,nested: { value: 100 }
};// 使用JSON方法复制(有局限性)
const jsonCopy = JSON.parse(JSON.stringify(specialObj));// 使用自定义深复制函数
const customDeepCopy = deepCopy(specialObj);// 观察结果
console.log("JSON复制的函数:", jsonCopy.func); // 预期:undefined(函数无法被JSON序列化)
console.log("自定义复制的函数:", typeof customDeepCopy.func); // 预期:"function"
console.log("JSON复制的日期:", jsonCopy.date instanceof Date); // 预期:false(变成字符串)
console.log("自定义复制的日期:", customDeepCopy.date instanceof Date); // 预期:true

问题:如何改进深复制函数以支持这些特殊类型?
提示:在深复制函数中添加对 DateRegExpFunction 等类型的特殊处理。

练习5:修复浅复制导致的bug

需求:下面的代码存在bug(修改购物车副本影响了原始购物车),请使用深复制修复。

// 原始购物车数据
const cart = {user: "张三",products: [{ id: 1, name: "商品1", quantity: 2 }]
};// 错误的复制方式(浅复制)
function addToCart(cart, product) {const newCart = { ...cart }; // 问题所在newCart.products.push(product);return newCart;
}// 添加商品
const newCart = addToCart(cart, { id: 2, name: "商品2", quantity: 1 });// 意外:原始购物车也被修改了
console.log("原始购物车商品数:", cart.products.length); // 预期:1,实际:2// 修复代码:使用深复制
// 请在此处修改addToCart函数

修复方案:将浅复制改为深复制,确保 products 数组不会被共享。

参考答案

练习1:识别浅复制的局限性

参考答案

// 1. 创建原始对象(包含嵌套对象)
const original = {name: "测试",details: {age: 20,score: 90}
};// 2. 浅复制(扩展运算符)
const shallowCopy = { ...original };// 3. 修改复制对象的顶层属性
shallowCopy.name = "浅复制修改";// 4. 修改复制对象的嵌套属性
shallowCopy.details.age = 30;// 5. 观察结果
console.log("原始对象name:", original.name); // 输出:"测试"(顶层属性独立)
console.log("原始对象details.age:", original.details.age); // 输出:30(嵌套属性共享引用)

关键解析
浅复制仅复制对象的顶层键值对,对于嵌套的 details 对象,复制的是引用地址(而非新对象)。因此,shallowCopy.detailsoriginal.details 指向同一个内存地址,修改其中一个会影响另一个。

练习2:实现深复制解决嵌套问题

参考答案

// 1. 完善的深复制函数(支持对象和数组)
function deepCopy(obj) {// 处理基本数据类型和nullif (obj === null || typeof obj !== "object") {return obj;}// 处理数组if (Array.isArray(obj)) {return obj.map(item => deepCopy(item));}// 处理对象const copy = {};for (const key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepCopy(obj[key]); // 递归复制嵌套属性}}return copy;
}// 2. 使用深复制
const deepCopied = deepCopy(original);// 3. 修改深复制对象的嵌套属性
deepCopied.details.age = 40;// 4. 观察结果
console.log("原始对象details.age:", original.details.age); // 输出:30(不受影响)
console.log("深复制对象details.age:", deepCopied.details.age); // 输出:40(独立修改)

关键解析
深复制通过递归遍历所有层级,对嵌套对象/数组重新创建新实例,彻底切断与原始对象的引用关联。因此,修改深复制对象的任何属性都不会影响原始对象。

练习3:对比不同复制方式的效果

参考答案

const data = {id: 1,items: [10, 20, 30]
};// 1. 浅复制(Object.assign)
const copy1 = Object.assign({}, data);// 2. 浅复制(扩展运算符)
const copy2 = { ...data };// 3. 深复制(自定义函数)
const copy3 = deepCopy(data);// 修改嵌套数组
copy1.items.push(40);
copy2.items.push(50);
copy3.items.push(60);// 观察结果
console.log("原始对象items:", data.items); // 输出:[10, 20, 30, 40, 50](受浅复制影响)
console.log("copy1 items:", copy1.items);   // 输出:[10, 20, 30, 40, 50](与原始共享数组)
console.log("copy2 items:", copy2.items);   // 输出:[10, 20, 30, 40, 50](与原始共享数组)
console.log("copy3 items:", copy3.items);   // 输出:[10, 20, 30, 60](独立数组)

关键解析

  • Object.assign 和扩展运算符都是浅复制,它们复制的 items 数组是引用,因此修改 copy1.itemscopy2.items 会影响原始对象。
  • 深复制的 copy3.items 是全新数组,与原始对象完全独立,修改不会相互影响。

练习4:处理特殊类型的深复制

参考答案

// 改进的深复制函数(支持特殊类型)
function deepCopySpecial(obj) {if (obj === null || typeof obj !== "object") {return obj;}// 处理日期if (obj instanceof Date) {return new Date(obj);}// 处理正则if (obj instanceof RegExp) {return new RegExp(obj.source, obj.flags);}// 处理数组if (Array.isArray(obj)) {return obj.map(item => deepCopySpecial(item));}// 处理普通对象const copy = {};for (const key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepCopySpecial(obj[key]);}}return copy;
}// 测试数据
const specialObj = {func: () => console.log("函数"),date: new Date(2023, 0, 1),regex: /test/g,nested: { value: 100 }
};// JSON方法复制(有局限)
const jsonCopy = JSON.parse(JSON.stringify(specialObj));// 自定义深复制(支持特殊类型)
const customDeepCopy = deepCopySpecial(specialObj);// 观察结果
console.log("JSON复制的函数:", jsonCopy.func); // 输出:undefined(JSON不支持函数)
console.log("自定义复制的函数:", typeof customDeepCopy.func); // 输出:"function"(保留函数)
console.log("JSON复制的日期类型:", jsonCopy.date instanceof Date); // 输出:false(变成字符串)
console.log("自定义复制的日期类型:", customDeepCopy.date instanceof Date); // 输出:true(保留日期类型)
console.log("JSON复制的正则flags:", jsonCopy.regex?.flags); // 输出:undefined(正则被转为空对象)
console.log("自定义复制的正则flags:", customDeepCopy.regex.flags); // 输出:"g"(保留正则属性)

关键解析

  • JSON.parse(JSON.stringify()) 无法处理函数、DateRegExp 等特殊类型(会丢失类型或值)。
  • 自定义深复制函数通过判断对象类型(instanceof),对特殊类型单独处理(如 new Date(obj) 复制日期),确保类型和值都被正确复制。

练习5:修复浅复制导致的bug

参考答案

// 原始购物车数据
const cart = {user: "张三",products: [{ id: 1, name: "商品1", quantity: 2 }]
};// 修复后的函数(使用深复制)
function addToCart(cart, product) {// 深复制整个购物车(包括products数组)const newCart = deepCopySpecial(cart);newCart.products.push(product); // 修改副本的数组,不影响原始数据return newCart;
}// 添加商品
const newCart = addToCart(cart, { id: 2, name: "商品2", quantity: 1 });// 观察结果
console.log("原始购物车商品数:", cart.products.length); // 输出:1(不受影响)
console.log("新购物车商品数:", newCart.products.length); // 输出:2(正确添加)

关键解析
原代码的 { ...cart } 是浅复制,newCart.productscart.products 共享同一个数组引用,导致修改副本时原始数据被意外改变。
修复方案通过深复制(deepCopySpecial)创建完全独立的 newCart,确保 products 数组修改仅影响副本。

总结

  1. 浅复制:仅复制顶层结构,嵌套的引用类型(对象/数组)共享引用,适合简单对象且无需隔离嵌套数据的场景。
  2. 深复制:递归复制所有层级,完全隔离原始数据,适合复杂嵌套对象,但性能开销更高。
  3. 实践建议
    • 简单场景用浅复制(扩展运算符/Object.assign)。
    • 复杂场景用成熟方案(如 Lodash 的 _.cloneDeep)或完善的自定义深复制函数(处理特殊类型)。
    • 避免用 JSON.parse(JSON.stringify()) 处理含特殊类型的数据。
http://www.dtcms.com/a/303568.html

相关文章:

  • 深入解析IPMI FRU规范:分区结构与字段标识详解
  • 10_opencv_分离颜色通道、多通道图像混合
  • Nuxt3 全栈作品【通用信息管理系统】修改密码
  • OpenLayers 综合案例-热力图
  • 在虚拟机ubuntu上修改framebuffer桌面不能显示图像
  • C++进阶—C++11
  • 5G 便携式多卡图传终端:移动作业的 “实时感知纽带”
  • 【unitrix】 6.19 Ord特质(ord.rs)
  • 【灰度实验】——图像预处理(OpenCV)
  • 2025年7月28日训练日志
  • 【三桥君】如何解决后端Agent和前端UI之间的交互问题?——解析AG-UI协议的神奇作用
  • 排水管网实时监测筑牢城市安全防线
  • 线程间-数据缓存机制(线程邮箱)
  • CDN架构全景图
  • STM32 usb HOST audio USB 音频设备 放音乐
  • springCloudAlibaba集成Dubbo
  • 【版本更新】火语言 0.9.94.0 更新
  • 虚拟面孔,真实革命
  • Product Hunt 每日热榜 | 2025-07-28
  • JAVA_EIGHTEEN_特殊文件
  • STM32——寄存器映射
  • LLaMA-Factory微调教程2:命令行sft微调
  • 【拓扑排序 缩点】P2272 [ZJOI2007] 最大半连通子图|省选-
  • 【跳跃游戏】
  • BUUCTF-MISC-[HBNIS2018]caesar1
  • Linux驱动22 --- RV1126 环境搭建设备树修改
  • 从零到一:我是如何用深度学习打造高性能书籍推荐系统的
  • mp核心功能
  • 零基础学习性能测试第九章:全链路追踪-项目实操
  • 猎板 PCB 控深槽工艺:5G 基站散热模块的关键支撑