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

相关推荐
喵星人工作室3 小时前
C++火影忍者1.1版本
开发语言·c++·游戏
晓得迷路了3 小时前
栗子前端技术周刊第 130 期 - Angular 22 RC、Rolldown 1.0.1、pnpm 11.2...
前端·javascript·react.js
এ慕ོ冬℘゜3 小时前
原生 JS 手写日期选择器|完整可复用日历组件实战
前端·javascript·css
Maimai108083 小时前
用 TanStack Table、React Query 和 shadcn/ui 搭一个可维护的数据表格架构
前端·javascript·react.js·ui·架构·前端框架·reactjs
東雪木3 小时前
Java 基础语法与核心数据类型 专属复习笔记
java·开发语言·笔记·java面试
ch.ju3 小时前
Java程序设计(第3版)第四章——方法的重载
java·开发语言
蜡笔小电芯3 小时前
【Electron】第4章—renderer.js 与页面交互逻辑
javascript·electron·交互
ch.ju3 小时前
Java Programming Chapter 4——Overloading of method
java·开发语言
Teable任意门互动3 小时前
拆解 Teable 背后研发主体,开源多维表格平台实力与落地案例
开发语言·开源·excel·飞书·开源软件