返回首页

谈谈JavaScript中的原型链?

问题解析

原型链是 JavaScript 实现继承的核心机制。理解原型链对于理解 JavaScript 的对象系统、继承机制以及属性查找过程至关重要。它是 JavaScript 区别于其他基于类(Class-based)语言的重要特性。

核心概念

什么是原型?

在 JavaScript 中,每个对象(除 null 外)都有一个内部属性 [[Prototype]](在浏览器中通过 __proto__ 访问),指向它的原型对象。原型对象也是一个对象,也有自己的原型,这样就形成了一条原型链。

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

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.__proto__ === Function.prototype);          // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);                        // null

详细解答

1. 原型的基本概念

// 每个函数都有一个 prototype 属性
function Animal() {}
console.log(Animal.prototype);
// {
//   constructor: ƒ Animal(),
//   __proto__: Object.prototype
// }

// 通过构造函数创建实例
const cat = new Animal();

// 实例的 __proto__ 指向构造函数的 prototype
cat.__proto__ === Animal.prototype;  // true

// 原型对象的 constructor 指向构造函数
Animal.prototype.constructor === Animal;  // true

2. 属性查找机制

function Person(name) {
    this.name = name;  // 实例属性
}

Person.prototype.age = 25;  // 原型属性

const person = new Person('Alice');

// 属性查找过程:
// 1. 先在实例自身查找
console.log(person.name);   // "Alice"(实例属性)

// 2. 实例上没有,去原型上查找
console.log(person.age);    // 25(原型属性)

// 3. 原型上没有,继续向上查找(原型链)
console.log(person.toString());  // "[object Object]"(Object.prototype 上的方法)

// 4. 原型链顶端是 null,找不到则返回 undefined
console.log(person.notExist);  // undefined

3. 原型链的终点

// 原型链:instance -> Constructor.prototype -> Object.prototype -> null

function Dog() {}
const dog = new Dog();

console.log(dog.__proto__ === Dog.prototype);              // true
console.log(Dog.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);                   // null

// 完整的原型链
dog -> Dog.prototype -> Object.prototype -> null

4. 函数也是对象

// 函数是 Function 的实例
function fn() {}
console.log(fn.__proto__ === Function.prototype);  // true

// Function 本身也是函数
console.log(Function.__proto__ === Function.prototype);  // true

// Function.prototype 是特殊的函数对象
console.log(typeof Function.prototype);  // "function"
console.log(Function.prototype.__proto__ === Object.prototype);  // true

// 完整的函数原型链
fn -> Function.prototype -> Object.prototype -> null

深入理解

1. 原型链图示

┌─────────────────────────────────────────────────────────────────┐
│                         原型链结构                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐         ┌─────────────────┐                    │
│  │   person    │         │  Person.prototype│                   │
│  │  {name:...} │────────→│  {constructor:  │                   │
│  │  __proto__  │         │   sayHello: fn} │                   │
│  └─────────────┘         └─────────────────┘                    │
│                                   │                              │
│                                   ↓                              │
│                          ┌─────────────────┐                    │
│                          │ Object.prototype│                    │
│                          │ {toString: fn,  │                    │
│                          │  hasOwnProperty:│                    │
│                          │   fn, ...}      │                    │
│                          └─────────────────┘                    │
│                                   │                              │
│                                   ↓                              │
│                                null                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

2. instanceof 的原理

// instanceof 检查的是原型链上是否存在构造函数的 prototype

function Person() {}
const person = new Person();

console.log(person instanceof Person);    // true
console.log(person instanceof Object);    // true
console.log(person instanceof Function);  // false

// instanceof 的实现原理
function myInstanceof(instance, Constructor) {
    let proto = instance.__proto__;
    const prototype = Constructor.prototype;

    while (proto) {
        if (proto === prototype) return true;
        proto = proto.__proto__;
    }
    return false;
}

console.log(myInstanceof(person, Person));  // true
console.log(myInstanceof(person, Object));  // true

3. 原型链与继承

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

