原型/原型链
一、基本概念
-
prototype(构造函数的原型对象)-
每个 函数(
Function) 在创建时,默认会有一个名为prototype的属性,指向一个对象。 -
这个对象会成为 由该函数作为构造器 new 出来的对象的原型。
-
示例:
function Person() {} console.log(Person.prototype); // { constructor: f Person, ... } -
-
__proto__(对象的隐式原型,非标准但广泛实现)-
所有 对象 都有一个
__proto__属性(大多数现代浏览器都支持),它指向该对象的原型对象(即构造函数的 prototype)。 -
它实际上是
[[Prototype]]的一个访问器,是浏览器为开发者调试提供的便利方式。
const p = new Person(); console.log(p.__proto__ === Person.prototype); // true -
-
[[Prototype]](内部槽) 是 对象内部的隐藏属性,由规范定义。-
不能直接访问,但可以通过
__proto__、Object.getPrototypeOf()读到,也可以用Object.setPrototypeOf()修改。 -
真正控制了“原型链”行为的是
[[Prototype]]。
-
二、原型与原型链
什么是原型?
-
在 JS 中,函数可以作为构造函数,new 关键字创建出来的对象会将
[[Prototype]]设置为该函数的 prototype。 -
每个对象都有一个原型(即
[[Prototype]]),这个原型本身也是一个对象。
原型链的定义
-
当访问对象的属性时,如果对象本身没有,就会去它的原型(
[[Prototype]])中查找; -
如果还找不到,就去原型的原型查找,一直向上查;
-
直到原型为
null(即Object.prototype.__proto__ === null)为止; -
这一串查找的过程称为原型链(
Prototype Chain)。
示例说明:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hi, I'm ${this.name}`);
};
const p1 = new Person('Alice');
// 查找 sayHello 属性过程:
p1.sayHello(); // => 会先查找 p1 上有没有 sayHello 属性
// => 没有,往 p1.__proto__(即 Person.prototype)上找
// => 找到 sayHello,执行三个对象的关系总结图(结构)
p1 --> [[Prototype]] --> Person.prototype --> [[Prototype]] --> Object.prototype --> null
Person --> prototype --> Person.prototypefunction Animal() {}
Animal.prototype.eat = function () {
console.log("eating");
};
function Dog() {}
Dog.prototype = new Animal(); // 原型继承
Dog.prototype.constructor = Dog;
const d = new Dog();
d.eat(); // 继承自 Animal.prototype三、es5 中的继承方式
-
原型链继承(Prototype Chain Inheritance)
function Parent() { this.name = 'parent'; } Parent.prototype.sayHi = function () { console.log('Hi from parent'); }; function Child() {} Child.prototype = new Parent(); // 核心 const c = new Child(); c.sayHi(); // Hi from parent-
优点:
- 简单易懂,父类方法可复用。
-
缺点:
- 所有子类实例共享父类引用属性(如数组会互相影响)。
- 子类构造函数无法向父类传参。
-
-
借用构造函数继承(
Constructor Stealing/Classic Inheritance)
function Parent(name) {
this.name = name;
this.arr = [1, 2];
}
function Child(name) {
Parent.call(this, name); // 核心:借用父构造函数
}
const c1 = new Child('Tom');
const c2 = new Child('Jerry');
c1.arr.push(3);
console.log(c2.arr); // [1, 2]-
优点:
-
避免引用属性共享问题。
-
可向父类传参。
-
-
缺点:
- 方法不能复用(每个实例都有一份父类方法)。
-
组合继承(
Combination Inheritance)⭐最常用function Parent(name) { this.name = name; this.arr = [1, 2]; } Parent.prototype.sayHi = function () { console.log('Hi, ' + this.name); }; function Child(name) { Parent.call(this, name); // 借用构造函数 } Child.prototype = new Parent(); // 原型链继承 Child.prototype.constructor = Child; const c = new Child('Tom'); c.sayHi(); // Hi, Tom-
优点:
- 实例独立、方法可复用。
-
缺点:
- 调用了两次父构造函数(一次在
Child.prototype = new Parent(),一次在Parent.call(this))。
- 调用了两次父构造函数(一次在
-
-
原型式继承(
Object.create简化版)const parent = { name: 'parent', arr: [1, 2], }; const child = Object.create(parent); child.name = 'child';-
优点:
- 创建对象很方便。
-
缺点:
-
共享引用属性。
-
无法传参。
-
-
-
寄生式继承(
Parasitic Inheritance)function createChild(obj) { const clone = Object.create(obj); clone.sayHi = function () { console.log('Hi'); }; return clone; } const parent = { name: 'parent' }; const child = createChild(parent);-
优点:
- 可以扩展功能。
-
缺点:
- 方法不可复用(每次都新建方法)。
-
-
寄生组合式继承(
Parasitic Combination Inheritance)⭐推荐使用function Parent(name) { this.name = name; } Parent.prototype.sayHi = function () { console.log('Hi, ' + this.name); }; function Child(name) { Parent.call(this, name); } Child.prototype = Object.create(Parent.prototype); // 不执行 Parent 构造函数 Child.prototype.constructor = Child;-
优点:
-
避免重复调用构造函数。
-
方法共享、实例独立,性能最好。
-
-
| 继承方式 | 可传参 | 共享引用属性问题 | 方法复用 | 性能 |
|---|---|---|---|---|
| 原型链继承 | ✘ | 有 | ✔ | 中 |
| 构造函数继承 | ✔ | 无 | ✘ | 中 |
| 组合继承 | ✔ | 无 | ✔ | 一般 |
| 原型式继承 | ✘ | 有 | ✔ | 中 |
| 寄生式继承 | ✘ | 有 | ✘ | 差 |
| 寄生组合继承 | ✔ | 无 | ✔ | 最优 |