JavaScript 原型链详解

1. 原型链的基本概念

1.1 什么是原型链?

原型链是JavaScript实现继承和共享属性的核心机制。每个对象都有一个内部属性[[Prototype]](可通过__proto__访问),指向它的原型对象。原型对象也有自己的原型,这样层层链接形成原型链。

// 基本示例
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

2. 原型链的构成

2.1 构造函数、原型对象和实例的关系

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

// 原型对象
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

// 创建实例
const person1 = new Person('Alice');

// 原型链关系
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true

2.2 完整的原型链图示

实例对象
    ↓ __proto__
构造函数.prototype
    ↓ __proto__
Object.prototype
    ↓ __proto__
null

3. 原型链的查找机制

3.1 属性访问的过程

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

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

const cat = new Animal('Tom');

// 属性查找过程
console.log(cat.name); // 1. 查找自身属性 ✓
console.log(cat.eat);  // 2. 自身没有 → 查找 Animal.prototype ✓
console.log(cat.toString); // 3. 继续向上 → Object.prototype ✓
console.log(cat.nonExistent); // 4. 直到null → undefined

3.2 属性屏蔽

function Parent() {
    this.value = "parent";
}
Parent.prototype.getValue = function() {
    return this.value;
};

function Child() {
    this.value = "child";
}

Child.prototype = new Parent();

const obj = new Child();
console.log(obj.value); // "child"(自身属性屏蔽原型属性)
console.log(obj.getValue()); // "child"(方法访问的是自身value)

4. 原型继承的实现方式

4.1 原型链继承(经典方式)

// 父类
function Animal(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}

Animal.prototype.sayName = function() {
    console.log(`My name is ${this.name}`);
};

// 子类
function Dog(name, age) {
    Animal.call(this, name); // 继承属性
    this.age = age;
}

// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 添加子类方法
Dog.prototype.bark = function() {
    console.log('Woof!');
};

const dog1 = new Dog('Buddy', 2);
dog1.colors.push('yellow');

const dog2 = new Dog('Max', 3);
console.log(dog2.colors); // ['red', 'blue'] 不受影响

4.2 ES6的class语法(语法糖)

class Animal {
    constructor(name) {
        this.name = name;
    }
    
