
原型的定义
我们说在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__);
结果

这里还要给大家科普一个小知识,就是普通对象没有原型对象的,也就是说不是函数对象 也可以说成不是通过new Function创建的对象,那么就不会存在原型对象
我们来验证一下
代码如下
//普通对象
var json={
}
console.log(json.prototype);
//元素对象
var oDiv = document.getElementById("connent");
console.log(oDiv.prototype);
//函数对象
function test(){
}
console.log(test.prototype);
结果如下

所以说首先只有函数对象才会有原型对象
理解构造函数、实例化对象、原型对象彼此之间的关系
这样就形成了每个函数对象其实都有一个指向另一个对象的指针
这里我们要说明一点的就是,只有函数才有一个prototype属性,这个prototype属性就是我们的原型对象
同时从图中也可以看到原型当中含有一个constructor 属性,而这个属性指向的就是当前原型对象的构造函数
我们一般会拿这个构造函数通过new创建出来实例对象,而实例对象是没有prototype属性的!
如果你在一个实例对象上调用prototype必然返回undefined, 而一个实例对象靠的是使用__proto__的隐式属性,进行访问原型对象!
如图

如果按照这个逻辑推理的话,你可以使用以下代码进行验证一下,是否正确:
console.log(T1.__proto__==Test.prototype); //返回true
console.log(Test.prototype.constructor==Test); //返回true
结果证明的确是这样子,实例对象自己会有一个指针属性为__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
以上我们用了isPrototypeOf方法来监测一个实例对象中的__proto__指针是否指向对应的原型对象
在js中我们还可以使用一个叫Object.getPrototypeOf()的方法来监测实例对象和原型对象之间的关系
例如
console.log(Object.getPrototypeOf(p1)==Person.prototype); //返回true
console.log(Object.getPrototypeOf(p1)==Test.prototype); //返回false
所以从结果上看
使用这个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();
结果如下:

代码分析
按照这个查找逻辑上来看的话,调用的查找方式如下:
先在实例对象上查找定义的属性和方法,优先级最高,如果找不到的情况下,然后再是构造函数中进行查找我们定义的属性和方法, 最后如果也找不到的情况下,就到原型对象中去寻找!
注意:这并不是把原型对象中的属性和方法覆盖了,只是优先调用的顺序而已!
例如
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);
代码分析
首先这里构造函数中我们什么都没有定义的情况下,这里就是先搜索实例对象本身,如果在实例对象中找到了具有给定的属性或者方法则进行返回!
如果没有找到,则会根据一个叫__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); //这里输出的结果来自于 原型对象
其实我们就可以按照这个查找逻辑,来修改原型对象从而实现父子继承的关系 这个我们后面再说!
__proto__的真正含义!
那么实例对象到底底层是如何查到原型对象中去的呢?
这其实就要说到刚刚我们提及到的__proto__这个东西了! 嘿嘿
我们来看一张图:
如图

每个实例对象都会有一个 __ proto__ 属性,这个属性是自动生成的, __ proto__ 属性指向自己的原型对象
而且实例对象也就是通过这一条__ proto__线路,找到原型对象中的属性和方法的
这就是我马上要提到的原型链
特别注意
这里我提醒一下,可能你以前看到的也的确是叫__proto__这个
但是目前Chrome打印出来之后效果提示的是[[Prototype]]
如图

这里只是显示变了而已,代码层面上,实例对象还是可以继续使用__proto__这个属性的
然而__proto__的真正意义也就在于两个字:查找 也就是接下来要说的原型链
因为原型链就是通过__proto__属性形成的,任何对象普通对象和函数对象都有__proto__属性
prototype与__proto__的区别
其实我们在上面的图中也能看出来彼此的一个很明显的区别:
__proto__是实例对象指向原型对象的指针,我们俗称隐式原型,并且是每个实例对象都会有的一个属性!
prototype是构造函数/函数才有的原型对象,我们俗称为显式原型
这里我特别提一下,其实prototype就是一个用来设置原型,而另外一个__proto__则用来查找数据,如果你还不明白,那么就看下面的原型链解释就清楚了!
所以说大家不要再把__proto__与函数的 func.prototype 属性混淆了!