JS 入门通关手册(21):原型链:JS 继承的底层原理

在搞懂了 this 指向、构造函数与原型之后,我们终于触及 JS 面向对象的核心 ------原型链。它是 JS 继承的底层实现,是连接构造函数、原型、实例的 "隐形纽带",更是前端面试的高频必考考点。

很多初学者会把 "原型" 和 "原型链" 混淆,一句话分清:原型是单个构造函数与实例的关联(一对一),原型链是多个原型通过 __proto__ 串联起来的 "继承链路"(多对多串联)。

下面全程贴合前两篇风格,无多余格式,直接适配聊天窗口查看,兼顾入门易懂和面试实用性,所有代码可直接复制运行。


一、回顾基础:构造函数、实例、原型的三角关系

理解原型链的前提,必须先吃透这三者的关联,上一篇核心知识点快速回顾:

  1. 每个构造函数(如 Person)都有 prototype 属性,指向它的原型对象;
  2. 每个实例(如 p1)都有 __proto__ 属性(浏览器内置,非标准但常用),指向创建它的构造函数的原型;
  3. 原型对象有 constructor 属性,指回对应的构造函数。

javascript

运行

复制代码
// 构造函数
function Person(name) {
  this.name = name; // 实例属性
}
// 原型添加共享方法
Person.prototype.sayHello = function() {
  console.log(`我是${this.name}`);
};
// 实例对象
const p1 = new Person("张三");

// 三角关系验证(必记)
console.log(p1.__proto__ === Person.prototype); // true(实例 → 原型)
console.log(Person.prototype.constructor === Person); // true(原型 → 构造函数)
console.log(p1.constructor === Person); // true(实例间接指向构造函数)

二、原型链的本质:层层继承的 "原型链路"

1. 什么是原型链?

核心逻辑:每个原型对象本身也是对象 ,它也有自己的 __proto__ 属性,指向它的 "父原型";父原型也有 __proto__,继续向上指向父原型,直到指向 Object.prototype(所有对象的顶层原型),最终 Object.prototype.__proto__ = null,链路终止。

简单说:实例 → 构造函数原型 → 父原型 → ... → Object.prototype → null,这条串联的链路就是原型链。

2. 原型链的形成(代码直观演示)

javascript

运行

复制代码
function Person(name) {
  this.name = name;
}
const p1 = new Person("张三");

// 一步步拆解原型链
console.log(p1.__proto__); // Person.prototype(实例的直接原型)
console.log(p1.__proto__.__proto__); // Object.prototype(顶层原型)
console.log(p1.__proto__.__proto__.__proto__); // null(链路终止)

// 关键结论:所有对象都能继承 Object.prototype 的方法
console.log(p1.toString()); // [object Object](继承自 Object.prototype)
console.log(Person.prototype.hasOwnProperty("sayHello")); // true(继承自 Object.prototype)

3. 原型链的核心作用:实现继承

原型链的本质就是实现 JS 继承 ------ 实例不仅能访问自身属性 / 方法,还能通过原型链,访问原型、父原型上的所有属性 / 方法。

javascript

运行

复制代码
// 父构造函数(Animal)
function Animal(type) {
  this.type = type; // 父类属性
}
// 父类原型方法(共享)
Animal.prototype.eat = function() {
  console.log(`${this.type} 会吃东西`);
};

// 子构造函数(Person),继承 Animal
function Person(name, type) {
  // 继承父类属性(绑定 this,避免全局污染)
  Animal.call(this, type);
  this.name = name; // 子类自身属性
}

// 核心:构建原型链(让子类原型指向父类实例)
Person.prototype = new Animal();
// 必须手动修正 constructor 指向(否则指向 Animal)
Person.prototype.constructor = Person;

// 子类原型添加自身方法
Person.prototype.sayHello = function() {
  console.log(`我是${this.name},属于${this.type}`);
};

