知识篇 -- JS原型与原型链

Ray Shine 2024/2/26 JavaScript基础知识继承

JavaScript是一种基于原型的面向对象语言,与传统的基于类的面向对象语言(如Java、C++)有着本质的区别。理解原型 (Prototype)原型链 (Prototype Chain) 是掌握JavaScript继承机制、对象创建和属性查找的关键。

# 一、什么是原型 (Prototype)? 核心

在JavaScript中,每个函数(除了箭头函数)在创建时都会自动拥有一个 prototype 属性,这个属性通常指向一个对象,我们称之为原型对象

  • 作用:原型对象是用来实现继承和共享属性与方法的。当使用 new 关键字调用一个函数(作为构造函数)来创建实例时,新创建的实例会隐式地链接到构造函数的原型对象。
  • __proto__ 属性:每个JavaScript对象(除了 null)都有一个内部属性 [[Prototype]],在ES5及之前可以通过 __proto__ 属性访问(现在推荐使用 Object.getPrototypeOf()Object.setPrototypeOf())。这个 __proto__ 指向创建该对象的构造函数的原型对象。

示例

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

// Person.prototype 就是原型对象
Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person("Alice");
const bob = new Person("Bob");

alice.greet(); // Hello, my name is Alice
bob.greet();   // Hello, my name is Bob

console.log(alice.__proto__ === Person.prototype); // true
console.log(bob.__proto__ === Person.prototype);   // true

解释greet 方法定义在 Person.prototype 上,而不是每个 Person 实例上。这样,所有 Person 实例都可以共享同一个 greet 方法,节省了内存,也实现了方法的复用。

# 二、什么是原型链 (Prototype Chain)? 继承机制

当试图访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript会沿着其内部的 [[Prototype]] 链接向上查找,直到找到该属性或到达原型链的顶端(null)。这个查找过程形成的链条就是原型链

  • 链的构成
    • 实例对象__proto__ 指向 构造函数prototype 对象。
    • 构造函数prototype 对象 的 __proto__ 指向 Object.prototype
    • Object.prototype__proto__ 指向 null,标志着原型链的终点。

图示 (简化版):

alice (实例)
  ↓ __proto__
Person.prototype (原型对象)
  ↓ __proto__
Object.prototype (所有对象的最终原型)
  ↓ __proto__
null (原型链终点)

示例

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

function Dog(name, breed) {
    Animal.call(this, name); // 继承Animal的属性
    this.breed = breed;
}

// 关键步骤:将Dog的原型设置为Animal的实例,实现原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor指向

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

const myDog = new Dog("Buddy", "Golden Retriever");
myDog.eat();  // Buddy is eating. (通过原型链找到Animal.prototype上的eat方法)
myDog.bark(); // Buddy is barking. (Dog.prototype上的bark方法)
console.log(myDog.name); // Buddy (Dog实例自身的属性)

解释:当 myDog.eat() 被调用时,JavaScript首先在 myDog 实例上查找 eat 方法,没有找到。然后沿着 myDog.__proto__ 向上查找,找到 Dog.prototype,也没有找到。再沿着 Dog.prototype.__proto__ 向上查找,找到 Animal.prototype,找到了 eat 方法并执行。

# 三、constructor 属性 重要

每个原型对象都有一个 constructor 属性,它指向关联的构造函数。

  • 作用:通过实例的 constructor 属性可以追溯到创建它的构造函数。
  • 注意:当通过 Object.create() 改变原型时,需要手动修复 constructor 的指向,否则它会指向旧的构造函数。
    function MyFunc() {}
    const obj = new MyFunc();
    console.log(obj.constructor === MyFunc); // true
    
    function AnotherFunc() {}
    AnotherFunc.prototype = Object.create(MyFunc.prototype);
    const anotherObj = new AnotherFunc();
    console.log(anotherObj.constructor === AnotherFunc); // false (指向MyFunc)
    // 修复:AnotherFunc.prototype.constructor = AnotherFunc;
    // console.log(anotherObj.constructor === AnotherFunc); // true
    

# 四、原型链的实际应用 应用

  • 继承:通过原型链,子对象可以继承父对象的属性和方法。
  • 代码复用:将共享的方法放在原型对象上,所有实例都可以复用,减少内存占用。
  • 属性查找:理解属性查找机制,有助于优化代码和避免不必要的性能开销。

# 五、现代JavaScript中的原型与继承 ES6+

虽然原型链是JavaScript继承的底层机制,但在ES6之后,我们通常使用 class 关键字来定义类和实现继承,这只是原型链的语法糖,底层依然是基于原型实现的。

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

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 调用父类的构造函数
        this.breed = breed;
    }
    bark() {
        console.log(`${this.name} is barking.`);
    }
}

const myDog = new Dog("Buddy", "Golden Retriever");
myDog.eat();  // Buddy is eating.
myDog.bark(); // Buddy is barking.

解释class 语法使得JavaScript的继承看起来更像传统的面向对象语言,但其内部仍然是基于原型链的。extends 关键字会自动处理原型链的设置和 constructor 的修复。

理解原型和原型链是深入掌握JavaScript的必经之路。它揭示了JavaScript对象之间如何共享行为和数据,是理解许多高级概念(如闭包、this 指向)的基础。

最后更新时间: 2025/11/20 22:59:30
ON THIS PAGE