返回首页

03-对象与原型链

分类:01-JavaScript基础核心
发布于:
阅读时间:88 分钟

对象与原型链

📋 学习目标

  • 深入理解JavaScript对象的本质和创建方式
  • 掌握原型链的工作机制和继承原理
  • 学会使用ES6类语法进行面向对象编程
  • 理解JavaScript中继承和多态的实现
  • 掌握对象的高级特性和设计模式

🎯 对象基础

1. 对象的创建方式

// 1. 对象字面量(最常用)
const person = {
    name: 'Alice',
    age: 25,
    greet: function() {
        return `Hello, I'm ${this.name}`;
    }
};

console.log(person.name);        // 'Alice'
console.log(person.greet());     // 'Hello, I'm Alice'

// 2. 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = function() {
        return `Hello, I'm ${this.name}`;
    };
}

const person2 = new Person('Bob', 30);
console.log(person2.name);       // 'Bob'
console.log(person2.greet());    // 'Hello, I'm Bob'

// 3. Object.create()(指定原型)
const personProto = {
    greet: function() {
        return `Hello, I'm ${this.name}`;
    }
};

const person3 = Object.create(personProto);
person3.name = 'Charlie';
person3.age = 35;

console.log(person3.greet());    // 'Hello, I'm Charlie'

// 4. 工厂函数
function createPerson(name, age) {
    return {
        name,
        age,
        greet() {
            return `Hello, I'm ${this.name}`;
        }
    };
}

const person4 = createPerson('David', 40);
console.log(person4.greet());    // 'Hello, I'm David'

2. 对象的属性

const car = {
    brand: 'Toyota',
    model: 'Camry',
    year: 2020,
    'property with spaces': 'value'
};

// 访问属性
console.log(car.brand);                      // 'Toyota'
console.log(car['brand']);                    // 'Toyota'
console.log(car['property with spaces']);     // 'value'

// 动态属性名
const propertyName = 'model';
console.log(car[propertyName]);               // 'Camry'

// 添加属性
car.color = 'blue';
car['engine'] = '2.5L';

// 删除属性
delete car.year;

// 检查属性是否存在
console.log('brand' in car);                 // true
console.log('year' in car);                  // false
console.log(car.hasOwnProperty('brand'));      // true

// 枚举属性
for (const key in car) {
    if (car.hasOwnProperty(key)) {
        console.log(`${key}: ${car[key]}`);
    }
}

// Object.keys(), Object.values(), Object.entries()
console.log(Object.keys(car));               // ['brand', 'model', 'property with spaces', 'color', 'engine']
console.log(Object.values(car));             // ['Toyota', 'Camry', 'value', 'blue', '2.5L']
console.log(Object.entries(car));            // [['brand', 'Toyota'], ['model', 'Camry'], ...]

3. 属性描述符

const obj = {};

// 添加属性(默认描述符)
Object.defineProperty(obj, 'name', {
    value: 'Alice',
    writable: true,      // 可写
    enumerable: true,    // 可枚举
    configurable: true  // 可配置
});

// 只读属性
Object.defineProperty(obj, 'readOnly', {
    value: 'Cannot change',
    writable: false
});

// 不可枚举属性
Object.defineProperty(obj, 'hidden', {
    value: 'You cannot see me',
    enumerable: false
});

// 访问器属性
const person = {
    _age: 25,

    get age() {
        return this._age;
    },

    set age(newAge) {
        if (newAge > 0 && newAge < 150) {
            this._age = newAge;
        }
    }
};

console.log(person.age); // 25
person.age = 30;
console.log(person.age); // 30
person.age = -5;        // 无效,不会设置

// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(person, 'age');
console.log(descriptor);
// {
//   get: [Function: get age],
//   set: [Function: set age],
//   enumerable: true,
//   configurable: true
// }

// 批量定义属性
Object.defineProperties(obj, {
    firstName: {
        value: 'John',
        writable: true
    },
    lastName: {
        value: 'Doe',
        writable: true
    },
    fullName: {
        get() {
            return `${this.firstName} ${this.lastName}`;
        },
        enumerable: true
    }
});

🔗 原型链

1. 原型基础概念

// 每个函数都有prototype属性
function Person(name) {
    this.name = name;
}

Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

