切换主题
原型链
原型链是学习 JavaScript 时永远绕不过的一座山,因为它关乎面向对象编程语言的特点之一:继承!
原型
在深入学习原型链之前,我们需要明确一些基本概念:
- 在 JavaScript 中一切皆对象,包括基本类型(
nullundefined除外)! - 显式原型:
prototype(即原型)prototype只存在函数上(箭头函数没有prototype);- 若非函数能访问到
prototype,一定是其因为查找了原型链,并非其本身属性。
- 隐式原型:
[[prototype]]即__proto__(指向构造函数的原型)- 所有对象均有
__proto__属性,包括箭头函数(nullundefined除外)。
- 所有对象均有
- 构造函数:
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(即自身)的原型;JavaScript逐一尝试完以上所有试想后会发现返回结果:全为Function.__proto__ === Function.prototypetrue。
逐一尝试
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总结
根据此前的猜想和逐一验证,现在我们可以将原型链知识总结成下图: 