关于原型和原型链的详细理解

在浏览本文之前首先明白什么是对象?,在JavaScript中我们会用typeof( )这个函数,那么使用typeof( )输出的值一般会有number、boolean、string、undefined、function、object等这些类型,那我们本文要研究的就是object和function这两种类型,返回值是这两种类型的就是我们本文所要研究的对象

什么是对象?

那么对象该如何去定义呢?我个人认为对象就是一些属性的集合。举个栗子🌰

 var Laowang={
     'name':'老王',
     'feature':'热心肠',
     'skill':function( ){
          alert('特长是修水管');
 }
}

那么在上面的例子中’Laowang’就是一个对象,你肯定会有疑问关于我对对象的定义,在’Laowang’这个对象中出现了function方法,但是这个方法在’Laowang’这个对象中是以键值对的形式出现的,所以这个function就是’skill’这个属性的属性值。所以验证了对象就是一些属性的集合这句话。

上面的例子很好理解,但是数组和函数好像不能这样去定义属性,但他们也是对象啊,不要迷惑,他们有自己定义属性的方法。以函数为例:

var laowang=function( ){
      alert('修水管');
}
laowang.skill='热心肠';
laowang.skill2='爱串门';
laowang.skill3={
    'a':'亲切问候邻居家孩子'
}

这不,function就被赋予了skill、skill2、skill3这三个属性。

上面说到function和objec这两个返回值是对象,既然都是对象,为什么返回的不是一个值。由于function和object的关系比较特殊所以返回的值不同,我在下文会详细讲到function和object的‘特殊关系’

function和object的关系

上文说道function和object都是对象,但是function的返回值是function而不是object,那么他俩之间肯定有某种’神秘的关系’。

function和object的关系其实就像’先有蛋还是先有鸡’这种让你抓狂的问题。function是object的一种,但是object又是由function创建的,什么,你要打我脸?

var arr=['a','b','c'];
var obj={
    'name':'老王',
    'age':'99'
}

以上两个都是对象,但是都不是由function创建的,不要忘记了这种写法只是用字面量的方式来创建对象的。这种写法只是为了让代码更简单明了更容易理解。归根到底以上两种对象是由function创建的,请看以下代码:

var arr=new Arry('a','b','c');
var obj=new Object();
obj.name='老王';
obj.age='99';

在以上代码中Arry( )和Object( )都是函数,通常我们把他们当做构造函数,由构造函数我们可以new出很多实例对象,构造函数和我们平常自定义的函数没有语法上的区别,区分就是构造函数一般首字母是大写的。

是不是感觉很乱?为什么function和object的关系是这样的,不要慌张,耐心看完本文你就会豁然开朗。

原型(prototype)

上面扯了半天对象,到这里终于讲到本文的主要内容了–原型(prototype)。

那么prototype到底是什么呢?不要着急,让我们一步步来。

上面我们说到function也是一种对象,现在对这个应该没有任何疑问了,如果有疑问请滑动你的鼠标从头开始看!!

function作为对象,那么他肯定是若干属性的集合,在JavaScript中,function默认有一个属性,这个属性就是prototype,既然是属性那么肯定有相对应的属性值,prototype的属性值是一个对象,既然是对象,那么肯定是若干属性的集合,这个对象里有一个默认的属性:constructor,这个属性相当于一个’指针’,指向这个函数本身。
以下图为例:

《关于原型和原型链的详细理解》

prototype既然作为对象,属性的集合,不可能就只有constructor这一个属性,肯定可以自定义的增加许多属性,如上图所示。

上图还出现了person1这个实例函数,他是由构造函数Person实例化出来的,上文说到每个function都有prototype这个属性,person1也不例外,他的prototype大家会发现和Person这个构造函数的是一样的,实例对象的原型指向的是其构造函数的原型对象。我们再看一段代码:

var Person=function(){};
  Person.prototype.name='Nicholas';
  Person.prototype.age='29';
  Person.prototype.job='Software Engineer';