// 每个对象都有__proto__属性(指向其构造函数的原型)
const person = new Person('Alice');

console.log(person.__proto__ =<mark> Person.prototype); // true
console.log(Person.prototype.constructor </mark>= Person); // true

// 原型链查找
console.log(person.greet()); // 在Person.prototype上找到greet方法

// 添加到原型上的方法被所有实例共享
Person.prototype.sayAge = function() {
    return `I am ${this.age} years old`;
};

person.age = 25;
console.log(person.sayAge()); // 'I am 25 years old'

2. 原型链继承

// 基础继承
function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    return `${this.name} is eating`;
};

function Dog(name, breed) {
    Animal.call(this, name); // 调用父构造函数
    this.breed = breed;
}

// 设置原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    return `${this.name} is barking`;
};

const dog = new Dog('Buddy', 'Golden Retriever');
console.log(dog.eat());   // 'Buddy is eating' (继承自Animal)
console.log(dog.bark());  // 'Buddy is barking' (Dog自己的方法)

// 检查继承关系
console.log(dog instanceof Dog);     // true
console.log(dog instanceof Animal);  // true
console.log(dog instanceof Object);  // true

3. 原型链的实际应用

// 方法继承
function ArrayExtender() {
    // 空构造函数
}

ArrayExtender.prototype = Object.create(Array.prototype);
ArrayExtender.prototype.constructor = ArrayExtender;

ArrayExtender.prototype.first = function() {
    return this[0];
};

ArrayExtender.prototype.last = function() {
    return this[this.length - 1];
};

const extendedArray = new ArrayExtender();
extendedArray.push(1, 2, 3, 4, 5);

console.log(extendedArray.first()); // 1
console.log(extendedArray.last());  // 5
console.log(extendedArray instanceof Array); // true

// 混入模式(Mixin)
const canFly = {
    fly() {
        return `${this.name} is flying`;
    }
};

const canSwim = {
    swim() {
        return `${this.name} is swimming`;
    }
};

function Duck(name) {
    this.name = name;
}

// 混入功能
Object.assign(Duck.prototype, canFly, canSwim);

const duck = new Duck('Donald');
console.log(duck.fly()); // 'Donald is flying'
console.log(duck.swim()); // 'Donald is swimming'

🏗️ ES6类语法

1. 基础类定义

// ES6类语法(语法糖,本质还是原型链)
class Person {
    // 构造函数
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // 实例方法
    greet() {
        return `Hello, I'm ${this.name}`;
    }

    // 访问器方法
    get info() {
        return `${this.name} is ${this.age} years old`;
    }

    set info(newInfo) {
        const parts = newInfo.split(' is ');
        this.name = parts[0];
        this.age = parseInt(parts[1]);
    }
}

const person = new Person('Alice', 25);
console.log(person.greet());    // 'Hello, I'm Alice'
console.log(person.info);      // 'Alice is 25 years old'

person.info = 'Bob is 30 years old';
console.log(person.name);      // 'Bob'
console.log(person.age);       // 30

2. 继承

// 父类
class Animal {
    constructor(name) {
        this.name = name;
    }

    eat() {
        return `${this.name} is eating`;
    }

    // 静态方法
    static getKingdom() {
        return 'Animalia';
    }
}

// 子类
class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 调用父构造函数
        this.breed = breed;
    }

    bark() {
        return `${this.name} is barking`;
    }

    // 重写父方法
    eat() {
        return `${this.name} (${this.breed}) is eating happily`;
    }

    // 调用父方法
    eatLikeAnimal() {
        return super.eat(); // 调用父类的eat方法
    }
}

const dog = new Dog('Buddy', 'Golden Retriever');
console.log(dog.eat());           // 'Buddy (Golden Retriever) is eating happily'
console.log(dog.eatLikeAnimal()); // 'Buddy is eating'
console.log(dog.bark());          // 'Buddy is barking'
console.log(Animal.getKingdom()); // 'Animalia'

3. 私有字段和方法

