详解JavaScript中的__proto__和prototype

发布时间 2023-06-05 18:27:07作者: 黄河大道东

  对于 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指向的对象就是我们说的原型对象)。看图

img

  如图,我们上边声明了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__加上去。

img

   如上图所示,不管是函数本身那个框框,还是它的原型对象那个框框,都有__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

  好绕啊,我决定画个图。

img

  请仔细观察此图,这个图里有一个非常诡异的浅蓝色箭头,它描述的信息是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。如图。

img

  • 如图,虚线表示实例化的意思,p是由Person()构造函数实例化出来的。由于p不是函数,所以p没有它自己的"原型对象"。

总结:

  每个对象都有__proto__属性,它指向该对象的构造函数的原型对象。

  函数是一种特殊的对象,它由2部分构成,一部分是函数体本身,一部分是它的原型对象

  函数除了有属性__proto__,还有属性prototypeprototype指向该函数的原型对象。

  到这里我们把__proto__prototype基本说明白了,那么最后又引申出一个问题:这个该死的原型对象有什么用?

参考文章:

https://www.cnblogs.com/ldq678/p/9381327.html

https://blog.csdn.net/m0_49016709/article/details/123254326