// 测试继承效果
const p1 = new Person("张三", "哺乳动物");
p1.name; // 张三(自身属性)
p1.type; // 哺乳动物(继承父类属性)
p1.sayHello(); // 我是张三,属于哺乳动物(自身原型方法)
p1.eat(); // 哺乳动物 会吃东西(继承父类原型方法)

// 查看完整原型链
console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__.__proto__ === Animal.prototype); // true
console.log(p1.__proto__.__proto__.__proto__ === Object.prototype); // true

三、原型链的核心特性(必掌握)

1. 查找规则:自上而下,找到即停止

访问实例的属性 / 方法时,JS 引擎会沿原型链自上而下查找,找到就停止,找不到直到 Object.prototype,仍没有则返回 undefined(方法报错)。

javascript

运行

复制代码
function Person(name) {
  this.name = name;
  // 实例自身方法
  this.sayHello = function() {
    console.log("实例自身的 sayHello");
  };
}
// 子类原型方法
Person.prototype.sayHello = function() {
  console.log("原型上的 sayHello");
};
// 顶层原型方法
Object.prototype.sayHello = function() {
  console.log("Object 原型上的 sayHello");
};

const p1 = new Person("张三");
p1.sayHello(); // 实例自身的 sayHello(找到即停止)

// 删除实例自身方法,继续查找
delete p1.sayHello;
p1.sayHello(); // 原型上的 sayHello

// 删除原型方法,继续向上查找
delete Person.prototype.sayHello;
p1.sayHello(); // Object 原型上的 sayHello

// 删除顶层原型方法,报错
delete Object.prototype.sayHello;
// p1.sayHello(); // 报错:p1.sayHello is not a function

2. 修改原型,影响所有相关实例

所有实例共享同一条原型链,修改原型上的属性 / 方法,无论实例是修改前还是修改后创建的,都会受到影响。

javascript

运行

复制代码
function Person(name) {
  this.name = name;
}
const p1 = new Person("张三");
const p2 = new Person("李四");

// 给原型添加方法(修改原型)
Person.prototype.sayHi = function() {
  console.log(`Hi,我是${this.name}`);
};

// 之前创建的实例也能访问
p1.sayHi(); // Hi,我是张三
p2.sayHi(); // Hi,我是李四

3. 原型链的终点:Object.prototype.proto = null

所有原型链的最终终点都是 nullnull 没有 __proto__,也没有任何属性方法,目的是终止查找,避免无限循环。

javascript

运行

复制代码
console.log(Object.prototype.__proto__); // null
// console.log(Object.prototype.__proto__.__proto__); // 报错:Cannot read properties of null

4. 函数也是对象,也有原型链

JS 中函数也是对象,因此函数也有 __proto__,其原型链为:函数 → Function.prototype → Object.prototype → null

javascript

运行

复制代码
function Person() {}

// 函数的 __proto__ 指向 Function.prototype
console.log(Person.__proto__ === Function.prototype); // true
// Function.prototype 的 __proto__ 指向 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true

四、高频坑点与面试题(重中之重)

坑点 1:原型上的引用类型属性,会被所有实例共享修改

原型上的引用类型(数组、对象),所有实例共享同一个引用,修改一个实例的该属性,会影响所有实例。

javascript

运行

复制代码
function Person(name) {
  this.name = name;
}
// 原型上的引用类型属性
Person.prototype.hobbies = ["吃饭", "睡觉"];

const p1 = new Person("张三");
const p2 = new Person("李四");

p1.hobbies.push("打游戏"); // 修改原型上的数组
console.log(p1.hobbies); // ["吃饭", "睡觉", "打游戏"]
console.log(p2.hobbies); // ["吃饭", "睡觉", "打游戏"] → 被影响!

// 解决方法:引用类型属性放在构造函数中,每个实例单独拥有
function Person(name) {
  this.name = name;
  this.hobbies = ["吃饭", "睡觉"]; // 放在构造函数内
}

坑点 2:原型被覆盖,原型链断裂

直接给构造函数的 prototype 赋值新对象,会覆盖原原型,导致原原型方法丢失,原型链断裂,需手动修正 constructor

