Skip to content
On This Page

JavaScript原型和原型链

1. 基本概念

  1. 原型(Prototype)

    每个 JavaScript 对象都有一个内部属性叫做 [[Prototype]],这通常可以通过 __proto__ 来访问。这个 [[Prototype]] 指向另一个对象,从而形成了所谓的原型链。

  2. 原型链

    JavaScript 的原型链是一个机制,通过它可以实现对象间的属性继承。当访问一个对象的属性时,JavaScript 引擎首先会看这个属性是否存在于对象自身。如果不存在,那么它会沿着这个对象的原型链向上查找,直到找到该属性或到达原型链的顶端(通常是 Object.prototype),在这种情况下返回 undefined

  3. 构造函数与原型

    在 JavaScript 中,函数都有一个 prototype 属性,这个属性指向一个对象,这个对象的作用是当使用 new 操作符创建一个实例时,作为该实例的原型。例如:

    javascript
    function Person(name) {
      this.name = name;
    }
    Person.prototype.sayHello = function() {
      console.log('Hello, ' + this.name);
    };
    let alice = new Person('Alice');
    alice.sayHello(); // 输出 "Hello, Alice"
  4. 继承与原型链

    JavaScript 通过原型链实现继承,即一个对象可以继承另一个对象的属性和方法。在上面的例子中,alice 继承了 Person.prototype 上的方法 sayHello

  5. Object.prototype

    JavaScript 中所有对象最终都可以追溯到 Object.prototype。这是原型链的顶端,也是所有对象最终会继承的原型对象。Object.prototype 上的方法比如 toString()hasOwnProperty() 等都可以在所有对象上调用。

  6. class 语法糖

    写过 ES5 的原型继承代码,一定知道那其实是有些繁琐的。为了简化这一过程,ES6 引入了 class 语法糖,其实质还是基于原型的继承。以下是一个简单示例:

    javascript
    class Animal {
      constructor(name) {
        this.name = name;
      }
      
      speak() {
        console.log(this.name + ' makes a noise.');
      }
    }
    
    class Dog extends Animal {
      speak() {
        console.log(this.name + ' barks.');
      }
    }
    
    let dog = new Dog('Rover');
    dog.speak(); // 输出 "Rover barks."
  7. 对于原型链的调试

    在调试 JavaScript 代码时,我们可以使用 console.log 或开发者工具来查看对象的 __proto__ 属性,从而了解它们在原型链中的位置。

2. 拓展知识

2.1 原型修改

原型修改 (Prototype Modification):更改原型对象的内容,例如添加或删除方法或属性。

假设我们有一个构造函数 Person,我们可以通过其原型 Person.prototype 添加方法或属性:

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

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person('Alice');
alice.sayHello(); // 输出: Hello, my name is Alice

2.2 原型重写

原型重写 (Prototype Reassignment):将对象的原型更改为一个全新的对象。

有时候,我们可能想完全替换一个对象的原型。这被称为原型重写:

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

// 原始原型
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

// 新的原型对象
Person.prototype = {
  greet: function() {
    console.log(`Greetings, my name is ${this.name}`);
  }
};

const bob = new Person('Bob');
bob.greet(); // 输出: Greetings, my name is Bob

请注意,通过这种方式创建的新对象 bob 现在不再具有 sayHello 方法,因为我们已经完全重写了原型。只会包含新原型中定义的 greet 方法。

3. 原型链指向

在 JavaScript 中,所有对象都是通过原型链(prototype chain)来实现继承的。简单来说,原型链是一种让对象共享属性和方法的机制。每个对象都有一个内部链接(proto),指向其构造函数的原型对象(prototype)。 原型对象本身也可以有一个 proto,这样一直链接下去,形成一个链状结构,直到指向 null 为止。

3.1 对象的创建与原型链

每当我们创建一个对象(无论是通过对象字面量还是通过构造函数),该对象会自动获得一个 proto 属性,指向其构造函数的 prototype。

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

const student = new Person('Alex');
console.log(student.__proto__ === Person.prototype);  // true

3.2 原型链查询机制

JavaScript 会通过原型链来查找属性或方法。假设我们要访问对象 student 的 name 属性,它首先会在 student 对象自身查找,如果找不到,该查找操作会沿着 proto 链条继续向上,直到找到为止,或者到链条末尾(即 null)停止。

3.3 原型对象的原型

由于原型对象本身也是一个对象,它也有自己的 proto 属性。一般情况下,函数的 prototype 对象的 proto 会指向 Object.prototype, 而 Object.prototype 的 proto 则为 null。

javascript
console.log(Person.prototype.__proto__ === Object.prototype);  // true
console.log(Object.prototype.__proto__);  // null

3.4 Function 和 Object 的特殊关系

在 JavaScript 中,函数也是对象,因此 Function 的 proto 是 Function.prototype。值得注意的是,Function.prototype 本身是一个函数对象(特殊的函数对象,通常被称为 “空函数” 或 “基础函数”),其 proto 指向 Object.prototype。 这种相互指向的关系构成了 JavaScript 的原型链的基础。

javascript
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true

3.5 原型链与继承

原型链是实现继承的一种重要方式。例如,我们可以通过构造函数继承或原型继承来实现对象的属性和方法复用。

javascript
function Animal(name) {
  this.name = name;
}
Animal.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
}

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const dog = new Dog('Buddy', 'Labrador');
dog.sayHello();  // Hello, I'm Buddy