目录
对于 JS 来说,__proto__
和prototype
的区别是个绕不开的话题。本文就试图从它们的根本上说清楚它们是什么,又有什么区别,所以本文会从 JS 的对象开始说起,以其期待把本文的主题说透彻说明白。
一、JS的对象创建方法
大家都知道,在JS 的世界中有一句话:万物皆对象
。而 JS 中的对象跟其他强类型的语言中的对象又有天壤之别,本文无意讨论 JS 中对象跟譬如 C#、java 等强类型语言中的对象的区别,不过还是要说下 JS 中对象创建的方法。
JS 语言本身非常灵活(不严谨),它的对象也非常灵活,比如 JS 允许动态的给对象添加属性(此处的动态添加属性指的是添加之前没有定义过的属性)。JS 创建对象的方式也是很多的,比如要创建一个用来描述“人”的对象p,该对象具有 name 、age 属性和say()方法。那么创建对象的手段至少有如下几种:
- 使用Object创建
var p = new Object();
p.name = "homes";
p.age = 32;
p.say = function(){
console.log("hello");
}
- 使用字面量方式
var p = {
name: "homes",
age: 32,
say: function () {
console.log("hello");
}
};
- 使用构造函数
var Person = function () {
this.name = "homes";
this.age = 32;
this.say = function () {
console.log("hello");
}
}
var p = new Person();
等等等,总之还有很多方法,比如使用原型来创建对象,使用拷贝方式,使用工厂方式。其实这些方式只是形式上有所变化而已。
这里重点要讨论的是第3种,使用构造函数创建对象
二、双对象法则
首先观察使用构造函数创建对象的代码,var Person = function( ){....}
,由于 JS 没有“类”的概念,其实这里的 Person 其实就是个函数;不过在 JS 的世界里还有一句话:万物皆对象,所以函数也是个对象(是由JS内置对象Function实例化出来的),那么以此得出 Person 是一个函数性质的对象。
上面这句话要说明什么呢?
重点来了
,在 JS 中如果一个对象是函数,那么这个对象是由两部分构成的,一个是这个函数的定义部分,如果这个函数被当做构造函数来用了,那么这第一部分就是这个构造函数。
第二部分,是这个函数的原型对象。
很多人在看到“原型对象”这个词的时候彻底懵逼,在这里必须把这个"原型"这个词说明白了。我第一次看到这个词“原型对象”的时候,我就想起了西游记,里边的妖怪最后都要现原形,难不成 JS 的世界就是妖怪的世界?每个对象都有个“原形”?可以通过某种手段让一个 JS 对象现原形?这是大错特错的。
这里的“原型对象”只不过是起个名字而已,我们就这样理解就好了,每一个函数都是对象
,当代码运行时,在内存中每个对象都由2块内容来合成这1个对象;换句话说代码运行时,内存里有2个独立的空间,这两个独立空间存储2个不同的对象,1个是函数体本身,一个是另外一个对象,这2个东西合成我们定义的一个函数对象,而这个“另一个对象”我们起名叫做“原型对象”,仅此而已。
那接下来问题来了
1、那个“原型对象”有什么用?
2、既然一个函数对象是由2部分组成的,那这两部分又是如何联系起来的?
三、__proto__
和prototype
prototype
是什么?
先不管那个额外的“原型对象”有什么鸟用,我们先看看一个函数对象的两部分是如何联系起来的。为什么要回答这个问题,因为如果两部分不能有效的联系起来,当我们 JS 程序中有多个函数对象时岂不是要乱套?
它们两部分之间是通过prototype
联系起来的(prototype
指向的对象就是我们说的原型对象)。看图
如图,我们上边声明了Person构造函数,当程序运行时,内存里会有2个部分,一个是左边的方框,代表该函数体,右边的方框代表该函数的另一部分,即它的“原型对象”。function
的内存中,除了有我们声明name,age属性,还有一个prototype
,这个属性是一个指针,它指向自己的原型对象所在地址。同理,每个原型对象也有一个属性,叫constructor
,也是个指针,它指回了自己的另一半,即该原型所从属的构造函数。
比如,我们可以使用如下语句:Person.prototype,来访问函数自己的原型对象。
需要注意的是,如果这个对象不是函数,那么该对象不具有prototype属性。
例如:
var a = {
name: "homes",
age: 32,
say: function () {
console.log("hello");
}
};
console.log(a.prototype); // 输出:undefined
var b = new Date();
console.log(b.prototype); // 输出:undefined
var c = new String("123");
console.log(c.prototype); // 输出:undefined
即使一个对象是根据构造函数实例化出来的,由于该对象不是函数,它也不具有prototype
属性。例如:
var Person = function () {
this.name = "homes";
this.age = 32;
this.say = function () {
console.log("hello");
}
}
var p = new Person();
console.log(p.prototype); // 输出:undefined
说白了,prototype
属性是只能通过 函数名.prototype
的形式来访问。
__proto__
又是什么?
首先要注意,这个“__proto__
”左右两边是2个连续的下划线。
它是 JS 中所有对象都有的属性,既然如此,函数也是个对象,那么它也必须有,所以上图可以再改改,把这个__proto__
加上去。
如上图所示,不管是函数本身那个框框,还是它的原型对象那个框框,都有__proto__
属性,并且__proto__
属性也是一个指针,它指向“它的构造函数的原型对象”。怎么理解这句话?
在 JS 的世界里,每一个函数都是 Function
对象的实例,也就是通过 new Function()
构造出来的。所以在这里,Person
函数也是由 Function
构造出来的一个实例,函数实例。所以,Person函数的__proto__
指向了Function
函数的原型对象,也就是Function.prototype
。
而 Person 的原型对象 Person.prototype
也是个对象,它也有__proto__
属性,它的__proto__
也要指向“它的构造函数的原型对象”,那么这个 Person
的原型对象的构造函数的原型对象又是谁呢?答案是Object
的原型对象,也就是Object.prototype
。
接下来深入一步,既然Function
的原型对象 Function.prototype
是个对象,它也有__proto__
属性吧,那Function.prototype.__proto__
又指向谁呢?答案是指向 Object
的原型对象,也就是Object.prototype
。
接下来再深入一步,Object.prototype
的__proto__
又指向谁呢?由于Object
对象就是 JS 的世界中的原始天尊了,所以Object.prototype.__proto__
指向null
。
好绕啊,我决定画个图。
请仔细观察此图,这个图里有一个非常诡异的浅蓝色箭头,它描述的信息是Object
的__proto__
指针指向了Function.prototype
。这是几个意思?你刚刚不是说了Object就相当于JavaScript的原始天尊了吗?额,额,好吧,Function对象是原始天尊的爸爸级别的。换句话说Object也是Function搞出来的。。。。
那根据构造函数Person( ) 创建出来的实例呢?
例如 : var p = new Person( );
由Person()函数构造出来的实例p就只是个普通对象了,所以p不是函数,也就是说,p这个对象只有__proto__
属性,没有prototype
属性。p.__proto__
指向“它的构造函数的原型对象”,所以p.__proto__
指向的对象是Person.prototype
,因为p
的构造函数是Person
,它的原型对象就是Person.prototype
。如图。
- 如图,虚线表示实例化的意思,
p
是由Person()
构造函数实例化出来的。由于p
不是函数,所以p
没有它自己的"原型对象"。
总结:
每个对象都有__proto__
属性,它指向该对象的构造函数的原型对象。
函数是一种特殊的对象,它由2部分构成,一部分是函数体本身,一部分是它的原型对象
函数除了有属性__proto__
,还有属性prototype
,prototype
指向该函数的原型对象。
到这里我们把__proto__
和prototype
基本说明白了,那么最后又引申出一个问题:这个该死的原型对象有什么用?
参考文章:
标签:__,proto,对象,JavaScript,JS,原型,prototype From: https://www.cnblogs.com/hhddd-1024/p/17458678.html