一、函数的prototype(原型)
Javascript没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。这个属性包含一个对象(prototype对象),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
- 每个函数都有一个
prototype(显式原型)属性,在定义函数的时候添加,它默认指向一个Object空对象(prototype对象)。 - 每个实例对象都有一个
__proto__(隐式原型)属性,在创建对象的时候添加,默认值为构造函数的prototype属性的值。 - 原型对象中有一个
constructor属性,它指向函数对象。 - 函数的
所有实例对象自动拥有原型中的属性(方法)。 - 对象的
隐式原型的值等于其对应的构造函数显式原型的值。
下面通过例子来对显式原型和隐式原型加深理解:
//定义一个构造函数
function Fun() {
}
//创建一个实例对象
let f = new Fun();
console.log(Fun.prototype)
console.log(f.__proto__)
结果如下图,自己创建的构造函数Fun在没有添加属性或者方法的情况下是一个空的Object对象。
//Date为内置的构造函数
console.log(Date.prototype)
//创建一个Date的实例对象date
let date = new Date()
//date实例拥有原型(prototype)中的方法
console.log(date.getFullYear())//2021
console.log(date.__proto__ === Date.prototype) //true
console.log(Date.prototype.constructor === Date) //true
结果如下图:
分析: Date是内置的构造函数,所以
Date.prototype指向的不再是一个Object空对象,因为系统自动添加了很多供实例对象使用的方法,简单画了个图便于理解。
简单回顾一下构造函数、原型对象和实例对象的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
二、原型链(隐式原型链)
接下来让我们再仔细分析下面这个例子:
function Fn() {
this.text1 = function () {
console.log('text1')
}
}
Fn.prototype.text2 = function () {
console.log('text2')
}
let f = new Fn();
f.text1() //text1
f.text2() //text2
console.log(f.toString()) //[object Object]
f.text3() //Uncaught TypeError: f.text3 is not a function
console.log(f.text3);//undefind
console.log(Object.prototype.__proto__) //null
当js脚本运行的时候,会自动创建一个Object函数对象并且自动添加一些方法。当函数调用属性或者方法的时候,首先会在自己的区域找,如果没有就会根据
__proto__(隐式原型)往上面查找原型对象,直到找到Object函数对象的原型对象,因为这里面的__proro__ = null,所以Object函数对象的原型对象就是做原型链的尽头,如果最终没找到就会返回undefind,因为整个查找过程都只和隐式原型链有关,所以原型链又叫做隐式原型链。
知道了原型链的原理以后就不难分析上面的结果了。
进阶图
现在从上往下依次分为3个区域:
- f1、f2是通过自己设置的构造函数Foo()产生的实例对象,它们都有
__proto__属性并且指向构造函数的原型对象(prototype),而构造函数Foo()的原型对象里面有一个constructor属性指向Foo()。Foo函数是Function的实例,所以函数都有两个属性__proto__和prototype。所有函数的__proto__ 属性都相等并且等于Function的原型对象
function Foo() {
}
//上面的写法相当于 let Foo = new Function()
let f1 = new Foo()
let f2 = new Foo()
console.log(Foo.prototype.constructor === Foo) //true
console.log(f1.__proto__ === Foo.prototype) //true
console.log(f2.__proto__ === Foo.prototype) //true
- o1、o2是通过内置的构造函数Object()产生的实例对象,它们都有
__proto__属性并且指向Object函数的原型对象(prototype),构造函数Object()的原型对象里面有一个constructor属性指向Foo(),注意这里的__proto__ = null。 - 一个函数的显式原型和隐式原型相等说明这个函数是
Function,Function()由系统提供,相当于Function = new Function()。 - 可以看到Object()实例对象的隐式原型__proto__指向Function()的显式原型,前面我们说过:实例对象的隐式原型等于构造函数的显式原型,说明Object()是Function()的实例对象,任何函数都是通过
new Function()产生的。 - 所有函数的显式原型值向的对象默认是空Object实例对象(但是Object不满足)。
- 所有函数都是Function()的实例(包含Function)。
- Object的原型对象是原型链尽头。