切换主题
原型链
原型链是学习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
):
- 首先查找实例自身有无
name
属性,结果没有; - 然后查找原型上
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
是否指向构造函数本身;JavaScriptKon.prototype.constructor === Kon
- 既然一切皆对象,那么构造函数的原型的隐式原型是否指向对象的原型;JavaScript
Kon.prototype.__proto__ === Object.prototype
- 既然一切皆对象,那么对象的原型的隐式原型是否还存在,存在的话又指向谁;JavaScript
Object.prototype.__proto__ === null
- 构造函数
Kon
本身是否由基础Function
派生(继承)而来;JavaScriptKon.__proto__ === Function.prototype
- 基础
Function
的原型的隐式原型是否也满足一切皆对象;JavaScriptFunction.prototype.__proto__ === Object.prototype
- 对象的原型的
constructor
是否也指向其构造函数本身;JavaScriptObject.prototype.constructor === Object
- 对象构造函数是否同
Kon
构造函数一样,也由基础Function
派生(继承)而来;JavaScriptObject.__proto__ === Function.prototype
- 基础
Function
的constructor
是否也指向其构造函数本身;JavaScriptFunction.prototype.constructor === Function
- 作为基础
Function
,那么其隐式原型是否同其它构造函数一样指向基础Function
(即自身)的原型;JavaScriptFunction.__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
总结
根据此前的猜想和逐一验证,现在我们可以将原型链知识总结成下图: