Skip to content
本页目录

原型链

原型链是学习JavaScript时永远绕不过的一座山,因为它关乎面向对象编程语言的特点之一:继承

原型

在深入学习原型链之前,我们需要明确一些基本概念:

  • 在JavaScript中一切皆对象,包括基本类型(null undefined除外)!
  • 显式原型:prototype(即原型)
    • prototype只存在函数上(箭头函数没有prototype);
    • 非函数能访问到prototype,一定是其因为查找了原型链,并非其本身属性。
  • 隐式原型:[[prototype]]__proto__(指向构造函数的原型)
    • 所有对象均有__proto__属性,包括箭头函数(null undefined除外)。
  • 构造函数:constructor(原型上的属性,指向构造函数)。

对上述概念有疑问之处,不妨试试在浏览器控制台简单验证一下:

验证一下
  • 首先是一切皆对象,就用值类型来举例
JavaScript
// 值类型 false 并不具有tostring方法,查找了原型链
// 找到了其构造函数Boolean.prototype的toString
(false).toString()  // 'false'
(false).__proto__.toString() // 'false'
// 验证
(false).__proto__ === Boolean.prototype // true
// number类型
(623).__proto__ === Number.prototype // true
// null、undefined
(null).__proto__  // Uncaught TypeError
(undefined).__proto__  // Uncaught TypeError
  • 箭头函数没有(显式)原型
JavaScript
const arrowFunc = () => '我是箭头函数'
arrowFunc.prototype  // undefined

明确这些基本概念后,接下来我们进入正题:

JavaScript
function Kon() {
  // do something
}
Kon.prototype.name = '轻音少女'
const yui = new Kon()
console.log(yui.name) // 轻音少女

在这个例子中,通过new运算符生成实例yui,但是实例本身并没有name属性,可以看见代码行4中, 我们在构造函数Kon的原型上声明了name属性,在输出时,却输出了原型上的属性值'轻音少女',这是为什么呢?
当我们通过访问name属性时(即yui.name):

  1. 首先查找实例自身有无name属性,结果没有;
  2. 然后查找原型上yui.__proto__.name是否存在,结果找到返回。

为了验证一下上面所说的内容是否正确,给实例增加name属性(代码行6):

JavaScript
function Kon() {
  // do something
}
Kon.prototype.name = '轻音少女'
const yui = new Kon()
yui.name = '平泽唯'
console.log(yui.name) // 平泽唯

此时输出了实例上的name属性值'平泽唯',接着我们将实例上的name属性删除(代码行8),再看看结果是否与之前的描述一致:

JavaScript
function Kon() {
  // do something
}
Kon.prototype.name = '轻音少女'
const yui = new Kon()
yui.name = '平泽唯'
console.log(yui.name) // 平泽唯
delete yui.name
console.log(yui.name) // 轻音少女

此时依次输出'平泽唯''轻音少女',结果与之前描述一致。

原型链

接下来我们来一探原型链的真面目,在前例中我们知道实例的(隐式)原型指向了构造函数的(显式)原型,即:yui.__proto__ === Kon.prototype, 那么构造函数Kon的显式原型Kon.prototype和隐式原型Kon.__proto__又指向了谁呢?

现在我们来逐一尝试验证:

  • 构造函数的原型的constructor是否指向构造函数本身;
    JavaScript
    Kon.prototype.constructor === Kon
  • 既然一切皆对象,那么构造函数的原型的隐式原型是否指向对象的原型;
    JavaScript
    Kon.prototype.__proto__ === Object.prototype
  • 既然一切皆对象,那么对象的原型的隐式原型是否还存在,存在的话又指向谁;
    JavaScript
    Object.prototype.__proto__ === null
  • 构造函数Kon本身是否由基础Function派生(继承)而来;
    JavaScript
    Kon.__proto__ === Function.prototype
  • 基础Function的原型的隐式原型是否也满足一切皆对象
    JavaScript
    Function.prototype.__proto__ === Object.prototype
  • 对象的原型的constructor是否也指向其构造函数本身;
    JavaScript
    Object.prototype.constructor === Object
  • 对象构造函数是否同Kon构造函数一样,也由基础Function派生(继承)而来;
    JavaScript
    Object.__proto__ === Function.prototype
  • 基础Functionconstructor是否也指向其构造函数本身;
    JavaScript
    Function.prototype.constructor === Function
  • 作为基础Function,那么其隐式原型是否同其它构造函数一样指向基础Function(即自身)的原型;
    JavaScript
    Function.__proto__ === Function.prototype

逐一尝试完以上所有试想后会发现返回结果:全为true

逐一尝试
JavaScript
function Kon() {
  // do something
}
const yui = new Kon()
yui.__proto__ === Kon.prototype  // true

// 验证构造函数的(显式)原型的constructor是否指向构造函数
Kon.prototype.constructor === Kon  // true

// 既然一切皆对象,那么构造函数的原型的隐式原型是否指向对象的原型
Kon.prototype.__proto__ === Object.prototype  // true

// 既然一切皆对象,那么对象的原型的隐式原型是否还存在,存在的话又指向谁
Object.prototype.__proto__ === null  // true,即不存在

// 构造函数Kon本身是否由基础Function派生(继承)而来
Kon.__proto__ === Function.prototype  // true

// 基础Function的原型的隐式原型是否也满足一切皆对象
Function.prototype.__proto__ === Object.prototype  // true

// 对象的原型的constructor是否也指向其构造函数本身
Object.prototype.constructor === Object  // true

// 对象构造函数是否同Kon构造函数一样,也由基础Function派生(继承)而来
Object.__proto__ === Function.prototype  // true

// 基础Function的constructor是否也指向其构造函数本身
Function.prototype.constructor === Function  // true

// 作为基础Function,那么其隐式原型是否同其它构造函数一样指向基础Function(即自身)的原型
Function.__proto__ === Function.prototype  // true

总结

根据此前的猜想和逐一验证,现在我们可以将原型链知识总结成下图: 原型链总结