JavaScript 对象完全指南
深入理解 JavaScript 对象的方方面面:从基础操作到原型继承,从 ES6 类到高级技巧
第一章:对象基础
1.1 什么是对象?
在 JavaScript 中,对象是一组键值对的集合,是引用数据类型。对象是 JavaScript 的核心,几乎所有东西都是对象(或最终继承自对象)。
javascript
// 对象的类比理解
// 现实中的对象:一个人有姓名、年龄、性别等属性,有说话、走路等行为
// JavaScript 中的对象
const person = {
// 属性(Property):描述对象的特征
name: '张三',
age: 25,
gender: '男',
// 方法(Method):描述对象的行为
sayHello: function() {
console.log('你好,我是' + this.name);
},
walk: function() {
console.log(this.name + '在走路');
}
};
// 使用对象
console.log(person.name); // '张三'
person.sayHello(); // '你好,我是张三'
对象的特点:
- 对象是无序的键值对集合
- 键(key)是字符串或 Symbol,值(value)可以是任意类型
- 对象是引用类型,赋值时传递的是引用而非副本
1.2 创建对象的多种方式
1.2.1 对象字面量(最常用)
javascript
// 基本语法
const obj = {};
// 带属性的对象
const person = {
name: '张三',
age: 25,
isStudent: false
};
// 属性值可以是任意类型
const complexObj = {
name: '李四',
age: 30,
hobbies: ['读书', '游泳', '编程'],
address: {
city: '北京',
district: '海淀区',
street: '中关村大街'
},
isActive: true,
birthDate: new Date('1994-01-01'),
sayHi: function() {
console.log('Hi!');
},
// ES6 简写方法
greet() {
console.log('Hello!');
}
};
// 使用特殊字符作为键(需要用引号)
const specialKey = {
'first-name': '张',
'last-name': '三',
'2024': '年份',
'': '空字符串键',
' ': '空格键'
};
// 访问特殊键
console.log(specialKey['first-name']);
console.log(specialKey['2024']);
1.2.2 new Object()
javascript
// 使用构造函数创建对象
const person = new Object();
// 添加属性
person.name = '张三';
person.age = 25;
person.sayHello = function() {
console.log('Hello, ' + this.name);
};
// 与字面量的区别
const obj1 = {}; // 字面量,推荐
const obj2 = new Object(); // 构造函数
const obj3 = Object.create(null); // 创建无原型的对象
// 性能:字面量更快,代码更清晰
1.2.3 Object.create()
javascript
// 创建一个新对象,使用现有对象作为新对象的原型
const personPrototype = {
greet() {
console.log('Hello, I am ' + this.name);
}
};
const person = Object.create(personPrototype);
person.name = '张三';
person.greet(); // 'Hello, I am 张三'
// 指定属性描述符
const person2 = Object.create(personPrototype, {
name: {
value: '李四',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 25,
writable: false, // 只读
enumerable: true,
configurable: true
}
});
// 创建纯净对象(没有原型)
const pureObject = Object.create(null);
console.log(pureObject.toString); // undefined(没有继承 Object.prototype)
// 用途:作为字典/Map 使用,避免原型链干扰
const dict = Object.create(null);
dict['key'] = 'value';
// 不需要担心 'toString'、'constructor' 等属性
1.2.4 工厂函数
javascript
// 工厂函数:返回对象的函数
function createPerson(name, age) {
return {
name: name,
age: age,
greet() {
console.log('Hello, I am ' + this.name);
}
};
}
const person1 = createPerson('张三', 25);
const person2 = createPerson('李四', 30);
person1.greet(); // 'Hello, I am 张三'
person2.greet(); // 'Hello, I am 李四'
// ES6 简写
function createPersonES6(name, age) {
return {
name, // 属性简写:name: name
age,
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
}
// 工厂函数的问题:每个对象都有独立的函数副本
console.log(person1.greet === person2.greet); // false(浪费内存)
1.2.5 构造函数
javascript
// 构造函数:使用 new 关键字创建对象
function Person(name, age) {
// this 指向新创建的对象
this.name = name;
this.age = age;
// 不推荐在构造函数中定义方法(每个实例都有副本)
// this.greet = function() {
// console.log('Hello, I am ' + this.name);
// };
}
// 在原型上定义方法(所有实例共享)
Person.prototype.greet = function() {
console.log('Hello, I am ' + this.name);
};
// 创建实例
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);
person1.greet(); // 'Hello, I am 张三'
person2.greet(); // 'Hello, I am 李四'
// 验证共享
console.log(person1.greet === person2.greet); // true(共享方法)
// 构造函数的执行过程
// 1. 创建新对象
// 2. 将 this 绑定到这个新对象
// 3. 执行构造函数代码
// 4. 返回这个新对象(除非显式返回其他对象)
// 构造函数的返回值
function ReturnObject() {
this.name = 'test';
return { type: '显式返回的对象' };
}
function ReturnPrimitive() {
this.name = 'test';
return 123; // 被忽略
}
console.log(new ReturnObject()); // { type: '显式返回的对象' }
console.log(new ReturnPrimitive()); // { name: 'test' }
1.2.6 ES6 Class(语法糖)
javascript
// ES6 Class 是构造函数的语法糖
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 方法自动添加到原型
greet() {
console.log('Hello, I am ' + this.name);
}
// 静态方法
static isAdult(age) {
return age >= 18;
}
// getter
get info() {
return `${this.name}, ${this.age}岁`;
}
// setter
set newName(name) {
this.name = name;
}
}
const person = new Person('张三', 25);
person.greet(); // 'Hello, I am 张三'
console.log(person.info); // '张三, 25岁'
// 静态方法
console.log(Person.isAdult(20)); // true
// Class 本质上是函数
console.log(typeof Person); // 'function'
console.log(Person.prototype.greet); // [Function: greet]
1.3 对象的属性操作
1.3.1 访问属性
javascript
const person = {
name: '张三',
age: 25,
'first-name': '张',
'last-name': '三'
};
// 点号访问(推荐,简洁)
console.log(person.name); // '张三'
console.log(person.age); // 25
// 方括号访问(用于特殊键名或动态键)
console.log(person['name']); // '张三'
console.log(person['first-name']); // '张'(特殊字符必须用方括号)
// 动态属性访问
const key = 'name';
console.log(person[key]); // '张三'
// 表达式作为键
const prefix = 'first';
console.log(person[prefix + '-name']); // '张'
// 访问不存在的属性
console.log(person.gender); // undefined(不会报错)
// 可选链操作符(ES2020)
const user = {
profile: {
name: '张三'
}
};
console.log(user.profile?.name); // '张三'
console.log(user.settings?.theme); // undefined(不会报错)
console.log(user?.profile?.name); // '张三'
// 对比:不使用可选链
// console.log(user.settings.theme); // ❌ 报错:Cannot read property 'theme' of undefined
1.3.2 添加和修改属性
javascript
const person = {
name: '张三'
};
// 添加新属性
person.age = 25;
person['gender'] = '男';
// 修改现有属性
person.name = '李四';
person['age'] = 30;
// 动态添加属性
const propName = 'email';
person[propName] = 'zhangsan@example.com';
// 使用 Object.defineProperty(精确控制属性特性)
Object.defineProperty(person, 'id', {
value: 12345,
writable: false, // 不可修改
enumerable: false, // 不可枚举
configurable: false // 不可删除或修改特性
});
console.log(person.id); // 12345
person.id = 99999; // 静默失败(严格模式下报错)
console.log(person.id); // 12345(仍然是 12345)
// 批量定义属性
Object.defineProperties(person, {
phone: {
value: '13800138000',
writable: true,
enumerable: true
},
address: {
value: '北京市',
writable: true,
enumerable: true
}
});
1.3.3 删除属性
javascript
const person = {
name: '张三',
age: 25,
password: 'secret123'
};
// delete 操作符
delete person.age;
console.log(person.age); // undefined
console.log('age' in person); // false
// 删除方括号访问的属性
delete person['password'];
// delete 的返回值
delete person.name; // true(删除成功)
delete person.notExist; // true(删除不存在的属性也返回 true)
// 无法删除的属性
Object.defineProperty(person, 'id', {
value: 123,
configurable: false // 不可配置
});
delete person.id; // false(删除失败)
console.log(person.id); // 123(仍然存在)
// delete 与 undefined 的区别
person.email = 'test@example.com';
person.email = undefined; // 属性还在,值为 undefined
delete person.email; // 属性被彻底删除
// 使用解构删除(ES6)
const { password, ...userWithoutPassword } = person;
// userWithoutPassword 不包含 password
1.3.4 检测属性存在
javascript
const person = {
name: '张三',
age: 0, // 值为 0
email: '', // 空字符串
address: null, // null
phone: undefined // undefined
};
// in 操作符:检测对象及其原型链
console.log('name' in person); // true
console.log('age' in person); // true(0 也是有效值)
console.log('toString' in person); // true(继承自 Object.prototype)
// hasOwnProperty:只检测对象自身属性
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // true
console.log(person.hasOwnProperty('toString')); // false(继承的)
// 属性访问的问题
if (person.age) { // ❌ 0 会被当作 false
console.log('有年龄');
}
if (person.email) { // ❌ 空字符串会被当作 false
console.log('有邮箱');
}
if (person.address) { // ❌ null 会被当作 false
console.log('有地址');
}
// 正确的方式
if ('age' in person) { // ✅ 检测属性是否存在
console.log('有年龄属性');
}
if (person.age !== undefined) { // ✅ 检测值是否为 undefined
console.log('年龄已定义');
}
// Object.hasOwn(ES2022,推荐替代 hasOwnProperty)
console.log(Object.hasOwn(person, 'name')); // true
// Object.prototype.hasOwnProperty 的问题
const obj = Object.create(null); // 没有原型的对象
// obj.hasOwnProperty('key'); // ❌ 报错:hasOwnProperty 不存在
Object.prototype.hasOwnProperty.call(obj, 'key'); // ✅ 安全的方式
Object.hasOwn(obj, 'key'); // ✅ 更好的方式
1.4 对象的遍历
1.4.1 for...in 循环
javascript
const person = {
name: '张三',
age: 25,
gender: '男'
};
// for...in 遍历对象的所有可枚举属性(包括继承的)
for (let key in person) {
console.log(key + ': ' + person[key]);
}
// name: 张三
// age: 25
// gender: 男
// 只遍历自身属性
for (let key in person) {
if (person.hasOwnProperty(key)) {
console.log(key + ': ' + person[key]);
}
}
// 遍历顺序:先数字键(按升序),后字符串键(按添加顺序)
const obj = {
'3': '三',
'1': '一',
'2': '二',
'a': 'A',
'b': 'B'
};
for (let key in obj) {
console.log(key); // 1, 2, 3, a, b
}
1.4.2 Object.keys() / values() / entries()
javascript
const person = {
name: '张三',
age: 25,
gender: '男'
};
// Object.keys():获取所有可枚举属性的键名数组
const keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'gender']
// Object.values():获取所有可枚举属性的值数组
const values = Object.values(person);
console.log(values); // ['张三', 25, '男']
// Object.entries():获取键值对数组
const entries = Object.entries(person);
console.log(entries);
// [['name', '张三'], ['age', 25], ['gender', '男']]
// 遍历键值对
Object.entries(person).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// 从 entries 重建对象
const obj = Object.fromEntries(entries);
console.log(obj); // { name: '张三', age: 25, gender: '男' }
// 实际应用:过滤对象
const filtered = Object.fromEntries(
Object.entries(person).filter(([key, value]) => typeof value === 'string')
);
console.log(filtered); // { name: '张三', gender: '男' }
// 实际应用:映射对象
const upperCase = Object.fromEntries(
Object.entries(person).map(([key, value]) => [
key,
typeof value === 'string' ? value.toUpperCase() : value
])
);
1.4.3 Object.getOwnPropertyNames()
javascript
const person = {
name: '张三',
age: 25
};
Object.defineProperty(person, 'id', {
value: 123,
enumerable: false // 不可枚举
});
// Object.keys() 只返回可枚举属性
console.log(Object.keys(person)); // ['name', 'age']
// Object.getOwnPropertyNames() 返回所有自身属性(包括不可枚举)
console.log(Object.getOwnPropertyNames(person)); // ['name', 'age', 'id']
// 获取 Symbol 属性
const symKey = Symbol('key');
person[symKey] = 'symbol value';
console.log(Object.getOwnPropertySymbols(person)); // [Symbol(key)]
// Reflect.ownKeys() 返回所有自身属性(包括 Symbol)
console.log(Reflect.ownKeys(person)); // ['name', 'age', 'id', Symbol(key)]
1.5 对象的复制
1.5.1 浅拷贝
javascript
const original = {
name: '张三',
age: 25,
address: {
city: '北京',
district: '海淀'
}
};
// 方法1:展开运算符(ES6,推荐)
const copy1 = { ...original };
// 方法2:Object.assign()
const copy2 = Object.assign({}, original);
// 方法3:JSON 方法(有局限性)
const copy3 = JSON.parse(JSON.stringify(original));
// 浅拷贝的问题:嵌套对象共享引用
copy1.name = '李四'; // ✅ 不影响原对象
copy1.address.city = '上海'; // ❌ 影响原对象!
console.log(original.address.city); // '上海'(被修改了!)
// 验证
console.log(original.address === copy1.address); // true(同一个引用)
1.5.2 深拷贝
javascript
const original = {
name: '张三',
age: 25,
address: {
city: '北京',
district: '海淀'
},
hobbies: ['读书', '游泳'],
birthDate: new Date('1994-01-01'),
sayHi: function() {
console.log('Hi!');
}
};
// 方法1:JSON 方法(简单但不完美)
const jsonCopy = JSON.parse(JSON.stringify(original));
// 问题:
// 1. 丢失函数
// 2. 丢失 Date 对象(变成字符串)
// 3. 丢失 undefined
// 4. 丢失循环引用
console.log(jsonCopy.sayHi); // undefined
console.log(typeof jsonCopy.birthDate); // 'string'
// 方法2:递归深拷贝(基础版)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}
if (obj instanceof Object) {
const copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
}
const deepCopy = deepClone(original);
deepCopy.address.city = '上海';
console.log(original.address.city); // '北京'(未被修改)
// 方法3:使用库(推荐生产环境)
// lodash: _.cloneDeep(obj)
// 或使用 structuredClone(现代浏览器)
const structuredCopy = structuredClone(original);
// 注意:structuredClone 也不支持函数
第二章:属性描述符与对象配置
2.1 属性描述符详解
每个属性都有一个对应的描述符,控制该属性的行为。
javascript
const person = {
name: '张三'
};
// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
// {
// value: '张三',
// writable: true,
// enumerable: true,
// configurable: true
// }
// 数据描述符的四个特性
// 1. value:属性的值
// 2. writable:是否可修改
// 3. enumerable:是否可枚举(for...in、Object.keys)
// 4. configurable:是否可删除或修改特性
2.1.1 writable(可写性)
javascript
const person = {};
Object.defineProperty(person, 'name', {
value: '张三',
writable: false // 只读
});
console.log(person.name); // '张三'
person.name = '李四'; // 静默失败(非严格模式)
console.log(person.name); // '张三'(未被修改)
// 严格模式下会报错
'use strict';
person.name = '李四'; // TypeError: Cannot assign to read only property
2.1.2 enumerable(可枚举性)
javascript
const person = {
name: '张三',
age: 25
};
Object.defineProperty(person, 'password', {
value: 'secret123',
enumerable: false // 不可枚举
});
// 枚举时看不到 password
for (let key in person) {
console.log(key); // name, age(没有 password)
}
console.log(Object.keys(person)); // ['name', 'age']
console.log(Object.values(person)); // ['张三', 25]
// 但 password 仍然存在
console.log(person.password); // 'secret123'
console.log(Object.getOwnPropertyNames(person)); // ['name', 'age', 'password']
// 用途:隐藏内部属性
2.1.3 configurable(可配置性)
javascript
const person = {};
Object.defineProperty(person, 'id', {
value: 123,
writable: true,
configurable: false // 不可配置
});
// 不可删除
delete person.id; // false(删除失败)
console.log(person.id); // 123
// 不可修改特性
// Object.defineProperty(person, 'id', { writable: false }); // TypeError
// 但 value 仍然可以修改(如果 writable 为 true)
person.id = 456;
console.log(person.id); // 456
// 一旦设为不可配置,就改不回来了
2.2 Getter 和 Setter
访问器属性(Accessor Property)使用 getter 和 setter 代替 value 和 writable。
javascript
const person = {
firstName: '张',
lastName: '三',
// getter
get fullName() {
return this.firstName + this.lastName;
},
// setter
set fullName(name) {
const parts = name.split('');
this.firstName = parts[0];
this.lastName = parts.slice(1).join('');
}
};
console.log(person.fullName); // '张三'(调用 getter)
person.fullName = '李四'; // 调用 setter
console.log(person.firstName); // '李'
console.log(person.lastName); // '四'
// 使用 Object.defineProperty 定义
const user = {
firstName: '王',
lastName: '五'
};
Object.defineProperty(user, 'fullName', {
get: function() {
return this.firstName + ' ' + this.lastName;
},
set: function(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
},
enumerable: true,
configurable: true
});
2.2.1 实际应用:数据验证
javascript
const user = {
_age: 25, // 约定:下划线前缀表示私有
get age() {
return this._age;
},
set age(value) {
if (typeof value !== 'number') {
throw new TypeError('年龄必须是数字');
}
if (value < 0 || value > 150) {
throw new RangeError('年龄必须在 0-150 之间');
}
this._age = value;
}
};
user.age = 30; // ✅
console.log(user.age); // 30
// user.age = -5; // ❌ RangeError
// user.age = 'abc'; // ❌ TypeError
2.2.2 实际应用:计算属性
javascript
const cart = {
items: [
{ name: '苹果', price: 5, quantity: 3 },
{ name: '香蕉', price: 3, quantity: 5 }
],
get total() {
return this.items.reduce((sum, item) => {
return sum + item.price * item.quantity;
}, 0);
},
get itemCount() {
return this.items.reduce((count, item) => {
return count + item.quantity;
}, 0);
}
};
console.log(cart.total); // 30 (5*3 + 3*5)
console.log(cart.itemCount); // 8 (3 + 5)
// 添加商品后自动更新
cart.items.push({ name: '橙子', price: 4, quantity: 2 });
console.log(cart.total); // 38 (30 + 4*2)
2.3 对象防篡改
JavaScript 提供了三个级别的对象防篡改机制。
2.3.1 Object.preventExtensions()(禁止扩展)
javascript
const person = {
name: '张三'
};
Object.preventExtensions(person);
// 可以读取
console.log(person.name); // '张三'
// 可以修改
person.name = '李四'; // ✅
// 可以删除
delete person.name; // ✅
// ❌ 不能添加新属性
person.age = 25; // 静默失败(严格模式报错)
// 检测
console.log(Object.isExtensible(person)); // false
2.3.2 Object.seal()(密封)
javascript
const person = {
name: '张三',
age: 25
};
Object.seal(person);
// 可以读取 ✅
// 可以修改 ✅
person.name = '李四';
// ❌ 不能添加
person.gender = '男'; // 失败
// ❌ 不能删除
delete person.age; // 失败
// 所有属性的 configurable 设为 false
// 检测
console.log(Object.isSealed(person)); // true
console.log(Object.isExtensible(person)); // false
2.3.3 Object.freeze()(冻结)
javascript
const person = {
name: '张三',
age: 25,
address: {
city: '北京'
}
};
Object.freeze(person);
// 可以读取 ✅
// ❌ 不能修改
person.name = '李四'; // 失败
// ❌ 不能添加
person.gender = '男'; // 失败
// ❌ 不能删除
delete person.age; // 失败
// 注意:浅冻结,嵌套对象仍然可以修改
person.address.city = '上海'; // ✅ 仍然可以修改!
// 深度冻结函数
function deepFreeze(obj) {
// 获取所有属性名
const propNames = Object.getOwnPropertyNames(obj);
// 遍历属性
for (const name of propNames) {
const value = obj[name];
// 如果属性值是对象,递归冻结
if (value && typeof value === 'object') {
deepFreeze(value);
}
}
return Object.freeze(obj);
}
const frozenPerson = deepFreeze({ ...person });
frozenPerson.address.city = '广州'; // 现在修改无效了
// 检测
console.log(Object.isFrozen(person)); // true
2.3.4 三个级别对比
| 特性 | preventExtensions | seal | freeze |
|---|---|---|---|
| 添加属性 | ❌ | ❌ | ❌ |
| 删除属性 | ✅ | ❌ | ❌ |
| 修改属性值 | ✅ | ✅ | ❌ |
| 修改特性 | ✅ | ❌ | ❌ |
| 检测方法 | isExtensible() | isSealed() | isFrozen() |
第三章:原型与继承
3.1 原型基础
3.1.1 什么是原型?
每个 JavaScript 对象(除了 null)都有一个内部属性 [[Prototype]],指向它的原型对象。
javascript
// 对象字面量的原型是 Object.prototype
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// 创建对象时指定原型
const proto = {
greet() {
console.log('Hello!');
}
};
const person = Object.create(proto);
person.greet(); // 'Hello!'
console.log(Object.getPrototypeOf(person) === proto); // true
3.1.2 原型链
当访问对象的属性时,如果对象本身没有,会沿着原型链向上查找。
javascript
const grandparent = {
familyName: '张氏',
tradition: '重视教育'
};
const parent = Object.create(grandparent);
parent.name = '父亲';
parent.job = '教师';
const child = Object.create(parent);
child.name = '孩子';
child.hobby = '读书';
// 原型链:child -> parent -> grandparent -> Object.prototype -> null
console.log(child.hobby); // '孩子'(自身属性)
console.log(child.name); // '孩子'(自身属性,覆盖 parent 的 name)
console.log(child.job); // '教师'(从 parent 继承)
console.log(child.familyName); // '张氏'(从 grandparent 继承)
console.log(child.toString); // [Function: toString](从 Object.prototype 继承)
console.log(child.notExist); // undefined(原型链尽头也没找到)
// 检测属性位置
console.log(child.hasOwnProperty('hobby')); // true(自身)
console.log(child.hasOwnProperty('job')); // false(继承)
console.log('job' in child); // true(在原型链上)
3.1.3 构造函数与原型
javascript
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 在原型上定义方法(所有实例共享)
Person.prototype.greet = function() {
console.log('Hello, I am ' + this.name);
};
Person.prototype.species = '人类';
// 创建实例
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);
// 实例属性
console.log(person1.name); // '张三'
console.log(person2.name); // '李四'
// 原型属性(共享)
console.log(person1.species); // '人类'
console.log(person2.species); // '人类'
console.log(person1.species === person2.species); // true
// 方法(共享)
person1.greet(); // 'Hello, I am 张三'
person2.greet(); // 'Hello, I am 李四'
console.log(person1.greet === person2.greet); // true(同一个函数)
// 原型链
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype); // true
// constructor 属性
console.log(Person.prototype.constructor === Person); // true
console.log(person1.constructor === Person); // true
3.2 继承模式
3.2.1 原型链继承
javascript
// 父构造函数
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色']; // 引用类型
}
Animal.prototype.sayName = function() {
console.log('我的名字是' + this.name);
};
// 子构造函数
function Dog(name, breed) {
this.breed = breed;
}
// 继承:将 Dog 的原型指向 Animal 的实例
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; // 修复 constructor
Dog.prototype.sayBreed = function() {
console.log('我的品种是' + this.breed);
};
const dog1 = new Dog('旺财', '金毛');
const dog2 = new Dog('来福', '哈士奇');
dog1.sayName(); // '我的名字是旺财'
dog1.sayBreed(); // '我的品种是金毛'
// 问题1:无法向父构造函数传参
dog1.sayName(); // name 是 undefined
// 问题2:引用类型共享
dog1.colors.push('棕色');
console.log(dog2.colors); // ['黑色', '白色', '棕色'](也被修改了!)
3.2.2 借用构造函数继承
javascript
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
function Dog(name, breed) {
// 借用父构造函数
Animal.call(this, name);
this.breed = breed;
}
const dog1 = new Dog('旺财', '金毛');
const dog2 = new Dog('来福', '哈士奇');
console.log(dog1.name); // '旺财'
console.log(dog1.colors); // ['黑色', '白色']
dog1.colors.push('棕色');
console.log(dog2.colors); // ['黑色', '白色'](未被修改)
// 问题:无法继承原型上的方法
// dog1.sayName(); // ❌ 报错:sayName 不是函数
3.2.3 组合继承(经典继承)
javascript
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
Animal.prototype.sayName = function() {
console.log('我的名字是' + this.name);
};
function Dog(name, breed) {
// 继承实例属性
Animal.call(this, name);
this.breed = breed;
}
// 继承原型方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.sayBreed = function() {
console.log('我的品种是' + this.breed);
};
const dog = new Dog('旺财', '金毛');
dog.sayName(); // '我的名字是旺财'
dog.sayBreed(); // '我的品种是金毛'
// 缺点:父构造函数被调用了两次
// 1. Animal.call(this, name)
// 2. new Animal()
3.2.4 寄生组合继承(最佳实践)
javascript
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
Animal.prototype.sayName = function() {
console.log('我的名字是' + this.name);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 核心:只继承原型,不调用父构造函数
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.sayBreed = function() {
console.log('我的品种是' + this.breed);
};
const dog = new Dog('旺财', '金毛');
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
// 优化:封装继承函数
function inheritPrototype(child, parent) {
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
inheritPrototype(Cat, Animal);
3.3 ES6 Class 继承
javascript
// 父类
class Animal {
constructor(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
sayName() {
console.log('我的名字是' + this.name);
}
// 静态方法
static isAnimal(obj) {
return obj instanceof Animal;
}
}
// 子类
class Dog extends Animal {
constructor(name, breed) {
// 必须在使用 this 之前调用 super()
super(name);
this.breed = breed;
}
sayBreed() {
console.log('我的品种是' + this.breed);
}
// 重写父类方法
sayName() {
super.sayName(); // 调用父类方法
console.log('我是一只' + this.breed);
}
}
const dog = new Dog('旺财', '金毛');
dog.sayName();
// '我的名字是旺财'
// '我是一只金毛'
// 静态方法继承
console.log(Dog.isAnimal(dog)); // true
// 检查继承关系
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
3.3.1 super 关键字
javascript
class Parent {
constructor(value) {
this.value = value;
}
method() {
console.log('Parent method');
}
static staticMethod() {
console.log('Parent static method');
}
}
class Child extends Parent {
constructor(value, extra) {
// super() 调用父类构造函数
super(value);
this.extra = extra;
}
method() {
// super.method() 调用父类实例方法
super.method();
console.log('Child method');
}
static staticMethod() {
// super.staticMethod() 调用父类静态方法
super.staticMethod();
console.log('Child static method');
}
}
const child = new Child('value', 'extra');
child.method();
// 'Parent method'
// 'Child method'
Child.staticMethod();
// 'Parent static method'
// 'Child static method'
第四章:ES6+ 对象新特性
4.1 属性简写
javascript
const name = '张三';
const age = 25;
// ES5
const person1 = {
name: name,
age: age,
greet: function() {
console.log('Hello');
}
};
// ES6 简写
const person2 = {
name, // 等同于 name: name
age, // 等同于 age: age
greet() { // 等同于 greet: function()
console.log('Hello');
}
};
// 方法简写的优势:super 可以正常使用
const obj = {
method() {
// 可以使用 super
}
};
4.2 计算属性名
javascript
const propName = 'dynamicKey';
const index = 1;
// ES5
const obj1 = {};
obj1[propName] = 'value';
obj1['item' + index] = 'item1';
// ES6 计算属性名
const obj2 = {
[propName]: 'value', // 动态键名
['item' + index]: 'item1', // 表达式
[getKey()]: 'computed value', // 函数调用
[`${propName}2`]: 'value2' // 模板字符串
};
function getKey() {
return 'computed';
}
console.log(obj2);
// {
// dynamicKey: 'value',
// item1: 'item1',
// computed: 'computed value',
// dynamicKey2: 'value2'
// }
// 实际应用:根据 ID 创建对象
const users = ['张三', '李四', '王五'];
const userMap = users.reduce((map, name, index) => ({
...map,
[`user_${index + 1}`]: name
}), {});
console.log(userMap);
// { user_1: '张三', user_2: '李四', user_3: '王五' }
4.3 展开运算符
javascript
// 对象展开
const defaults = {
host: 'localhost',
port: 3000,
debug: false
};
const config = {
...defaults, // 展开 defaults
port: 8080, // 覆盖 port
secure: true // 添加新属性
};
console.log(config);
// { host: 'localhost', port: 8080, debug: false, secure: true }
// 多个对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 3, c: 4 }(后面的覆盖前面的)
// 与解构结合(删除属性)
const person = {
name: '张三',
password: 'secret',
age: 25
};
const { password, ...safePerson } = person;
console.log(safePerson); // { name: '张三', age: 25 }
// 条件展开
const condition = true;
const obj = {
a: 1,
...(condition && { b: 2 }), // 条件为 true 时展开
...(false && { c: 3 }) // 条件为 false 时不展开
};
console.log(obj); // { a: 1, b: 2 }
4.4 Object 新方法
4.4.1 Object.assign()
javascript
// 对象合并
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 2, c: 3 }
// 创建副本(浅拷贝)
const original = { x: 1, y: { z: 2 } };
const copy = Object.assign({}, original);
// 属性覆盖
const obj = Object.assign({}, { a: 1, b: 2 }, { b: 3, c: 4 });
console.log(obj); // { a: 1, b: 3, c: 4 }
// 注意事项:浅拷贝
original.y.z = 999;
console.log(copy.y.z); // 999(也被修改了)
// 用途:设置默认值
function createUser(options) {
const defaults = {
name: '匿名',
age: 18,
role: 'user'
};
return Object.assign({}, defaults, options);
}
console.log(createUser({ name: '张三' }));
// { name: '张三', age: 18, role: 'user' }
4.4.2 Object.is()
javascript
// 严格相等比较(修复 === 的特殊情况)
// === 的问题
console.log(NaN === NaN); // false
console.log(+0 === -0); // true
// Object.is() 正确处理
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
// 其他情况与 === 相同
console.log(Object.is(1, 1)); // true
console.log(Object.is('a', 'a')); // true
console.log(Object.is({}, {})); // false(不同引用)
4.4.3 Object.setPrototypeOf() / getPrototypeOf()
javascript
const animal = {
species: '动物',
speak() {
console.log('发出声音');
}
};
const dog = {
breed: '狗'
};
// 设置原型
Object.setPrototypeOf(dog, animal);
dog.speak(); // '发出声音'
console.log(dog.species); // '动物'
// 获取原型
console.log(Object.getPrototypeOf(dog) === animal); // true
// 注意:频繁修改原型会影响性能
4.4.4 Object.fromEntries()
javascript
// 将键值对数组转换为对象
const entries = [
['name', '张三'],
['age', 25],
['city', '北京']
];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: '张三', age: 25, city: '北京' }
// 与 Object.entries() 互逆
const original = { a: 1, b: 2 };
const back = Object.fromEntries(Object.entries(original));
console.log(back); // { a: 1, b: 2 }
// 实际应用:Map 转对象
const map = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
const objFromMap = Object.fromEntries(map);
console.log(objFromMap); // { key1: 'value1', key2: 'value2' }
// 实际应用:过滤和转换
const prices = { apple: 5, banana: 3, orange: 4 };
const discounted = Object.fromEntries(
Object.entries(prices).map(([fruit, price]) => [
fruit,
price * 0.9 // 打9折
])
);
console.log(discounted); // { apple: 4.5, banana: 2.7, orange: 3.6 }
4.5 Symbol 作为键
javascript
// Symbol 创建唯一标识符
const id = Symbol('id');
const name = Symbol('name');
const user = {
[id]: 12345, // Symbol 键
[name]: '张三',
age: 25 // 普通字符串键
};
console.log(user[id]); // 12345
console.log(user[name]); // '张三'
// Symbol 键不会出现在常规遍历中
console.log(Object.keys(user)); // ['age']
console.log(Object.getOwnPropertyNames(user)); // ['age']
// 获取 Symbol 键
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id), Symbol(name)]
// 用途1:私有属性(约定)
const _password = Symbol('password');
class User {
constructor(name, password) {
this.name = name;
this[_password] = password; // 外部难以访问
}
}
// 用途2:防止属性名冲突
const widget1 = { type: 'button' };
const widget2 = { type: 'input' };
const widgetId = Symbol.for('widgetId'); // 全局 Symbol
widget1[widgetId] = 1;
widget2[widgetId] = 2;
// Symbol.for() 和 Symbol.keyFor()
const sym1 = Symbol.for('app.id');
const sym2 = Symbol.for('app.id');
console.log(sym1 === sym2); // true(同一个 Symbol)
console.log(Symbol.keyFor(sym1)); // 'app.id'
第五章:对象的实际应用
5.1 配置对象模式
javascript
// 使用对象参数代替多个参数
// ❌ 不好:参数太多,顺序容易错
function createElement(tag, className, id, text, attrs, events) {
// ...
}
// ✅ 好:使用配置对象
function createElement(tag, options = {}) {
const {
className = '',
id = '',
text = '',
attrs = {},
events = {}
} = options;
const element = document.createElement(tag);
if (className) element.className = className;
if (id) element.id = id;
if (text) element.textContent = text;
Object.entries(attrs).forEach(([key, value]) => {
element.setAttribute(key, value);
});
Object.entries(events).forEach(([event, handler]) => {
element.addEventListener(event, handler);
});
return element;
}
// 使用
const button = createElement('button', {
className: 'btn btn-primary',
id: 'submitBtn',
text: '提交',
attrs: { type: 'submit', disabled: false },
events: {
click: () => console.log('点击了'),
mouseover: () => console.log('鼠标悬停')
}
});
5.2 命名空间模式
javascript
// 使用对象创建命名空间,避免全局变量污染
const MyApp = {
// 配置
config: {
apiUrl: 'https://api.example.com',
timeout: 5000
},
// 工具方法
utils: {
formatDate(date) {
return date.toISOString().split('T')[0];
},
debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
},
// 模块
modules: {
User: {
getById(id) {
return fetch(`${MyApp.config.apiUrl}/users/${id}`);
},
create(data) {
return fetch(`${MyApp.config.apiUrl}/users`, {
method: 'POST',
body: JSON.stringify(data)
});
}
},
Order: {
getByUser(userId) {
return fetch(`${MyApp.config.apiUrl}/orders?userId=${userId}`);
}
}
}
};
// 使用
MyApp.utils.formatDate(new Date());
MyApp.modules.User.getById(123);
5.3 选项合并模式
javascript
// 深度合并选项对象
function deepMerge(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
deepMerge(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return deepMerge(target, ...sources);
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
// 使用
const defaults = {
server: {
host: 'localhost',
port: 3000
},
database: {
host: 'localhost',
port: 5432,
pool: {
min: 2,
max: 10
}
}
};
const userConfig = {
server: {
port: 8080
},
database: {
host: 'db.example.com',
pool: {
max: 20
}
}
};
const config = deepMerge({}, defaults, userConfig);
console.log(config);
// {
// server: { host: 'localhost', port: 8080 },
// database: {
// host: 'db.example.com',
// port: 5432,
// pool: { min: 2, max: 20 }
// }
// }
5.4 链式调用模式
javascript
// 实现链式调用:每个方法返回 this
class QueryBuilder {
constructor() {
this.table = '';
this.whereConditions = [];
this.orderByField = '';
this.limitCount = 0;
}
from(table) {
this.table = table;
return this; // 返回 this 实现链式调用
}
where(condition) {
this.whereConditions.push(condition);
return this;
}
orderBy(field, direction = 'ASC') {
this.orderByField = `${field} ${direction}`;
return this;
}
limit(count) {
this.limitCount = count;
return this;
}
build() {
let sql = `SELECT * FROM ${this.table}`;
if (this.whereConditions.length) {
sql += ' WHERE ' + this.whereConditions.join(' AND ');
}
if (this.orderByField) {
sql += ' ORDER BY ' + this.orderByField;
}
if (this.limitCount) {
sql += ' LIMIT ' + this.limitCount;
}
return sql;
}
}
// 使用
const query = new QueryBuilder()
.from('users')
.where('age > 18')
.where('status = "active"')
.orderBy('created_at', 'DESC')
.limit(10)
.build();
console.log(query);
// SELECT * FROM users WHERE age > 18 AND status = "active" ORDER BY created_at DESC LIMIT 10
附录:对象知识速查表
创建对象
| 方法 | 示例 | 说明 |
|---|---|---|
| 字面量 | {} |
最常用 |
| new Object() | new Object() |
不推荐 |
| Object.create() | Object.create(proto) |
指定原型 |
| 构造函数 | new Person() |
传统方式 |
| Class | new Person() |
ES6 语法糖 |
属性操作
| 操作 | 语法 | 说明 |
|---|---|---|
| 访问 | obj.key / obj['key'] |
点号或方括号 |
| 添加/修改 | obj.key = value |
直接赋值 |
| 删除 | delete obj.key |
彻底删除 |
| 检测 | 'key' in obj |
包括继承 |
| 检测自身 | obj.hasOwnProperty('key') |
不包括继承 |
遍历方法
| 方法 | 返回 | 说明 |
|---|---|---|
| for...in | - | 所有可枚举属性(包括继承) |
| Object.keys() | 键名数组 | 自身可枚举 |
| Object.values() | 值数组 | 自身可枚举 |
| Object.entries() | 键值对数组 | 自身可枚举 |
| Object.getOwnPropertyNames() | 键名数组 | 所有自身属性 |
对象状态
| 方法 | 效果 | 检测 |
|---|---|---|
| preventExtensions | 禁止添加 | isExtensible() |
| seal | 禁止添加/删除/配置 | isSealed() |
| freeze | 完全只读 | isFrozen() |
原型相关
| 操作 | 方法 | 说明 |
|---|---|---|
| 获取原型 | Object.getPrototypeOf(obj) |
- |
| 设置原型 | Object.setPrototypeOf(obj, proto) |
影响性能 |
| 创建指定原型 | Object.create(proto) |
推荐 |
| 检查原型 | obj instanceof Constructor |
- |
| 检查原型链 | proto.isPrototypeOf(obj) |
- |
以上是 JavaScript 对象的完整知识点。对象是整个 JavaScript 语言的核心,深入理解对象对于掌握 JavaScript 至关重要。