Skip to content

对象基础

对象是 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 = 30
const 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()) // 42

Object.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 = null
console.log(arr?.[0]) // undefined
// 配合空值合并
const city = user?.address?.city ?? '未知' // "北京"

访问不存在的属性#

const obj = { name: '张三' }
// 返回 undefined
console.log(obj.age) // undefined
console.log(obj.address?.city) // undefined
// in 操作符检查属性是否存在
console.log('name' in obj) // true
console.log('age' in obj) // false
console.log('toString' in obj) // true(继承的属性)
// hasOwnProperty 只检查自身属性
console.log(obj.hasOwnProperty('name')) // true
console.log(obj.hasOwnProperty('toString')) // false
// Object.hasOwn(ES2022,推荐)
console.log(Object.hasOwn(obj, 'name')) // true

属性操作#

添加和修改属性#

const person = { name: '张三' }
// 添加属性
person.age = 25
person['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.age
console.log(person) // { name: "张三", city: "北京" }
// delete 返回 true/false
console.log(delete person.name) // true
console.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 } = person
console.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) // 1
console.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 = 25
console.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) // 3
calculator.subtract(5, 3) // 2
calculator.multiply(2, 3) // 6

this 关键字#

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.greet
greet() // "你好,我是undefined"
// 解决方案1:bind
const 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 + forEach
Object.keys(person).forEach((key) => {
console.log(key, person[key])
})
// Object.entries + for...of
for (const [key, value] of Object.entries(person)) {
console.log(key, value)
}
// Object.entries + forEach
Object.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.assign
const copy2 = Object.assign({}, original)
// 方式3:Object.fromEntries
const copy3 = Object.fromEntries(Object.entries(original))
// 浅拷贝的问题:嵌套对象共享引用
copy1.info.age = 30
console.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)) // true
console.log(Object.isSealed(person)) // true
console.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/sealfreeze 更严格

核心要点