对象是JavaScript中最重要的数据类型之一,是键值对的集合。与原始类型(string、number等)不同,对象是引用类型,变量存储的是指向内存中对象的引用而非实际数据。
最常用、最简洁的对象创建方式。
// 空对象 const empty = {}; // 带有属性的对象 const person = { firstName: "John", lastName: "Doe", age: 30, isEmployed: true, hobbies: ["reading", "swimming"], address: { street: "123 Main St", city: "New York" }, // 方法 greet: function() { return `Hello, I'm ${this.firstName}`; }, // ES6方法简写 sayGoodbye() { return "Goodbye!"; }, // 计算属性名 ["user" + "Id"]: 12345 };
属性名规则:
const obj = { normal: "value", "with-dash": "value", // 需要引号 "with space": "value", // 需要引号 "123": "numeric", // 数字属性名自动转字符串 "": "empty" // 空字符串也可以作为属性名 };
// 使用Object构造函数(不推荐) const person = new Object(); person.name = "Alice"; person.age = 25; // 与对象字面量等价 const person2 = { name: "Alice", age: 25 };
创建一个新对象,使用现有对象作为新对象的原型。
// 创建以null为原型的对象(无原型链) const dict = Object.create(null); dict.key = "value"; // 没有toString等方法 // 使用现有对象作为原型 const animal = { type: "animal", makeSound() { console.log("Some sound"); } }; const dog = Object.create(animal); dog.breed = "Golden Retriever"; dog.makeSound(); // "Some sound"(继承自animal) // 指定属性描述符 const person = Object.create(Object.prototype, { name: { value: "Alice", writable: true, enumerable: true, configurable: true }, age: { value: 25, writable: false // 只读 } });
// 定义构造函数 function Person(name, age) { this.name = name; this.age = age; this.greet = function() { return `Hello, I'm ${this.name}`; }; } // 使用new创建实例 const person1 = new Person("Alice", 25); const person2 = new Person("Bob", 30); console.log(person1.greet()); // "Hello, I'm Alice" console.log(person2.greet()); // "Hello, I'm Bob"
const person = { name: "Alice", "favorite color": "blue", 123: "numeric key" }; // 点符号(要求属性名是有效标识符) person.name; // "Alice" // person.favorite color; // SyntaxError // 方括号(可以使用任意表达式) person["name"]; // "Alice" person["favorite color"]; // "blue" person[123]; // "numeric key" person["123"]; // "numeric key"(数字自动转字符串) // 动态属性访问 const prop = "name"; person[prop]; // "Alice" // 计算属性名 let i = 0; const obj = { ["prop" + ++i]: "value1", ["prop" + ++i]: "value2" }; // obj: { prop1: "value1", prop2: "value2" }
const person = {}; // 添加属性 person.name = "Alice"; person["age"] = 25; // 修改属性 person.age = 26; // 使用Object.assign()批量添加/修改 Object.assign(person, { email: "alice@example.com", phone: "123-456-7890" }); // 使用展开运算符(ES2018) const updatedPerson = { ...person, age: 27, country: "USA" }; // 删除属性 delete person.phone; delete person["email"]; // delete返回true(即使属性不存在) const result = delete person.nonExistent; // true
const person = { name: "Alice", age: undefined }; // in运算符 - 检查自有属性和继承属性 "name" in person; // true "age" in person; // true(值为undefined但存在) "toString" in person; // true(继承自Object.prototype) // hasOwnProperty() - 只检查自有属性 person.hasOwnProperty("name"); // true person.hasOwnProperty("age"); // true person.hasOwnProperty("toString"); // false // Object.prototype.hasOwnProperty.call() - 更安全 Object.prototype.hasOwnProperty.call(person, "name"); // Object.hasOwn() - ES2022新方法 Object.hasOwn(person, "name"); // true // undefined检查(不可靠) person.name !== undefined; // true person.age !== undefined; // false(存在但值为undefined) person.gender !== undefined; // false(不存在) // 可选链操作符(ES2020) person?.address?.city; // undefined(不报错)
const person = { name: "Alice", age: 25, city: "New York" }; // 遍历所有可枚举属性(包括继承的) for (let key in person) { console.log(key, person[key]); } // 只遍历自有属性 for (let key in person) { if (person.hasOwnProperty(key)) { console.log(key, person[key]); } }
const person = { name: "Alice", age: 25, city: "New York" }; // Object.keys() - 返回属性名数组 Object.keys(person); // ["name", "age", "city"] // Object.values() - 返回属性值数组(ES2017) Object.values(person); // ["Alice", 25, "New York"] // Object.entries() - 返回[key, value]数组(ES2017) Object.entries(person); // [["name", "Alice"], ["age", 25], ["city", "New York"]] // 遍历 Object.entries(person).forEach(([key, value]) => { console.log(`${key}: ${value}`); }); // 从entries重建对象 const entries = [["a", 1], ["b", 2]]; const obj = Object.fromEntries(entries); // { a: 1, b: 2 }
const sym = Symbol("secret"); const obj = { normal: "value", [sym]: "symbol value" }; Object.keys(obj); // ["normal"] Object.getOwnPropertyNames(obj); // ["normal"] Object.getOwnPropertySymbols(obj); // [Symbol(secret)] // 获取所有属性键(包括Symbol) Reflect.ownKeys(obj); // ["normal", Symbol(secret)]
每个属性都有一个描述符对象,包含以下特性:
const person = { name: "Alice", age: 25 }; // 获取属性描述符 const descriptor = Object.getOwnPropertyDescriptor(person, "name"); console.log(descriptor); // { // value: "Alice", // writable: true, // enumerable: true, // configurable: true // } // 获取所有属性的描述符 const descriptors = Object.getOwnPropertyDescriptors(person); // 定义单个属性 Object.defineProperty(person, "id", { value: 12345, writable: false, // 只读 enumerable: false, // 不可枚举 configurable: false // 不可删除或重新配置 }); // 定义多个属性 Object.defineProperties(person, { email: { value: "alice@example.com", writable: true, enumerable: true }, createdAt: { value: new Date(), writable: false, enumerable: false } });
const person = { firstName: "Alice", lastName: "Smith", // getter get fullName() { return `${this.firstName} ${this.lastName}`; }, // setter set fullName(value) { [this.firstName, this.lastName] = value.split(" "); } }; console.log(person.fullName); // "Alice Smith" person.fullName = "Bob Jones"; console.log(person.firstName); // "Bob" console.log(person.lastName); // "Jones" // 使用defineProperty定义访问器 const user = { _age: 25 }; Object.defineProperty(user, "age", { get() { return this._age; }, set(value) { if (value < 0 || value > 150) { throw new Error("Invalid age"); } this._age = value; }, enumerable: true, configurable: true }); user.age = 30; // ✓ // user.age = -5; // ✗ Error: Invalid age
const calculator = { // 传统方式 add: function(a, b) { return a + b; }, // ES6方法简写(推荐) subtract(a, b) { return a - b; }, // 箭头函数(注意this绑定) multiply: (a, b) => a * b, // 使用this value: 0, increment() { this.value++; return this; }, decrement() { this.value--; return this; }, // 链式调用 addValue(n) { this.value += n; return this; } }; // 链式调用示例 calculator.increment().addValue(5).decrement(); console.log(calculator.value); // 5
const person = { name: "Alice", sayHi() { console.log(`Hi, I'm ${this.name}`); }, // 箭头函数不绑定自己的this sayBye: () => { console.log(`Bye from ${this.name}`); // this是外层作用域的this }, // 嵌套函数中的this问题 delayedHi() { // 方式1:保存this const self = this; setTimeout(function() { console.log(`Hi, I'm ${self.name}`); }, 100); // 方式2:使用bind setTimeout(function() { console.log(`Hi, I'm ${this.name}`); }.bind(this), 100); // 方式3:使用箭头函数(推荐) setTimeout(() => { console.log(`Hi, I'm ${this.name}`); }, 100); } }; // 方法调用 vs 函数调用 person.sayHi(); // this = person const greet = person.sayHi; greet(); // this = undefined(严格模式)或全局对象
const original = { name: "Alice", address: { city: "New York" } }; // 方式1:展开运算符 const copy1 = { ...original }; // 方式2:Object.assign() const copy2 = Object.assign({}, original); // 测试 console.log(copy1 === original); // false(不同对象) console.log(copy1.address === original.address); // true(相同引用!) copy1.address.city = "Boston"; console.log(original.address.city); // "Boston"(原对象也被修改)
const original = { name: "Alice", address: { city: "New York", zip: "10001" }, hobbies: ["reading", "swimming"], date: new Date(), fn: function() {} }; // 方式1:JSON方法(有限制) const jsonCopy = JSON.parse(JSON.stringify(original)); // 限制:丢失函数、Date变为字符串、丢失undefined、循环引用报错 // 方式2:递归深拷贝 function deepClone(obj, hash = new WeakMap()) { // 处理null或基本类型 if (obj === null || typeof obj !== 'object') { return obj; } // 处理Date if (obj instanceof Date) { return new Date(obj); } // 处理RegExp if (obj instanceof RegExp) { return new RegExp(obj); } // 处理循环引用 if (hash.has(obj)) { return hash.get(obj); } // 处理数组 if (Array.isArray(obj)) { const clone = []; hash.set(obj, clone); for (let i = 0; i < obj.length; i++) { clone[i] = deepClone(obj[i], hash); } return clone; } // 处理对象 const clone = Object.create(Object.getPrototypeOf(obj)); hash.set(obj, clone); const descriptors = Object.getOwnPropertyDescriptors(obj); for (let key of Reflect.ownKeys(descriptors)) { const descriptor = descriptors[key]; if (descriptor.value !== undefined) { descriptor.value = deepClone(descriptor.value, hash); } Object.defineProperty(clone, key, descriptor); } return clone; } // 方式3:使用库(推荐生产环境) // _.cloneDeep(obj) // Lodash // structuredClone(obj) // 现代浏览器和Node.js 17+
// 引用比较 const a = { x: 1 }; const b = { x: 1 }; const c = a; a === b; // false(不同引用) a === c; // true(相同引用) // 浅比较(比较第一层属性) function shallowEqual(obj1, obj2) { if (obj1 === obj2) return true; const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (let key of keys1) { if (obj1[key] !== obj2[key]) return false; } return true; } // 深比较 function deepEqual(obj1, obj2) { if (obj1 === obj2) return true; if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) { return false; } const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (let key of keys1) { if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { return false; } } return true; } console.log(deepEqual({ a: { b: 1 } }, { a: { b: 1 } })); // true
// 1. 创建一个person对象,包含name、age、birthDate属性 // birthDate使用getter计算得出 // 2. 实现一个函数mergeObjects(obj1, obj2),合并两个对象 // 如果属性冲突,obj2的属性优先 // 3. 实现一个函数pick(obj, keys),从对象中选取指定属性 // pick({ a: 1, b: 2, c: 3 }, ['a', 'c']) => { a: 1, c: 3 } // 4. 实现一个函数omit(obj, keys),从对象中排除指定属性 // omit({ a: 1, b: 2, c: 3 }, ['b']) => { a: 1, c: 3 }
// 1. 创建一个只读对象,所有属性都不可修改、删除或重新配置 // 2. 实现一个函数freezeDeep(obj),深度冻结对象 // 3. 使用getter/setter实现一个计数器对象 // 只能通过increment()和decrement()修改值 // 通过value获取当前值
// 1. 测试JSON.parse(JSON.stringify())的局限性 // 2. 完善deepClone函数,支持Map和Set // 3. 实现一个函数isDeepEqual(obj1, obj2),支持循环引用检测
练习1答案:
// 1. person对象 const person = { name: "Alice", age: 25, get birthDate() { const year = new Date().getFullYear() - this.age; return new Date(year, 0, 1); } }; // 2. mergeObjects function mergeObjects(obj1, obj2) { return { ...obj1, ...obj2 }; // 或 Object.assign({}, obj1, obj2); } // 深合并 function deepMerge(obj1, obj2) { const result = { ...obj1 }; for (let key in obj2) { if (obj2[key] instanceof Object && key in obj1) { result[key] = deepMerge(obj1[key], obj2[key]); } else { result[key] = obj2[key]; } } return result; } // 3. pick function pick(obj, keys) { return keys.reduce((result, key) => { if (key in obj) { result[key] = obj[key]; } return result; }, {}); } // 4. omit function omit(obj, keys) { return Object.keys(obj) .filter(key => !keys.includes(key)) .reduce((result, key) => { result[key] = obj[key]; return result; }, {}); }
练习2答案:
// 1. 只读对象 function createReadonlyObject(obj) { const result = {}; for (let key of Object.keys(obj)) { Object.defineProperty(result, key, { value: obj[key], writable: false, enumerable: true, configurable: false }); } return result; } // 或使用Object.freeze() const readonly = Object.freeze({ a: 1, b: 2 }); // 2. 深度冻结 function freezeDeep(obj) { Object.freeze(obj); Object.keys(obj).forEach(key => { if (obj[key] && typeof obj[key] === 'object') { freezeDeep(obj[key]); } }); return obj; } // 3. 计数器 function createCounter() { let count = 0; return { get value() { return count; }, increment() { count++; return this; }, decrement() { count--; return this; } }; }
练习3答案:
// 1. JSON方法局限性测试 const obj = { fn: () => {}, date: new Date(), undef: undefined, inf: Infinity, nan: NaN, sym: Symbol('test'), circ: null }; obj.circ = obj; // 循环引用 // JSON.stringify(obj); // TypeError: Converting circular structure to JSON // 2. 支持Map和Set的deepClone function deepClone(obj, hash = new WeakMap()) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (obj instanceof Map) { const clone = new Map(); hash.set(obj, clone); obj.forEach((value, key) => { clone.set(deepClone(key, hash), deepClone(value, hash)); }); return clone; } if (obj instanceof Set) { const clone = new Set(); hash.set(obj, clone); obj.forEach(value => { clone.add(deepClone(value, hash)); }); return clone; } if (hash.has(obj)) return hash.get(obj); const clone = Array.isArray(obj) ? [] : {}; hash.set(obj, clone); for (let key of Reflect.ownKeys(obj)) { clone[key] = deepClone(obj[key], hash); } return clone; }
本章深入学习了JavaScript对象:
对象是JavaScript的核心,理解对象的特性和行为对于掌握这门语言至关重要。
下一章将学习数组,这是JavaScript中最常用的数据结构之一。