1 回顾原型的特点
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 原型对象中有一个属性constructor, 它指向函数对象
- 每个函数function都有一个prototype,即显式原型
- 每个实例对象都有一个
__proto__
,可称为隐式原型 - 对象的隐式原型的值为其对应构造函数的显式原型的值
- 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
- 对象的
__proto__
属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
2 图解原型链
function Fn() {
this.method1 = function () {
console.log("method1")
}
}
Fn.prototype.method2 = function () {
console.log("method2")
}
var fn = new Fn()
fn.method1();
fn.method2();
console.log(fn.toString())
fn.method3();
下面就一点一点的分析:
function Fn() {
this.method1 = function () {
console.log("method1")
}
}
上面这部分代码的内存分析:
- 堆空间会开辟一个空间,保存的就是Fn函数对象,假设内存地址为0x123
- 由于每个函数都有一个prototype属性, 它默认指向一个Object函数的一个实例对象(保存了Object函数的一个实例对象的地址),假设这个地址为0x234
- 栈空间中会声明一个变量指向0x123这块内存空间
在这就产生一个问题,Fn函数对象的prototype属性指向一个Object函数的实例对象,而
每个实例对象都有一个proto(隐式原型),并且这个对象的隐式原型的值为其对应函数对象的显式原型的值。
也就说当前的这个Object函数的实例对象有一个__proto__
属性,指向了Object函数的显示原型对象(保存了Object函数的显式原型对象的地址)
那这个Object函数对象是什么时候加载的?js引擎在一开始的时候就加载了这个函数对象:
接下来这部分代码
Fn.prototype.method2 = function () {
console.log("method2")
}
这个就比较简单了,就是在Fn的原型对象中添加了一个属性method2
简单说明:这里对method2做了简化,他俩也是函数对象
var fn = new Fn()
也比较简单,就创建了一个Fn的实例对象。需要注意两点:
- 在调用构造函数的时候,会给这个实例对象添加一个method1的属性
- Fn的实例对象的proto属性指向其构造函数的原型对象
fn.method1();
fn.method2();
console.log(fn.toString())
fn.method3();
- 当调用method1的时候,发现在自己的实例对象中有该方法,那么就会调用实例对象中的method1
- 当调用method2的时候,发现在自己的实例对象中没有该方法,那么就会去原型中寻找,发现存在method2,就会调用原型对象中的method2
- 当调用toString()的时候,发现在自己的实例对象中没有该方法,那么就会去原型中寻找,发现自己的原型对象中也不存在,就会去原型对象的原型对象(Object的原型对象),发现Object的原型对象存在toString()方法
就会去执行这个toString()方法 - 当调用method3()的时候,发现在自己的实例对象中没有该方法,那么就会去原型中寻找,发现自己的原型对象中也不存在,就会去原型对象的原型对象(Object的原型对象),发现Object的原型对象也不存在method3方法(undefined),在调用的时候就会报错( fn.method3 is not a function)
总结:
1. 函数对象的原型对象就是Object的一个实例对象,所以函数对象的prototype的proto属性就是Object的原
型对象
2. 函数对象的实例的proto属性是当前函数对象的原型对象
3. Object的原型对象的proto属性是null,不存在Object的原型对象的原型对象
3 Object的原型对象有哪些方法呢?
function Fn() {
this.method1 = function () {
console.log("method1")
}
}
// 如何找Object的原型对象呢: Fn.prototype就是一个Object的实例对象,所以Fn.prototype的__proto__属性就是Object的原型对象
console.log(Fn.prototype.__proto__)
4 验证Object的原型对象的原型是null
function Fn() {
this.method1 = function () {
console.log("method1")
}
}
// 如何找Object的原型对象呢: Fn.prototype就是一个Object的实例对象,所以Fn.prototype的__proto__属性就是Object的原型对象
console.log(Fn.prototype.__proto__)
console.log(Fn.prototype.__proto__.__proto__) // 输出是null
5 原型对象中的constructor属性
刚刚上面打印Object的原型对象的constructor属性就是Object对象的构造函数:
function Fn() {
}
// 如何找Object的原型对象呢: Fn.prototype就是一个Object的实例对象,所以Fn.prototype的__proto__属性就是Object的原型对象
console.log(Fn.prototype.__proto__)
console.log(Fn.prototype.__proto__.constructor === Object) // true
函数的原型的constructor又会指向其函数对象
// 定义一个Fn的函数对象
function Fn() {
}
// 函数的原型的constructor又会指向其函数对象
console.log(Fn.prototype.constructor === Fn) // true
var fn = new Fn();
// 函数的原型的constructor又会指向其函数对象
console.log(fn.__proto__.constructor === Fn) // true
6 Function函数对象的原型
function Fn() {
}
// 上面的也可以写为这样,
var Fn = new Function();
这里就出现了,函数对象都是Function函数的实例了,而所有的实例对象的有隐式原型(__proto__
)保存的就是其函数对象的显式原型,也就是说Fn函数对象的__proto__
保存的就是Function的原型对象
同样Object的构造函数也是Function函数的实例了,所以Object函数对象的__proto__
保存的就是Function的原型对象
上面的图又可以进化了
总结:
所有函数对象的__proto__
都是Function显式原型
Function是其自身的实例,上图可以看出Function.prototype = Function.__proto__
function Fn1() {
this.method1 = function () {
console.log("method1")
}
}
var Fn2 = Function();
// 所有函数对象的`__proto__`都是Function显式原型
console.log(Fn2.__proto__ === Fn1.__proto__) // true
console.log(Fn1.__proto__ === Function.prototype) // true
7 总结
- 函数的显式原型指向的对象默认是Object的实例对象,但是Object除外
function Fn() { }
console.log(Fn.prototype instanceof Object)// true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object)// true
需要需要纠正上面图的一个错误
- Function是他自身的实例:Function = new Function();
- 所有函数都是Function的实例,所以:
// 每个实例的__proto__都指向其构造函数的的prototype,而Function就是自己的实例
console.log(Function.prototype === Function.__proto__) // true
- Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__) // null