原型/原型链
一、基本概念
-
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- 优点:
-
避免重复调用构造函数。
-
方法共享、实例独立,性能最好。
-
- 优点:
| 继承方式 | 可传参 | 共享引用属性问题 | 方法复用 | 性能 |
|---|---|---|---|---|
| 原型链继承 | ✘ | 有 | ✔ | 中 |
| 构造函数继承 | ✔ | 无 | ✘ | 中 |
| 组合继承 | ✔ | 无 | ✔ | 一般 |
| 原型式继承 | ✘ | 有 | ✔ | 中 |
| 寄生式继承 | ✘ | 有 | ✘ | 差 |
| 寄生组合继承 | ✔ | 无 | ✔ | 最优 |