// 私有字段(ES2022)
class BankAccount {
    [#balance](/tags/balance) = 0; // 私有字段

    constructor(initialBalance) {
        if (initialBalance >= 0) {
            this.#balance = initialBalance;
        }
    }

    // 公共方法
    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
            return this.#balance;
        }
        throw new Error('Amount must be positive');
    }

    withdraw(amount) {
        if (amount > 0 && amount <= this.#balance) {
            this.#balance -= amount;
            return this.#balance;
        }
        throw new Error('Invalid amount');
    }

    getBalance() {
        return this.#balance;
    }

    // 私有方法
    [#validateAmount(amount)](/tags/validateAmount(amount)) {
        return amount > 0;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500

// account.#balance = 10000; // SyntaxError: Private field '#balance' must be declared in an enclosing class

4. 静态成员和实例成员

class MathUtils {
    // 静态属性
    static PI = 3.14159;

    // 静态方法
    static circleArea(radius) {
        return this.PI * radius * radius;
    }

    static max(...numbers) {
        return Math.max(...numbers);
    }

    // 实例方法
    constructor() {
        this.calculations = 0;
    }

    calculateCircleArea(radius) {
        this.calculations++;
        return MathUtils.circleArea(radius);
    }

    getCalculationCount() {
        return this.calculations;
    }
}

// 使用静态成员
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.circleArea(5)); // 78.53975
console.log(MathUtils.max(1, 5, 3, 9, 2)); // 9

// 使用实例成员
const calculator = new MathUtils();
console.log(calculator.calculateCircleArea(3)); // 28.27431
console.log(calculator.getCalculationCount()); // 1

🎨 高级对象特性

1. 对象冻结和密封

// Object.seal() - 密封对象(不能添加或删除属性,但可以修改)
const sealedObj = { a: 1, b: 2 };
Object.seal(sealedObj);

sealedObj.c = 3; // 严格模式下会报错
delete sealedObj.a; // 严格模式下会报错
sealedObj.a = 10; // 可以修改

console.log(Object.isSealed(sealedObj)); // true

// Object.freeze() - 冻结对象(完全不可变)
const frozenObj = { a: 1, b: 2 };
Object.freeze(frozenObj);

frozenObj.c = 3; // 严格模式下会报错
delete frozenObj.a; // 严格模式下会报错
frozenObj.a = 10; // 严格模式下会报错

console.log(Object.isFrozen(frozenObj)); // true

// 深度冻结
function deepFreeze(obj) {
    Object.freeze(obj);

    Object.getOwnPropertyNames(obj).forEach(prop => {
        if (obj[prop] !<mark> null && typeof obj[prop] </mark>= 'object') {
            deepFreeze(obj[prop]);
        }
    });

    return obj;
}

const nestedObj = {
    a: 1,
    b: {
        c: 2,
        d: {
            e: 3
        }
    }
};

deepFreeze(nestedObj);

2. 对象比较

// 浅比较
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };
const obj3 = obj1;

console.log(obj1 =<mark> obj2); // false (不同的对象引用)
console.log(obj1 </mark>= obj3); // true (相同的对象引用)

// 深度比较
function deepEqual(obj1, obj2) {
    if (obj1 =<mark> obj2) return true;

    if (typeof obj1 !</mark> 'object' || typeof obj2 !<mark> 'object' || obj1 </mark> null || obj2 == null) {
        return false;
    }

    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) return false;

    for (const key of keys1) {
        if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
            return false;
        }
    }

    return true;
}

console.log(deepEqual(obj1, obj2)); // true

3. 对象合并

// Object.assign() - 浅拷贝合并
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // { a: 1, b: 3, c: 4 }

// 展开运算符 - 更简洁的合并
const merged2 = { ...obj1, ...obj2 };
console.log(merged2); // { a: 1, b: 3, c: 4 }

// 深度合并
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 deepMerged = deepMerge(
    { a: 1, b: { x: 1, y: 2 } },
    { b: { y: 3, z: 4 }, c: 5 }
);
console.log(deepMerged); // { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }

🎯 设计模式

1. 工厂模式

// 简单工厂
class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        return `${this.name} makes a sound`;
    }
}

class Dog extends Animal {
    speak() {
        return `${this.name} barks`;
    }
}

class Cat extends Animal {
    speak() {
        return `${this.name} meows`;
    }
}

class AnimalFactory {
    static createAnimal(type, name) {
        switch (type.toLowerCase()) {
            case 'dog':
                return new Dog(name);
            case 'cat':
                return new Cat(name);
            default:
                return new Animal(name);
        }
    }
}