Animal.prototype.speak = function() {
    console.log(`${this.name} makes a sound`);
};

// 子类
function Dog(name, breed) {
    Animal.call(this, name);  // 借用构造函数继承实例属性
    this.breed = breed;
}

// 建立原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;  // 修正 constructor

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

const dog = new Dog('Buddy', 'Golden Retriever');

dog.speak();  // "Buddy makes a sound"(继承自 Animal)
dog.bark();   // "Buddy barks"(Dog 自己的方法)

console.log(dog instanceof Dog);     // true
console.log(dog instanceof Animal);  // true
console.log(dog instanceof Object);  // true

4. ES6 Class 与原型链

// ES6 Class 是原型链的语法糖
class Animal {
    constructor(name) {
        this.name = name;
    }

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

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

    bark() {
        console.log(`${this.name} barks`);
    }
}

const dog = new Dog('Buddy', 'Golden Retriever');

// 本质上仍然是原型链
dog.__proto__ === Dog.prototype;              // true
Dog.prototype.__proto__ === Animal.prototype; // true
Animal.prototype.__proto__ === Object.prototype; // true

最佳实践

1. 创建对象的方式与原型链

// 方式1:对象字面量
const obj1 = {};
obj1.__proto__ === Object.prototype;  // true

// 方式2:Object.create()
const obj2 = Object.create(null);  // 没有原型
const obj3 = Object.create(Object.prototype);  // 等同于 {}
const obj4 = Object.create({ foo: 'bar' });  // 指定原型

// 方式3:构造函数
function Person(name) {
    this.name = name;
}
const person = new Person('Alice');

// 方式4:class(ES6)
class Animal {}
const animal = new Animal();

2. 检查属性的位置

function Person(name) {
    this.name = name;
}
Person.prototype.age = 25;

const person = new Person('Alice');

// hasOwnProperty:检查是否是实例自身属性
console.log(person.hasOwnProperty('name'));   // true
console.log(person.hasOwnProperty('age'));    // false

// in 操作符:检查原型链上是否存在
console.log('name' in person);   // true
console.log('age' in person);    // true
console.log('toString' in person); // true

// Object.keys:只返回自身可枚举属性
console.log(Object.keys(person));  // ["name"]

// for...in:遍历自身和原型链上的可枚举属性
for (let key in person) {
    console.log(key);  // "name", "age"
}

// 使用 hasOwnProperty 过滤原型属性
for (let key in person) {
    if (person.hasOwnProperty(key)) {
        console.log(key);  // "name"
    }
}

3. 避免修改内置原型

// ❌ 不推荐:修改内置对象的原型
Array.prototype.myMethod = function() {
    // ...
};

// ✅ 推荐:使用工具函数或扩展类
class MyArray extends Array {
    myMethod() {
        // ...
    }
}

面试要点

  1. 什么是原型链?

    • 每个对象都有一个 __proto__ 属性指向其原型
    • 原型对象也有自己的原型,形成链式结构
    • 原型链的终点是 Object.prototype.__proto__ 即 null
  2. __proto__prototype 的区别?

    • __proto__:每个对象都有,指向该对象的原型
    • prototype:只有函数有,是构造函数创建实例时使用的原型
    • instance.__proto__ === Constructor.prototype
  3. 属性查找的过程?

    • 先在对象自身查找
    • 找不到则去原型上查找
    • 沿着原型链一直向上查找
    • 找到则返回,找不到返回 undefined
  4. instanceof 的原理?

    • 检查构造函数的 prototype 是否出现在实例的原型链上
    • 顺着 __proto__ 一直向上查找
  5. 如何实现继承?

    • 经典方式:Sub.prototype = Object.create(Super.prototype)
    • ES6 Class:class Sub extends Super
    • 都需要修正 constructor 指向
  6. 原型链的终点是什么?

    • Object.prototype.__proto__ === null
    • null 表示原型链的终点
  7. 为什么 Function.prototype 是函数?

    • 这是 JavaScript 引擎的特殊处理
    • typeof Function.prototype === "function"
    • 但它的行为更像普通对象