原型的定义
我们说在js
当中一切皆对象
,对吧, 那么在js
的函数对象
中都有一个内置的Prototype
属性
这个属性指向一个对象
,你可以把这个Prototype属性
想象成一个指针
, 它指向一个对象
而这个对象
就成为原函数对象
的原型
,俗称原型对象
我们来看一段简单的代码:
function Test(){
}
console.log(Test.prototype);
var T1=new Test();
var T2=new Test();
var T3=new Test();
console.log(T1.__proto__);
console.log(T2.__proto__);
console.log(T3.__proto__);
AI 代码解读
结果
这里还要给大家科普一个小知识,就是普通对象没有原型对象
的,也就是说不是函数对象
也可以说成不是通过new Function
创建的对象,那么就不会存在原型对象
我们来验证一下
代码如下
//普通对象
var json={
}
console.log(json.prototype);
//元素对象
var oDiv = document.getElementById("connent");
console.log(oDiv.prototype);
//函数对象
function test(){
}
console.log(test.prototype);
AI 代码解读
结果如下
所以说首先只有函数对象
才会有原型对象
理解构造函数、实例化对象、原型对象彼此之间的关系
这样就形成了每个函数对象
其实都有一个指向另一个对象的指针
这里我们要说明一点的就是,只有函数
才有一个prototype属性
,这个prototype属性
就是我们的原型对象
同时从图中也可以看到原型
当中含有一个constructor 属性
,而这个属性指向的就是当前原型对象
的构造函数
我们一般会拿这个构造函数
通过new
创建出来实例对象
,而实例对象
是没有prototype
属性的!
如果你在一个实例对象
上调用prototype
必然返回undefined
, 而一个实例对象
靠的是使用__proto__
的隐式属性,进行访问原型对象
!
如图
如果按照这个逻辑推理的话,你可以使用以下代码进行验证一下,是否正确:
console.log(T1.__proto__==Test.prototype); //返回true
console.log(Test.prototype.constructor==Test); //返回true
AI 代码解读
结果证明的确是这样子,实例对象
自己会有一个指针属性为__proto__
, 用它来指向构造函数
的原型对象
其实你也可以使用isPrototypeOf方法
来判断当前这个实例对象
中的__proto__指针
到底是不是能够指向到本身构造函数
的原型对象
中!
例如
//构造函数1
function Person(name,age) {
}
var p1=new Person();
//构造函数2
function Test(){
}
var test=new Test();
console.log(Person.prototype.isPrototypeOf(p1)); //返回true
console.log(Person.prototype.isPrototypeOf(test)); //返回false
AI 代码解读
以上我们用了isPrototypeOf方法
来监测一个实例对象
中的__proto__指针
是否指向对应的原型对象
在js
中我们还可以使用一个叫Object.getPrototypeOf()
的方法来监测实例对象
和原型对象
之间的关系
例如
console.log(Object.getPrototypeOf(p1)==Person.prototype); //返回true
console.log(Object.getPrototypeOf(p1)==Test.prototype); //返回false
AI 代码解读
所以从结果上看
使用这个Object.getPrototypeOf方法
返回的就是当前实例对象
中__proto__属性
所指向的原型对象
同时constructor
属性也的确指向了本身的构造函数
这样原型对象
和实例对象
之间就通过__proto__
连接在一起,形成了一个链条
, 而所谓的原型链
也就是实例对象
和原型对象
之间的链条关系, __proto__
这条线,也就是原型链
的关键
并且这条链条,从图中我们也可以看到,还可以往上走到一个叫Object.prototype
的地方!
实例对象属性和方法搜索的优先级
上面说了,有了原型对象
,那么实例对象
可以共享原型对象
中的属性
和方法
那么问题来了,这些实例对象
又是如何进行查找属性和方法的呢?
举个栗子
function createPerson(name,age) {
this.name=name;
this.age=age;
this.say=function (){
console.log('2.构造函数中定义的say方法!');
}
}
createPerson.prototype.say=function () {
console.log('我的名字叫:'+this.name);
}
var a=new createPerson('张三','33');
var b=new createPerson('李四','55');
var c=new createPerson('王武','66');
c.say=function(){
console.log('1.实例对象c 定义的say方法!');
}
a.say();
b.say();
c.say();
AI 代码解读
结果如下:
代码分析
按照这个查找逻辑上来看的话,调用的查找方式如下:
先在实例对象
上查找定义的属性
和方法
,优先级最高,如果找不到的情况下,然后再是构造函数
中进行查找我们定义的属性
和方法
, 最后如果也找不到的情况下,就到原型对象
中去寻找!
注意:这并不是把原型对象
中的属性
和方法
覆盖了,只是优先调用的顺序而已!
例如
function Person(){
}
Person.prototype.username='张三';
Person.prototype.age=30;
Person.prototype.job='设计师';
Person.prototype.say=function (){
console.log('我是'+this.username);
}
var p1=new Person();
var p2=new Person();
p1.username='李四';
console.log(p1.username);
console.log(p2.username);
AI 代码解读
代码分析
首先这里构造函数中我们什么都没有定义的情况下,这里就是先搜索实例对象本身
,如果在实例对象
中找到了具有给定的属性
或者方法
则进行返回!
如果没有找到,则会根据一个叫__proto__
的指针
到原型对象
中去寻找,如果找到就返回! 如果最终都没有找到则返回undefined
那么这个案例中,则执行了两次搜索!先询问了实例对象本身
是否具有, 然后顺着指针到原型对象
中去询问
所以说我们在实例对象
上调用属性
和方法
的时候,都会出现以上相同的搜索过程!
而有了这个搜索模式的帮助下,多个实例对象
则可以共享原型对象
所定义的属性
和方法
就是这个原理!
如图
这里我再次提一嘴,前面不是使用到了constructor
属性吗, 这个属性也默认是共享的,也就是所有实例对象
默认情况下,都可以通过访问这个属性来确定构造函数
是谁!
所以大家也应该注意一下,就是如果你在实例对象上定义了一个属性或者方法,而且原型对象中也定义了同名的属性或者方法,依照查找的顺序会依次搜索实例对象--->构造函数--->原型对象
, 即便是同名也是优先调用最先找到的位置!
也就是说当你在实例对象
上定义一个与原型对象
中同名属性
和方法
的时候,会自动屏蔽
原型对象中的同名属性和方法,注意这里也仅仅是屏蔽,而不是覆盖! 当然换句话说也可以理解为你在实例对象上添加同名属性和方法的时候,会阻止访问原型对象中的同名属性和方法,明白这个意思吧!
即便是你在实例对象
上把某个属性和方法的值定义为null
, 那么访问的时候也只会停留在实例对象
这个层面,而不会恢复其指向构造函数
和原型对象
的链接!
但是如果你使用delete操作符
是可以完全删除实例属性
同时也删除构造函数
中的同名属性,从而让我们能够访问原型对象
中的同名属性!
例如
function Person(){
this.username='李四';
}
Person.prototype.username='王五';
Person.prototype.age=30;
Person.prototype.job='设计师';
Person.prototype.say=function (){
console.log('我是'+this.username);
}
var p1=new Person();
p1.username=null;
delete p1.username;
console.log(p1.username); //这里输出的结果来自于 原型对象
AI 代码解读
其实我们就可以按照这个查找逻辑,来修改原型对象
从而实现父子继承
的关系 这个我们后面再说!
__proto__的真正含义!
那么实例对象
到底底层是如何查到原型对象
中去的呢?
这其实就要说到刚刚我们提及到的__proto__
这个东西了! 嘿嘿
我们来看一张图:
如图
每个实例对象
都会有一个 __ proto__
属性,这个属性是自动生成的, __ proto__
属性指向自己的原型对象
而且实例对象
也就是通过这一条__ proto__
线路,找到原型对象
中的属性
和方法
的
这就是我马上要提到的原型链
特别注意
这里我提醒一下,可能你以前看到的也的确是叫__proto__
这个
但是目前Chrome
打印出来之后效果提示的是[[Prototype]]
如图
这里只是显示变了而已,代码层面上,实例对象
还是可以继续使用__proto__
这个属性的
然而__proto__
的真正意义也就在于两个字:查找
也就是接下来要说的原型链
因为原型链
就是通过__proto__
属性形成的,任何对象普通对象
和函数对象
都有__proto__
属性
prototype与__proto__的区别
其实我们在上面的图中也能看出来彼此的一个很明显的区别:
__proto__
是实例对象
指向原型对象
的指针,我们俗称隐式原型
,并且是每个实例对象
都会有的一个属性!
prototype
是构造函数/函数
才有的原型对象
,我们俗称为显式原型
这里我特别提一下,其实prototype
就是一个用来设置原型
,而另外一个__proto__
则用来查找数据,如果你还不明白,那么就看下面的原型链
解释就清楚了!
所以说大家不要再把__proto__
与函数的 func.prototype
属性混淆了!