const dog = AnimalFactory.createAnimal('dog', 'Buddy');
const cat = AnimalFactory.createAnimal('cat', 'Whiskers');

console.log(dog.speak()); // 'Buddy barks'
console.log(cat.speak()); // 'Whiskers meows'

2. 观察者模式

class EventEmitter {
    constructor() {
        this.events = {};
    }

    // 订阅事件
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    }

    // 触发事件
    emit(eventName, data) {
        if (this.events[eventName]) {
            this.events[eventName].forEach(callback => callback(data));
        }
    }

    // 取消订阅
    off(eventName, callback) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
        }
    }
}

// 使用示例
const emitter = new EventEmitter();

emitter.on('userLogin', (user) => {
    console.log(`User ${user.name} logged in`);
});

emitter.on('userLogin', (user) => {
    console.log(`Sending welcome email to ${user.email}`);
});

emitter.emit('userLogin', { name: 'Alice', email: 'alice@example.com' });
// 'User Alice logged in'
// 'Sending welcome email to alice@example.com'

3. 单例模式

class DatabaseConnection {
    constructor() {
        if (DatabaseConnection.instance) {
            return DatabaseConnection.instance;
        }

        this.connection = this.createConnection();
        DatabaseConnection.instance = this;
    }

    createConnection() {
        // 模拟数据库连接
        return {
            connected: true,
            query: (sql) => `Result of: ${sql}`
        };
    }

    query(sql) {
        return this.connection.query(sql);
    }
}

const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();

console.log(db1 === db2); // true (同一个实例)
console.log(db1.query('SELECT * FROM users')); // 'Result of: SELECT * FROM users'

⚠️ 常见陷阱

1. 原型链污染

// ❌ 危险:修改原型
Array.prototype.sum = function() {
    return this.reduce((a, b) => a + b, 0);
};

console.log([1, 2, 3, 4].sum()); // 10

// 可能导致意外的行为
for (const key in []) {
    console.log(key); // 会输出 'sum'
}

// ✅ 更好的方式:使用工具函数
const arrayUtils = {
    sum: (arr) => arr.reduce((a, b) => a + b, 0)
};

console.log(arrayUtils.sum([1, 2, 3, 4])); // 10

2. this指向问题

const obj = {
    name: 'Object',
    methods: {
        regularMethod: function() {
            console.log(this.name); // 'Object'
        },

        arrowMethod: () => {
            console.log(this.name); // undefined (箭头函数不绑定this)
        }
    }
};

obj.methods.regularMethod(); // 'Object'
obj.methods.arrowMethod();  // undefined

// 在对象方法中使用箭头函数要小心

3. 属性枚举陷阱

const obj = { a: 1, b: 2 };

// 添加不可枚举属性
Object.defineProperty(obj, 'c', {
    value: 3,
    enumerable: false
});

for (const key in obj) {
    console.log(key); // 只输出 'a' 和 'b',不会输出 'c'
}

console.log(Object.keys(obj)); // ['a', 'b']
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c']

📝 最佳实践

1. 对象创建

// ✅ 推荐使用类语法
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `Hello, I'm ${this.name}`;
    }
}

// ✅ 使用工厂函数当需要复杂初始化逻辑
function createUser(config) {
    const defaults = {
        name: 'Anonymous',
        age: 18,
        role: 'user'
    };

    return { ...defaults, ...config };
}

2. 继承

// ✅ 使用extends进行继承
class Animal {
    constructor(name) {
        this.name = name;
    }

    eat() {
        return `${this.name} is eating`;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }

    bark() {
        return `${this.name} is barking`;
    }
}

3. 属性访问

// ✅ 使用访问器进行数据验证
class User {
    constructor(email) {
        this._email = email;
    }

    get email() {
        return this._email;
    }

    set email(newEmail) {
        if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
            this._email = newEmail;
        } else {
            throw new Error('Invalid email format');
        }
    }
}

🎯 小结

  • 掌握了JavaScript对象的本质和多种创建方式
  • 深入理解了原型链的工作机制和继承原理
  • 学会了使用ES6类语法进行面向对象编程
  • 理解了JavaScript中继承和多态的实现
  • 掌握了对象的高级特性和常用设计模式
  • 了解了常见的陷阱和最佳实践

下一步学习ES6新特性概览