Javascript中的对象

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 至关重要。

相关推荐
JosieBook2 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
加号32 小时前
【C#】 文件与目录管理:创建、删除操作的技术解析
开发语言·c#
diving deep3 小时前
脚本速览-python
开发语言·python
一生了无挂3 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
swordbob3 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
学Linux的语莫4 小时前
Vue 3 入门教程
前端·javascript·vue.js
怕浪猫4 小时前
第一章、Chrome DevTools Protocol (CDP) 详解
前端·javascript·chrome
小小编程路4 小时前
C++ 异常 完整讲解
开发语言·c++
AI科技星5 小时前
数术工坊 · 第四卷 橡皮泥江湖(拓扑学)【完整定稿】
c语言·开发语言·汇编·electron·概率论·拓扑学
张忠琳5 小时前
【Go 1.26.4】Golang Select 深度解析
开发语言·后端·golang