Skip to content

原型链是 JavaScript 中一个重要的概念,它是对象之间继承机制的基础。通过原型链,JavaScript 实现了基于对象的继承。理解原型链有助于深入理解 JavaScript 的继承、对象构造、属性查找等机制,也是常见面试考点之一。

一、原型链的概念

每个 JavaScript 对象都有一个内部属性叫 [[Prototype]](可以通过 __proto__ 访问),它指向另一个对象,这个对象就是该对象的原型。如果原型对象本身也有原型,那么就会形成一条原型链,直到原型链的终点是 null。在查找对象的属性时,如果对象本身没有这个属性,JavaScript 会沿着原型链向上查找,直到找到该属性或到达 null 为止。

关键点:

  1. 每个对象都有一个原型:对象的原型通过 [[Prototype]] 属性(或 __proto__)连接到另一个对象。
  2. 构造函数的 prototype:每个构造函数(函数本身是对象)都有一个 prototype 属性,指向该构造函数创建的对象的原型。
  3. 对象属性查找顺序:当访问对象的属性时,JavaScript 会首先在该对象本身上查找,如果找不到,则沿着原型链向上查找,直到找到属性或到达 null
  4. 终点是 null:原型链的最后一个对象的 __proto__ 指向 null,即原型链的终点。

二、原型链示例

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

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

在这个例子中:

  • alice 是通过 Person 构造函数创建的对象,它的原型指向 Person.prototype
  • Person.prototype 具有 sayHello 方法,alice 通过原型链可以访问该方法。
  • Person.prototype 本身的原型是 Object.prototype,它是所有对象的原型,原型链的终点是 null

三、原型链的结构

  1. 对象实例:每个对象实例都有一个隐式属性 __proto__,指向该对象的原型。
  2. 构造函数的 prototype 属性:构造函数拥有一个 prototype 属性,指向其原型对象,这个原型对象将成为由该构造函数创建的实例的原型。

原型链的图示:

alice.__proto__  ->  Person.prototype  ->  Object.prototype  ->  null

四、常见的原型链相关面试题

1. 解释 JavaScript 中的原型链是什么?

回答要点

  • 原型链是对象的继承机制。每个对象都有一个 __proto__ 指向其原型,原型链会沿着 __proto__ 一直向上查找,直到 null
  • 当访问对象的属性时,JavaScript 会首先在对象本身查找,如果找不到,则沿着原型链依次向上查找。

2. 如何访问对象的原型?

回答要点

  • 使用 __proto__ 来访问对象的原型(不推荐在生产环境中使用,属于非标准)。
  • 使用 Object.getPrototypeOf(obj) 获取对象的原型,这是标准方法。
javascript
const obj = {};
console.log(obj.__proto__ === Object.prototype);  // true
console.log(Object.getPrototypeOf(obj) === Object.prototype);  // true

3. 构造函数与原型链的关系是什么?

回答要点

  • 构造函数创建对象时,生成的对象会继承构造函数的 prototype 属性。
  • 构造函数的 prototype 属性指向一个对象,这个对象包含该构造函数实例共享的方法或属性。
  • 实例对象的 __proto__ 指向构造函数的 prototype
javascript
function Person() {}
const p = new Person();

console.log(p.__proto__ === Person.prototype);  // true
console.log(Person.prototype.constructor === Person);  // true

4. 原型链如何影响属性查找?

回答要点

  • 当访问对象的属性时,JavaScript 会首先检查对象自身的属性,如果没有找到,则沿着原型链向上查找,直到找到该属性或到达 null 为止。
  • 如果在原型链上找到了该属性,它将返回该属性的值。
  • 如果在整个原型链上都没有找到该属性,返回 undefined
javascript
function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  console.log('Hello');
};

const alice = new Person('Alice');
console.log(alice.name);         // "Alice"(在对象自身上找到)
alice.sayHello();                // "Hello"(在原型链上找到)

5. 什么是 Object.prototype

回答要点

  • Object.prototype 是所有对象的原型,所有通过构造函数创建的对象都继承自 Object.prototype
  • 原型链的终点是 Object.prototypeObject.prototype.__proto__ === null
javascript
console.log(Object.prototype.__proto__ === null);  // true

6. 什么是 prototype__proto__?它们之间的区别是什么?

回答要点

  • prototype:是构造函数的属性,指向一个对象,该对象将作为由该构造函数创建的实例的原型。
  • __proto__:是对象的属性,指向创建该对象的构造函数的 prototype,它表示对象的原型。
javascript
function Person() {}
const p = new Person();

console.log(Person.prototype); // 构造函数的 prototype
console.log(p.__proto__);      // 实例对象的 __proto__,指向 Person.prototype

7. 如何创建一个没有原型的对象?

回答要点

  • 可以使用 Object.create(null) 来创建一个没有原型的对象。此对象不继承 Object.prototype 的属性和方法。
javascript
const obj = Object.create(null);
console.log(Object.getPrototypeOf(obj));  // null

8. 如何判断一个属性是对象自身的还是从原型继承的?

回答要点

  • 可以使用 Object.hasOwnProperty() 方法来判断一个属性是否为对象自身的属性,而非从原型链继承的属性。
javascript
const obj = { name: 'Alice' };
console.log(obj.hasOwnProperty('name'));  // true
console.log(obj.hasOwnProperty('toString'));  // false(继承自 Object.prototype)

9. 什么是 instanceof 运算符,它是如何工作的?

回答要点

  • instanceof 用来判断一个对象是否是某个构造函数的实例。
  • instanceof 通过检查对象的原型链,判断对象的 __proto__ 是否在构造函数的 prototype 链上。
javascript
function Person() {}
const p = new Person();

console.log(p instanceof Person);  // true
console.log(p instanceof Object);  // true(因为 p 的原型链上有 Object.prototype)

10. 如何在 JavaScript 中实现继承?

回答要点

  • 使用构造函数和 prototype 来实现继承。
  • 使用 Object.create() 来实现继承。

原型继承

javascript
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  console.log(this.name + ' makes a sound.');
};

function Dog(name) {
  Animal.call(this, name);  // 继承属性
}
Dog.prototype = Object.create(Animal.prototype);  // 继承方法
Dog.prototype.constructor = Dog;

const dog = new Dog('Rex');
dog.speak();  // "Rex makes a sound."

总结

原型链是 JavaScript 中对象继承的基础,它通过每个对象的 __proto__ 属性指向原型对象,从而实现属性和方法的继承。了解原型链的结构及其工作机制是深入理解 JavaScript 继承和面向对象编程的关键。常见面试题围绕如何访问原型、属性查找机制、prototype__proto__ 的区别以及继承机制等展开。在实际

手写instanceof

js
const instanceof_ = (A, B) => {
  // 前置判断(A 是对象 B 是函数),不符合直接访问false
  if (typeof A !== 'object' || A === null || typeof B !== 'function') {
    return false;
  };
  // 取构造函数创建实例的原型
  const prototype = B.prototype;
  // 取对象实例的原型
  let __proto__ = A.__proto__;
  while (true) {
    // 假设找到顶端了,返回false
    if (__proto__ === null) {
      return false;
    };

    // 假设相等,返回true
    if (prototype === __proto__) {
      return true;
    };

    // 一直顺着__proto__往上找
    __proto__ = __proto__.__proto__;
  }
}

解释

js
A instanceof B
  1. A 是一个实例对象
  2. B 是一个构造器函数 在javascript中只有函数才有prototype属性