    speak() {
        console.log(`${this.name} makes a noise`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }
    
    speak() {
        super.speak(); // 调用父类方法
        console.log(`${this.name} barks`);
    }
}

const dog = new Dog('Rex', 'German Shepherd');
dog.speak();

5. 原型链的应用场景

5.1 方法共享(节省内存)

function Car(model) {
    this.model = model;
    this.mileage = 0;
}

// 所有实例共享这些方法
Car.prototype.drive = function(km) {
    this.mileage += km;
    console.log(`Driven ${km}km, total: ${this.mileage}km`);
};

Car.prototype.getInfo = function() {
    return `${this.model}: ${this.mileage}km`;
};

const car1 = new Car('Toyota');
const car2 = new Car('Honda');

// 方法在原型上,不在每个实例中
console.log(car1.drive === car2.drive); // true

5.2 扩展内置对象

// 为数组添加自定义方法
if (!Array.prototype.customMap) {
    Array.prototype.customMap = function(callback) {
        const result = [];
        for (let i = 0; i < this.length; i++) {
            result.push(callback(this[i], i, this));
        }
        return result;
    };
}

const arr = [1, 2, 3];
const doubled = arr.customMap(x => x * 2);
console.log(doubled); // [2, 4, 6]

// 注意:小心原型污染,最好使用自己的工具类

5.3 实现混入(Mixin)模式

// 定义多个Mixin
const CanEat = {
    eat(food) {
        console.log(`${this.name} is eating ${food}`);
    }
};

const CanSleep = {
    sleep() {
        console.log(`${this.name} is sleeping`);
    }
};

// 对象混入
function mixin(target, ...sources) {
    Object.assign(target, ...sources);
}

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

mixin(Cat.prototype, CanEat, CanSleep);

const cat = new Cat('Whiskers');
cat.eat('fish');
cat.sleep();

6. 原型链相关方法和属性

6.1 原型相关操作

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

const person = new Person('Alice');

// 1. 获取原型
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
console.log(person.__proto__ === Person.prototype); // true(非标准)

// 2. 设置原型
const newProto = { sayHello: function() { console.log('Hello') } };
Object.setPrototypeOf(person, newProto);

// 3. 创建指定原型的对象
const obj = Object.create(Person.prototype);

// 4. 检查原型链
console.log(person instanceof Person); // true
console.log(Person.prototype.isPrototypeOf(person)); // true

// 5. 检查自身属性 vs 原型链属性
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false

// 6. 获取所有属性(包括原型链)
for (let key in person) {
    console.log(key);
}

7. 原型链的注意事项

7.1 性能考虑

// 避免过长的原型链
function A() {}
function B() {}
function C() {}
function D() {}

// 创建过长的原型链
D.prototype = new C();
C.prototype = new B();
B.prototype = new A();

const d = new D();

// 属性查找会逐级向上,影响性能
console.log(d.someProperty); // 需要查找4层

7.2 原型污染问题

// 问题:修改原型会影响所有实例
function User() {}
const user1 = new User();
const user2 = new User();

User.prototype.greet = function() {
    console.log('Hello');
};

// 所有实例都能访问新方法
user1.greet(); // Hello
user2.greet(); // Hello

// 更危险的:修改内置原型
Array.prototype.push = function() {
    console.log('Array push is hacked!');
};

const arr = [];
arr.push(1); // 输出:Array push is hacked!

7.3 安全的原型扩展

// 使用组合代替继承
const animalBehaviors = {
    eat() { /* ... */ },
    sleep() { /* ... */ }
};

function createAnimal(name) {
    const animal = { name };
    
    // 将方法复制到实例,而不是放在原型上
    Object.assign(animal, animalBehaviors);
    
    return animal;
}

const cat = createAnimal('Cat');
cat.eat();

8. 实际应用案例

8.1 实现一个简单的EventEmitter

function EventEmitter() {
    this.events = {};
}

EventEmitter.prototype.on = function(event, listener) {
    if (!this.events[event]) {
        this.events[event] = [];
    }
    this.events[event].push(listener);
};

EventEmitter.prototype.emit = function(event, ...args) {
    if (this.events[event]) {
        this.events[event].forEach(listener => listener.apply(this, args));
    }
};

// 使用
const emitter = new EventEmitter();
emitter.on('message', function(msg) {
    console.log('Received:', msg);
});
emitter.emit('message', 'Hello World');

8.2 实现插件系统

function PluginSystem() {
    this.plugins = [];
}

PluginSystem.prototype.use = function(plugin) {
    this.plugins.push(plugin);
    
    if (plugin.install) {
        plugin.install(this);
    }
};

PluginSystem.prototype.run = function() {
    this.plugins.forEach(plugin => {
        if (plugin.run) {
            plugin.run(this);
        }
    });
};

// 插件
const loggerPlugin = {
    install(system) {
        system.log = function(msg) {
            console.log(`[LOG]: ${msg}`);
        };
    },
    run(system) {
        system.log('System is running');
    }
};

const system = new PluginSystem();
system.use(loggerPlugin);
system.run();

总结

原型链的核心要点:

  1. 继承机制:通过__proto__链接实现属性和方法的继承
  2. 查找顺序:从自身开始,沿原型链向上查找
  3. 共享特性:原型上的属性和方法被所有实例共享
  4. 动态性:修改原型会影响所有已存在的实例

最佳实践:

  1. 使用Object.create()创建具有指定原型的对象
  2. 优先使用ES6的class语法
  3. 避免修改内置对象的原型
  4. 对于复杂继承,考虑组合模式代替原型继承
  5. 使用Object.getPrototypeOf()而不是__proto__

理解原型链是掌握JavaScript面向对象编程的关键,它解释了JavaScript中"一切皆对象"的本质。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