原型(prototype)
每个函数都有一个自己的prototype属性,默认是一个object空对象(即:函数的原型对象)
可以看出此时的object空对象是指内部不存在我们自己添加的属性和方法。原型对象中存在constructor、__proto__属性。
原型对象中constructor指向的是它的函数对象。原型对象、函数对象关系如下:
原型添加方法
我们可以给原型对象添加属性或方法,通过实例对象可以直接访问。
function Fun() { } Fun.prototype.test = function () { console.log('test'); } let f = new Fun() f.test()
tip: new 一个对象背后做了什么?
创建一个空对象;给对象设置__proto__,值为构造函数对象的prototype属性值(this.__proto__ =Fun.prototype
);执行构造函数体,给对象添加属性或方法。
如果直接给函数原型添加方法,函数只能通过prototype访问,调用prototype无意义。
function fun() { } fun.prototype.tick = function () { console.log('tick'); } fun.prototype.tick()
显式原型与隐式原型
显示原型:每个函数都有一个prototype属性。在定义函数时自动添加,默认为object空对象。
隐式原型:每个实例对象都有一个__proto__属性。在创建对象时自动添加,默认指向构造函数的prototype属性值。
所有函数的__proto__都是一样的(函数都是new Function的实例)。
function Fun() { } let f1 = new Fun() let f2 = new Fun() console.log(f1.__proto__ === Fun.prototype); // true console.log(f2.__proto__ === Fun.prototype); // true
Fun函数在定义时,会自动生成一个原型对象,f1、f2是Fun的实例。
原型对象:相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象。
显式原型与隐式原型关系:
实例.__proto__ === 构造函数.prototype
函数、实例、object关系
首先定义一个函数,函数的prototype属性,指向函数的原型对象。函数的原型对象中的__proto__指向Object的原型对象。
function Fun() { } let f1 = new Fun() let f2 = new Fun()
执行上面这段代码,首先会创建一个Object,Object指向自己的函数对象。Object函数对象的prototype指向Object的原型对象。Object的原型对象中包含一些属性方法。比如我们常用的:hasOwnProperty、valueOf、toString等。且Object的原型对象的原型为null。
其次,创建函数时,自动创建函数原型对象。函数原型的__proto__指向Object的函数对象。
可以通过以下语句可以验证:
console.log(Fun.prototype.__proto__ == Object.prototype); console.log(f1.__proto__.__proto__ == Object.prototype);
如果上图不能够理解,可以转化成下图:
原型链
定义:访问一个对象的属性时,先在自身属性中查找,找到返回;否则再沿着__proto__
这条链向上找,找到返回;如果最终没找到,返回undefined
。
本质:隐式原型链
作用:查找对象的属性(方法)
基础原型链
function Fun() { } Fun.prototype.test = function () { console.log('Fun 中的 test'); } let f1 = new Fun() f1.test = function () { console.log('f1 实例中的 test'); } let f2 = new Fun() f1.test() // f1 实例中的 test f2.test() // Fun 中的 test console.log(f1.test2); // undefined console.log(f1.test2()); // TypeError: f1.test2 is not a function
f1实例中存在test方法所以直接使用。
而f2实例中不存在test方法,所以通过原型链(__proto__
)向上查找,f2的上一级Fun中存在test方法,所以输出:Fun 中的 test。
f1实例中不存在test2、test2(),对于test2属性输出undefined,对于test2()函数为TypeError。
构造函数、原型、实例对象关系
function fun(){}
上面一行代码就相等于先创建Object、Function,并实例化。再实例化fun函数。 开始加载:
引入Object
引入Function
至于为什么会多一条隐式原型?
那是因为:所有函数的隐式原型(__proto__
)都是一样的(函数就相当于是Function的实例)。所以Function的函数对象的隐式原型指向自身的原型对象。就相当于
Function = new Function(); Function.__proto__ = Function.prototype
Object与Function
Object函数对象,本身就是一个函数。Object函数就相当于是Function的实例。
fun执行
关系加强版
console.log(Function.prototype instanceof Object); // true
Function原型是一个Object对象,就相当于是Object函数的实例。所以Function原型的隐式原型等于Object的显式原型。
fun函数原型同样是Object对象。
得到关系如下:
总结:
- 函数的显式原型指向的都是默认的Object空对象(原型内部只有constructor和
__proto__
)。函数的原型对象都是Object实例。但是Object的原型对象为null。Object的原型对象是原型链的尽头。
- 所有函数的隐式原型(
__proto__
)都是一样的(函数都是new Function的实例),指向Function函数的原型。实例对象的隐式原型等于构造函数的显示原型。
原型链继承
基础原型链中讲解了,一个构造函数有多个实例,实例继承了原型上的方法属性。
下面是两个不同的构造函数之间的父子继承。
function Fun1() { this.tag = 'div1' } Fun1.prototype.test = function () { console.log('Fun1 中的 test'); } function Fun2() { } Fun2.prototype = new Fun1() Fun2.prototype.constructor = Fun2 let f2 = new Fun2() console.log(f2.tag); // div1 f2.test(); // Fun1 中的 test
两个构造函数之间,Fun2构造函数将原型指向Fun1的实例。在调用Fun2时,Fun2构造函数会通过原型链找到Fun1的原型对象,从而获取原型中的属性或方法。
特别要注意的是:我们还需要将Fun2的原型对象指向自身构造函数。如果不指回自身,那么自身的原型属性或方法将失效。
instanceof
定义:检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
A instanceof B
A:实例。看A的原型链(__proto__
)
B:构造函数。看B的 prototype 属性。 判断是否正确,只要跟据下图找A、B两个指向是否能够指向同一个。
function fun() { } let f1 = new fun() console.log(f1 instanceof Fun); // true console.log(f1 instanceof Object); // true console.log(Object instanceof Function); // true console.log(Object instanceof Object); // true console.log(Function instanceof Function); // true console.log(Function instanceof Object); // true console.log(Object instanceof fun); // false
Object instanceof Function
。Object为实例,Function为构造函数
Object instanceof fun
。Object为实例,fun为构造函数。