对象是 JavaScript 的核心数据结构,几乎一切皆对象。理解对象是掌握 JavaScript 的关键。
🎯 创建对象#
对象字面量#
// 最常用的方式const person = { name: '张三', age: 25, city: '北京',}
// 空对象const empty = {}
// 动态属性名(ES6)const key = 'score'const student = { name: '李四', [key]: 90, ['is' + 'Active']: true,}console.log(student) // { name: "李四", score: 90, isActive: true }
// 简写语法(ES6)const name = '王五'const age = 30const user = { name, age } // 等价于 { name: name, age: age }Object 构造函数#
// 基本使用const obj = new Object()obj.name = '张三'
// 传入对象会直接返回const original = { a: 1 }const same = new Object(original)console.log(same === original) // true
// 传入原始值会包装const numObj = new Object(42)console.log(typeof numObj) // "object"console.log(numObj.valueOf()) // 42Object.create()#
// 创建以指定对象为原型的对象const proto = { greet() { return `你好,${this.name}` },}
const person = Object.create(proto)person.name = '张三'console.log(person.greet()) // "你好,张三"
// 创建没有原型的纯净对象const pure = Object.create(null)console.log(pure.toString) // undefined(没有继承任何方法)
// 同时定义属性const user = Object.create(proto, { name: { value: '李四', writable: true, enumerable: true, },})属性访问#
点表示法和括号表示法#
const person = { 'name': '张三', 'full name': '张三丰', 'age': 25,}
// 点表示法:属性名是有效标识符时使用console.log(person.name) // "张三"console.log(person.age) // 25
// 括号表示法:属性名包含特殊字符或动态获取console.log(person['name']) // "张三"console.log(person['full name']) // "张三丰"
// 动态属性名const key = 'age'console.log(person[key]) // 25
// 计算属性名const prefix = 'user'console.log(person[prefix + 'name'] ?? '不存在') // "不存在"可选链操作符#
const user = { name: '张三', address: { city: '北京', },}
// 传统写法const zip = user && user.address && user.address.zip
// 可选链(ES2020)const zip2 = user?.address?.zip // undefined
// 方法调用const result = user.getName?.() // undefined(不报错)
// 数组访问const arr = nullconsole.log(arr?.[0]) // undefined
// 配合空值合并const city = user?.address?.city ?? '未知' // "北京"访问不存在的属性#
const obj = { name: '张三' }
// 返回 undefinedconsole.log(obj.age) // undefinedconsole.log(obj.address?.city) // undefined
// in 操作符检查属性是否存在console.log('name' in obj) // trueconsole.log('age' in obj) // falseconsole.log('toString' in obj) // true(继承的属性)
// hasOwnProperty 只检查自身属性console.log(obj.hasOwnProperty('name')) // trueconsole.log(obj.hasOwnProperty('toString')) // false
// Object.hasOwn(ES2022,推荐)console.log(Object.hasOwn(obj, 'name')) // true属性操作#
添加和修改属性#
const person = { name: '张三' }
// 添加属性person.age = 25person['city'] = '北京'
// 修改属性person.name = '李四'
// 批量添加Object.assign(person, { email: 'test@example.com', phone: '123456',})
// 展开运算符(创建新对象)const updated = { ...person, name: '王五', country: '中国',}删除属性#
const person = { name: '张三', age: 25, city: '北京',}
// delete 操作符delete person.ageconsole.log(person) // { name: "张三", city: "北京" }
// delete 返回 true/falseconsole.log(delete person.name) // trueconsole.log(delete person.notExist) // true(不存在的也返回 true)
// 不能删除不可配置的属性const obj = {}Object.defineProperty(obj, 'fixed', { value: 42, configurable: false,})console.log(delete obj.fixed) // false
// 解构实现删除(创建新对象)const { city, ...rest } = personconsole.log(rest) // { name: "张三" }属性描述符#
const person = { name: '张三' }
// 获取属性描述符const descriptor = Object.getOwnPropertyDescriptor(person, 'name')console.log(descriptor)// {// value: "张三",// writable: true, // 可修改// enumerable: true, // 可枚举// configurable: true // 可配置/删除// }
// 定义单个属性Object.defineProperty(person, 'id', { value: 1, writable: false, // 不可修改 enumerable: false, // 不可枚举 configurable: false, // 不可删除/重新配置})
person.id = 2 // 静默失败(严格模式报错)console.log(person.id) // 1console.log(Object.keys(person)) // ["name"](id 不出现)
// 定义多个属性Object.defineProperties(person, { age: { value: 25, writable: true }, city: { value: '北京', enumerable: true },})getter 和 setter#
const person = { firstName: '三', lastName: '张',
// getter get fullName() { return this.lastName + this.firstName },
// setter set fullName(value) { const parts = value.split('') this.lastName = parts[0] this.firstName = parts.slice(1).join('') },}
console.log(person.fullName) // "张三"person.fullName = '李四'console.log(person.firstName) // "四"console.log(person.lastName) // "李"
// 使用 defineProperty 定义const user = { _age: 0 }Object.defineProperty(user, 'age', { get() { return this._age }, set(value) { if (value < 0) throw new Error('年龄不能为负') this._age = value },})
user.age = 25console.log(user.age) // 25// user.age = -1 // Error: 年龄不能为负方法#
定义方法#
const calculator = { // 传统方式 add: function (a, b) { return a + b },
// 简写方式(ES6) subtract(a, b) { return a - b },
// 箭头函数(注意 this) multiply: (a, b) => a * b,}
calculator.add(1, 2) // 3calculator.subtract(5, 3) // 2calculator.multiply(2, 3) // 6this 关键字#
const person = { name: '张三',
greet() { console.log(`你好,我是${this.name}`) },
// 箭头函数没有自己的 this greetArrow: () => { console.log(`你好,我是${this.name}`) // this 不是 person },}
person.greet() // "你好,我是张三"person.greetArrow() // "你好,我是undefined"
// this 丢失问题const greet = person.greetgreet() // "你好,我是undefined"
// 解决方案1:bindconst boundGreet = person.greet.bind(person)boundGreet() // "你好,我是张三"
// 解决方案2:箭头函数包装const wrappedGreet = () => person.greet()wrappedGreet() // "你好,我是张三"对象遍历#
获取键/值/条目#
const person = { name: '张三', age: 25, city: '北京',}
// Object.keys() - 获取所有可枚举的自身属性键console.log(Object.keys(person)) // ["name", "age", "city"]
// Object.values() - 获取所有可枚举的自身属性值console.log(Object.values(person)) // ["张三", 25, "北京"]
// Object.entries() - 获取键值对数组console.log(Object.entries(person))// [["name", "张三"], ["age", 25], ["city", "北京"]]遍历方式#
const person = { name: '张三', age: 25 }
// for...in(包含继承的可枚举属性)for (const key in person) { console.log(key, person[key])}
// Object.keys + forEachObject.keys(person).forEach((key) => { console.log(key, person[key])})
// Object.entries + for...offor (const [key, value] of Object.entries(person)) { console.log(key, value)}
// Object.entries + forEachObject.entries(person).forEach(([key, value]) => { console.log(key, value)})遍历顺序#
const obj = { 2: 'two', 1: 'one', b: 'bee', a: 'ay',}
// 整数键按数值排序,其他按添加顺序console.log(Object.keys(obj)) // ["1", "2", "b", "a"]对象复制#
浅拷贝#
const original = { name: '张三', info: { age: 25 },}
// 方式1:展开运算符const copy1 = { ...original }
// 方式2:Object.assignconst copy2 = Object.assign({}, original)
// 方式3:Object.fromEntriesconst copy3 = Object.fromEntries(Object.entries(original))
// 浅拷贝的问题:嵌套对象共享引用copy1.info.age = 30console.log(original.info.age) // 30(被影响了!)深拷贝#
const original = { name: '张三', info: { age: 25 }, hobbies: ['读书', '运动'],}
// 方式1:JSON(有限制)const deep1 = JSON.parse(JSON.stringify(original))// 🔶 不支持:函数、undefined、Symbol、循环引用、Date、RegExp 等
// 方式2:structuredClone(现代浏览器)const deep2 = structuredClone(original)// ✅ 支持更多类型,支持循环引用
// 方式3:手动递归function deepClone(obj, hash = new WeakMap()) { if (obj === null || typeof obj !== 'object') return obj if (hash.has(obj)) return hash.get(obj) // 处理循环引用
const clone = Array.isArray(obj) ? [] : {} hash.set(obj, clone)
for (const key of Object.keys(obj)) { clone[key] = deepClone(obj[key], hash) } return clone}对象合并#
const defaults = { host: 'localhost', port: 3000, debug: false,}
const config = { port: 8080, debug: true,}
// Object.assign(修改第一个参数)const merged1 = Object.assign({}, defaults, config)console.log(merged1) // { host: "localhost", port: 8080, debug: true }
// 展开运算符(推荐)const merged2 = { ...defaults, ...config }
// 深度合并���要自定义function deepMerge(target, source) { for (const key of Object.keys(source)) { if ( source[key] instanceof Object && key in target && target[key] instanceof Object ) { deepMerge(target[key], source[key]) } else { target[key] = source[key] } } return target}对象冻结与密封#
Object.freeze#
const config = { apiUrl: 'https://api.example.com', timeout: 5000,}
Object.freeze(config)
config.apiUrl = 'http://other.com' // 静默失败config.newProp = 'value' // 静默失败delete config.timeout // 静默失败
console.log(config.apiUrl) // "https://api.example.com"
// 🔶 freeze 是浅冻结const nested = { outer: { inner: 'value', },}Object.freeze(nested)nested.outer.inner = 'changed' // 可以修改!
// 深冻结function deepFreeze(obj) { Object.keys(obj).forEach((key) => { if (typeof obj[key] === 'object' && obj[key] !== null) { deepFreeze(obj[key]) } }) return Object.freeze(obj)}Object.seal#
const person = { name: '张三', age: 25 }
Object.seal(person)
person.name = '李四' // ✅ 可以修改现有属性person.city = '北京' // ❌ 不能添加新属性delete person.age // ❌ 不能删除属性
console.log(person) // { name: "李四", age: 25 }
// 检查状态console.log(Object.isFrozen(config)) // trueconsole.log(Object.isSealed(person)) // trueconsole.log(Object.isExtensible(person)) // false对象比较#
// 引用比较const obj1 = { a: 1 }const obj2 = { a: 1 }const obj3 = obj1
console.log(obj1 === obj2) // false(不同对象)console.log(obj1 === obj3) // true(同一引用)
// 浅比较function shallowEqual(obj1, obj2) { const keys1 = Object.keys(obj1) const keys2 = Object.keys(obj2) if (keys1.length !== keys2.length) return false return keys1.every((key) => obj1[key] === obj2[key])}
// 深比较function deepEqual(obj1, obj2) { if (obj1 === obj2) return true if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false if (obj1 === null || obj2 === null) return false
const keys1 = Object.keys(obj1) const keys2 = Object.keys(obj2) if (keys1.length !== keys2.length) return false
return keys1.every((key) => deepEqual(obj1[key], obj2[key]))}常用工具方法#
// 检查是否为空对象function isEmpty(obj) { return Object.keys(obj).length === 0}
// 从对象中选取部分属性function pick(obj, keys) { return keys.reduce((result, key) => { if (key in obj) result[key] = obj[key] return result }, {})}
const user = { name: '张三', age: 25, email: 'test@example.com' }pick(user, ['name', 'email']) // { name: "张三", email: "test@example.com" }
// 从对象中排除部分属性function omit(obj, keys) { const result = { ...obj } keys.forEach((key) => delete result[key]) return result}
omit(user, ['age']) // { name: "张三", email: "test@example.com" }
// 对象转查询字符串function toQueryString(obj) { return Object.entries(obj) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&')}
toQueryString({ name: '张三', age: 25 }) // "name=%E5%BC%A0%E4%B8%89&age=25"总结#
| 操作 | 方法/语法 | 说明 |
|---|---|---|
| 创建 | {}、Object.create() | 字面量最常用 |
| 访问 | . [] ?. | 可选链处理 null/undefined |
| 遍历 | Object.keys/values/entries | 获取键/值/条目 |
| 复制 | {...obj}、structuredClone | 注意深浅拷贝区别 |
| 合并 | Object.assign、{...a, ...b} | 后者覆盖前者 |
| 冻结 | Object.freeze/seal | freeze 更严格 |
核心要点:
- 对象是引用类型,比较的是引用而不是值
- 用
?.安全访问嵌套属性 - 用
Object.hasOwn()检查自身属性 - 修改对象时注意是否需要深拷贝