javascript

运行

复制代码
function Person(name) {
  this.name = name;
}
// 原原型方法
Person.prototype.sayHello = function() {
  console.log(`我是${this.name}`);
};

// 直接覆盖原型(新对象)
Person.prototype = {
  constructor: Person, // 必须手动修正
  eat: function() {
    console.log("吃饭");
  }
};

const p1 = new Person("张三");
p1.eat(); // 吃饭(新原型方法)
// p1.sayHello(); // 报错:sayHello 丢失(原型链断裂)

面试题 1:instanceof 的底层原理(原型链应用)

instanceof 本质:判断 "实例的原型链上,是否存在某个构造函数的 prototype"。

javascript

运行

复制代码
function Person() {}
const p1 = new Person();

console.log(p1 instanceof Person); // true(p1 原型链有 Person.prototype)
console.log(p1 instanceof Object); // true(p1 原型链有 Object.prototype)

// 模拟 instanceof 原理(面试常考手写)
function myInstanceof(instance, constructor) {
  let proto = instance.__proto__; // 取实例原型
  const prototype = constructor.prototype; // 取构造函数原型
  while (true) {
    if (proto === null) return false; // 链路终止,没找到
    if (proto === prototype) return true; // 找到匹配原型
    proto = proto.__proto__; // 继续向上查找
  }
}

// 测试
console.log(myInstanceof(p1, Person)); // true
console.log(myInstanceof(p1, Object)); // true
console.log(myInstanceof(p1, Array)); // false

面试题 2:原型链查找优先级

优先级:实例自身属性 > 构造函数原型属性 > 父原型属性 > Object.prototype 属性

javascript

运行

复制代码
function Person() {
  this.name = "构造函数内的 name";
}
Person.prototype.name = "原型上的 name";
Object.prototype.name = "Object 原型上的 name";

const p1 = new Person();
console.log(p1.name); // 构造函数内的 name(优先级最高)

面试题 3:Object 和 Function 的原型链关系(高频难题)

记住结论即可,无需深究其底层设计:

javascript

运行

复制代码
// 1. Function 是 Object 的实例
console.log(Function instanceof Object); // true
// 2. Object 是 Function 的实例
console.log(Object instanceof Function); // true
// 3. 两者原型链终点都是 null
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

五、总结(核心要点)

  1. 原型链是 实例 → 原型 → 父原型 → ... → Object.prototype → null 的链路,是 JS 继承的底层原理;
  2. 属性 / 方法查找:自上而下,找到即停止,找不到返回 undefined
  3. 原型上的属性 / 方法被所有实例共享,修改原型会影响所有相关实例;
  4. 所有对象的顶层原型是 Object.prototype,链路终点是 null
  5. instanceof 底层是遍历原型链,判断构造函数的 prototype 是否在实例的原型链上。

下一篇我们讲 ES6 class,它本质是 "构造函数 + 原型" 的语法糖,学会原型链,class 一看就懂。

📌 所有代码可直接复制到浏览器控制台运行,动手实操更易理解。

相关推荐
是Yu欸2 小时前
LangGraph 智能体状态管理与决策
java·javascript·数据库
猫墨*2 小时前
springboot3、knife4j-openapi3配置动态接口版本管理
java·开发语言
weixin_531651812 小时前
Python 渐进式学习指南
开发语言·windows·python
weixin_649555672 小时前
C语言程序设计第四版(何钦铭、颜晖)第八章指针之在数组中查找指定元素
c语言·开发语言
齐鲁大虾2 小时前
如何在HTML/JavaScript中禁用Ctrl+C
前端·javascript·html
add45a2 小时前
C++中的原型模式
开发语言·c++·算法
代码s贝多芬的音符2 小时前
Android NV21 转 YUV 系列格式
android·开发语言·python
2401_844221322 小时前
C++类型推导(auto/decltype)
开发语言·c++·算法
2201_753877792 小时前
高性能计算中的C++优化
开发语言·c++·算法