var person1=new Person();
console.log(person1.name); // 'Nicholas'
console.log(person1.age); // '29'

在上面代码中person1是由构造函数Person实例化出来的,而且我们也没有给person1定义任何属性,但是person1.name==’Nicholas’;这是为什么?那我们就不得不说起-proto-这个属性了,每个对象都有这个属性,这个属性一般是隐藏的我们看不到,但是并不妨碍我们去了解他。

这个属性指向了创建这个对象的构造函数的prototype。即:person1._ proto_ ===Person.prototype,下面我们来看看这个’_ proto_’是什么鬼。

_ proto_,隐式原型

上文我们提到_ proto_,那到底这个_ proto_是什么呢?我看下面的代码:

var Person=function(){};
  Person.prototype.name='Nicholas';
  Person.prototype.age='29';
  Person.prototype.job='Software Engineer';
var person1=new Person();
  console.log(person1._proto_===Person.prototype);//true

通过看上面的代码会发现结果为true,你没有看错,这也不是巧合,这是必然的结果。

实质上person1是被Person实例化出来的,那么person1._ proto_===Person.prototype,下面用图给你展示一下:

《关于原型和原型链的详细理解》

上图的o1和o2是由Object实例化出来的,他们的_ proto_指向的是Object.prototype,这就说明:
每个对象都有一个_ proto_属性,指向创建该对象的构造函数的prototype

那么你肯定会问’每个对象都有一个_ proto_属性,指向创建该对象的构造函数的prototype‘,那Object也是一个对象,肯定也有_ proto_属性,那他指向谁?

关于Object._ proto_的指向问题很特殊,在这个Object._ proto_是个特例,它指向null,这个地方大家一定要牢记。

也许你还会有另一个疑问,函数也是对象,实例化出来的函数的_ proto_属性指向其构造函数,那么其构造函数的_ proto_指向谁?

Function这个前面没有提到,现在拿出来晒晒,构造函数是由谁创建的,就是由Function这个函数创建的,所以你上面的疑问就很好解答了。再用一张图让你更清晰的看清他们的关系:

《关于原型和原型链的详细理解》

这张图清晰的表明了自定义构造函数、Object、Function之间的关系!

眼神好的人会在上图发现一个问题:自定义函数Foo._ proto_指向Function.prototype,Object._ proto_指向Function.prototype,怎么Function._ proto_也指向Function.prototype,这不就是形成了一个’死循环’么,来,让我们仔细捋一捋,Function也是一个函数,既然是函数那么他肯定是由Function创建的,那么上面的’死循环’就解释通了。

在这里我还要解释一个地方,Function.prototype也是一个对象,那其肯定有_ proto_属性,那么指向谁呢?其指向Object.prototype,为什么呢?Function.prototype是一个普通的对象,就可以看成这个对象是由Object实例化出来的,那么Function.prototype._ proto_指向就是Object.prototype了。

下面上一张完整的图片,大家可以按照下面这种图片捋一下自己的思路,因为上面讲了那么多肯定会有些乱。

《关于原型和原型链的详细理解》

这张图完整的呈现出了实例对象、自定义函数、Object、Function之间种种错综复杂的关系,不要怕麻烦,一条一条的去找对应的关系。

继承

为什么会说到继承呢,因为继承是通过原型链来体现的,所以一并放在这里讲了。我们先看一段代码:

function Person(){  }
  var p1=new Person();
  Person.prototype.name='老王';
  Person.prototype.age='99';
  console.log(p1.name);//'老王'

以上代码中,p1是Person实例化出来的函数,我并没有给p1定义name这个属性,那p1.name是怎么来的–是从Person.prototype来的,因为p1._ proto_指向Person.prototype当访问对象的某个属性时,现在这个对象本身去找,如果找不到那就顺着_ proto_往上找,直到找到或者Object.prototype为止

由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。

讲到这里,关于原型和原型链就结束了,希望各位能深刻的理解。

    原文作者:我住隔壁我姓吴
    原文地址: https://www.jianshu.com/p/700a2a579351
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