原型与继承,什么是原型链?,如何实现继承?
一、原型(Prototype):JS 继承的核心基石
JavaScript 是基于原型(Prototype-Based) 的面向对象语言(而非类基),核心逻辑是:每个对象都有一个「原型对象」,对象可继承原型上的属性 / 方法。
1. 原型的核心概念
| 关键属性 / 方法 |
作用 |
[[Prototype]] |
对象的隐式原型(内部属性),指向其原型对象(浏览器中可通过 __proto__ 访问) |
prototype |
函数特有的属性,指向「原型对象」(只有函数有,普通对象无) |
constructor |
原型对象上的属性,指向创建该原型的构造函数(如 Person.prototype.constructor === Person) |
2. 核心规则
- **所有对象(除
Object.create(null))都有 [[Prototype]]**:包括普通对象、数组、函数、实例;
- 函数的
prototype 是原型对象:构造函数通过 prototype 定义 “共享属性 / 方法”,实例通过 [[Prototype]] 继承;
- 原型对象也是普通对象:因此原型对象也有自己的
[[Prototype]],这是 “原型链” 的基础。
示例:原型的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Person(name) { this.name = name; }
Person.prototype.sayHi = function() { console.log(`Hi, I'm ${this.name}`); };
const p1 = new Person("张三"); const p2 = new Person("李四");
p1.sayHi(); p2.sayHi();
console.log(p1.__proto__ === Person.prototype);
console.log(Person.prototype.constructor === Person);
|
二、原型链(Prototype Chain):继承的查找机制
原型链是对象的 [[Prototype]] 依次指向原型对象,直到 null 的链式结构,核心作用是:解析对象的属性 / 方法访问—— 当访问对象的属性 / 方法时,JS 引擎先在对象自身查找,找不到则沿原型链向上查找,直到 null(原型链终点)。
1. 原型链的结构(以 Person 实例为例)
1 2 3 4
| p1(实例) [[Prototype]] → Person.prototype(原型对象) [[Prototype]] → Object.prototype(顶级原型) [[Prototype]] → null(原型链终点)
|
2. 原型链的查找规则
- 自身优先:先查对象自身的属性 / 方法,找到则直接使用;
- 向上追溯:自身没有则沿
[[Prototype]] 找原型对象的属性 / 方法;
- 终点终止:直到
Object.prototype(其 [[Prototype]] 是 null),若仍未找到,返回 undefined(方法则报错)。
示例:原型链查找
1 2 3 4 5 6 7 8
| console.log(p1.name); console.log(p1.sayHi); console.log(p1.toString); console.log(p1.xxx);
console.log(Object.prototype.__proto__);
|
原型链的核心特性
- 继承性:实例可继承原型链上所有属性 / 方法(如
p1.toString() 继承自 Object.prototype);
- 共享性:原型上的方法 / 属性被所有实例共享(节省内存);
- 单向性:只能从对象向原型链上游查找,原型无法访问实例的属性。
三、JS 实现继承的常用方式(从经典到现代)
JS 没有 class 关键字(ES6 前),通过原型链实现继承,以下是主流方案(按推荐度排序):
1. ES6 class extends(推荐,语法糖)
ES6 新增 class/extends,本质是原型链的语法糖,更贴近类基语言的写法,易读性高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Person { constructor(name) { this.name = name; } sayHi() { console.log(`Hi, ${this.name}`); } }
class Student extends Person { constructor(name, grade) { super(name); this.grade = grade; } study() { console.log(`${this.name} is studying in grade ${this.grade}`); } }
const s1 = new Student("王五", 6); s1.sayHi(); s1.study(); console.log(s1.__proto__ === Student.prototype); console.log(Student.prototype.__proto__ === Person.prototype);
|
2. 原型链继承(基础方案,有缺陷)
核心:子类的 prototype 指向父类的实例,让子类实例通过原型链继承父类属性 / 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function Person(name) { this.name = name; this.hobbies = ["reading"]; } Person.prototype.sayHi = function() { console.log(`Hi, ${this.name}`); };
function Student(name, grade) { this.grade = grade; }
Student.prototype = new Person();
Student.prototype.constructor = Student;
const s1 = new Student("张三", 5); const s2 = new Student("李四", 6); s1.hobbies.push("swimming"); console.log(s2.hobbies);
|
缺陷:父类的引用类型属性会被所有子类实例共享,修改一个实例的属性会影响其他实例。
3. 构造函数继承(解决引用类型共享问题)
核心:在子类构造函数中调用父类构造函数(call/apply),实现实例属性的独立继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Person(name) { this.name = name; this.hobbies = ["reading"]; } Person.prototype.sayHi = function() { };
function Student(name, grade) { Person.call(this, name); this.grade = grade; }
const s1 = new Student("张三", 5); const s2 = new Student("李四", 6); s1.hobbies.push("swimming"); console.log(s2.hobbies); console.log(s1.sayHi);
|
缺陷:只能继承父类实例属性,无法继承父类原型上的方法(复用性低)。
4. 组合继承(原型链 + 构造函数,经典方案)
核心:结合原型链继承(继承原型方法)和构造函数继承(继承实例属性),弥补各自缺陷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function Person(name) { this.name = name; this.hobbies = ["reading"]; } Person.prototype.sayHi = function() { console.log(`Hi, ${this.name}`); };
function Student(name, grade) { Person.call(this, name); this.grade = grade; } Student.prototype = new Person(); Student.prototype.constructor = Student;
const s1 = new Student("张三", 5); s1.hobbies.push("swimming"); console.log(s2.hobbies); s1.sayHi();
|
缺陷:父类构造函数被调用两次(new Person() 和 Person.call()),存在冗余属性。
5. 寄生组合继承(最优经典方案,ES6 class 底层逻辑)
核心:通过 Object.create() 继承父类原型,避免父类构造函数重复调用,是组合继承的优化版。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Person(name) { this.name = name; this.hobbies = ["reading"]; } Person.prototype.sayHi = function() { };
function Student(name, grade) { Person.call(this, name); this.grade = grade; }
Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student;
Student.prototype.study = function() { };
|
优势:父类构造函数仅调用一次,无冗余属性,同时继承实例属性和原型方法,是 ES6 class extends 之前的最优方案。
四、核心总结
原型:每个对象有 [[Prototype]],函数有 prototype,是继承的基础;
原型链:[[Prototype]] 形成的链式结构,是属性 / 方法查找的核心机制,终点是 null;
继承实现
:
- 现代推荐:ES6
class extends(原型链语法糖,简洁易读);
- 经典方案:寄生组合继承(无冗余,性能优);
- 基础方案:原型链继承 / 构造函数继承(有缺陷,仅作理解);
核心逻辑****:JS 继承的本质是 “修改原型链”,让子类实例能沿原型链访问父类的属性 / 方法。****