拿来吧!原型与原型链

374 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

原型链的理解就像作用域链,原型可以让我们自己在一个构造函数里面定义一个方法,然后批量创建的对象就可以直接使用该方法。 ------弗朗西西斯·航

原型的概念

构造函数的原型

首先我们要先知道在JavaScript中每一个函数都会自动的拥有一个名为'prototype'的属性(包括构造函数),'prototype'是函数的原型,也叫显式原型,它定义了构造函数制造出来的对象的公共祖先。通过构造函数产生的对象,可以继承到该原型的属性和方法。原型也是对象!!!

对象的原型

同时在JavaScript中每个对象都有一个特殊的属性:' _ proto_ ' (这里下划线与proto之间没有空格,因为排版的时候连起来打下划线会被吃掉,后面的内容我也是这样写的哈,抱歉 ),' _ proto _'是对象的原型,也叫隐式原型。

原型的原理

那么prototype是怎么工作的呢?在上,在上面你已经清楚了JavaScript中的每一个函数都会自动的拥有一个名为“原型”的属性,当然这个函数也包括了构造函数。那么每一个通过某一个构造函数创建出来的对象都可以访问到在这个构造函数里面定义的属性和方法。这样说的有点绕口;为了更加形象化的来理解,我们使用一段代码来更加形象的表示出来。

//首先定义一个人的对象
 const Person=function(name,birthYear){
    this.name=name;
    this.birthYear=birthYear;
 }
 //查看人的原型
 console.log(Person.prototype);

执行结果:

QQ图片20220712215138.png

然后我们向人的原型里面增加一个计算年龄的函数

 //向Person里面添加了一个计算年龄的函数
 Person.prototype.calcAge=function(){
    console.log(2022-this.birthYear);
 }
 //查看此时Person的prototype
 console.log(Person.prototype);

执行结果:

QQ图片20220712215325.png

对比上面的两张结果图,我们发现在用Person的prototype属性添加一个calcAge()函数,Person的原型属性里面就有了calcAge()函数,接下来我们就声明一个实例对象来深刻理解一下每一个通过某一个构造函数创建出来的对象都可以访问到在这个构造函数里面定义的属性和方法这句话。

 const Person=function(name,birthYear){
    this.name=name;
    this.birthYear=birthYear;
 }
 //向Person里面添加了一个计算年龄的函数
 Person.prototype.calcAge=function(){
   console.log(2022-this.birthYear);
}
const bruce=new Person('bruce',2001)
bruce.calcAge()

执行结果:

QQ图片20220712215022.png

最后输出了21,说明被Person这个构造函数所创建出来的bruce对象可以访问到在Person的原型里面新添加的calcAge()函数。

我们再来查看bruce对象

QQ图片20220712220058.png

里面并没有calcAge()这个函数,这是因为它原型继承了构造函数里面的calcAge()这个函数。 这样我们就不必每次在创建一个对象的时候逐个的声明某一个函数,就比如现在我们要给所有的中国人new一个身份证对象,我们每个人的国籍都是中国,这样我们只要在身份证的原型上面添加一个国籍为中国的函数, 需要使用国籍的时候只要调用一下这个函数就可以。

现在我们来看看对象的原型:__ proto __ ;那么在这里我们看看bruce的原型

const Person=function(name,birthYear){
    this.name=name;
    this.birthYear=birthYear;
 }
 //向Person里面添加了一个计算年龄的函数
 Person.prototype.calcAge=function(){
   console.log(2022-this.birthYear);
}
const bruce=new Person('bruce',2001)
console.log(bruce.__proto__)

执行结果:

QQ图片20220712223705.png

上面是bruce这个对象的原型‘__ proto __ ’,并不是原型属性'prototype',在这里我们看到了calcAge()这个函数,所以bruce可以调用它。所以对于bruce对象的原型,本质上是其构造函数的prototype属性。让我们只要验证console.log(bruce. _ proto_ ===Person.prototype)的值是不是true,下面我们来验证一下:

const Person=function(name,birthYear){
   this.name=name;
   this.birthYear=birthYear;
}
//向Person里面添加了一个计算年龄的函数
Person.prototype.calcAge=function(){
  console.log(2022-this.birthYear);
}
const bruce=new Person('bruce',2001)
console.log(bruce.__proto__)
console.log(bruce.__proto__===Person.prototype);

执行结果:

QQ图片20220712225009.png

它的值为true,所以这就验证了bruce对象的原型,本质上是其构造函数的prototype属性。

现在让我们来看看new一个对象的时候,其构造函数的prototype属性是怎么连接到new出来的对象的。那么我们需要有一点构造函数的知识。

在利用new关键字创建一个对象的时候,new关键字调用了某个构造函数,其创建对象一共分为四步:

  1. 先创建一个空的对象{ }

  2. 调用构造函数,并且在函数调用中设置this关键字到这个新创建的对象,所以这个this关键字就是这个新的空对象

  3. 将这个新的空对象与构造函数的prototype属性连接,即创建对象的‘ __ proto __ ’属性**

  4. 函数自动返回

所以上面的bruce对象在new的时候,在第三步将其与构造函数的prototype属性连接,所以会有console.log(bruce.proto===Person.prototype)//true;这就是原型链的部分组成;同时这也说明了对象的原型(隐式原型)会等于其构造函数的原型(显示原型)。

原型链

定义

在原型上再加一个原型,再加一个原型...将原型连成链,访问顺序也依照这个链的顺序;跟作用域链理解一样,就叫原型链。

理解原型链

在上面我们已经基本上搞懂了函数的原型以及对象的原型,现在让我来看看原型链,看图

60B47CCF-F24E-42D8-8ECE-692DEE5E1CA5.png

救命!救命!我的天!

看到这张图的第一眼有没有一种内心想直呼我的天啊这种感觉,我第一次看到这张图的时候也被吓了一跳(内心:这么多英文字母,我怎么看的懂?)然后定睛一看,诶这不全是prototype跟对象的原型关键字。好了英文我们看的懂,那么我们就已经懂了三分之二,现在来分析分析原型链。

分析

首先这张图可以从左往右看有三个区域,我们先看中间,中间为构造函数,最上面的是Foo()构造函数,既然是构造函数那么它就有构造函数的原型:Foo.prototype,你看用方框框起来的function Foo()是不是有个prototype指向了Foo.prototype(构造函数Foo的原型)。这是第一根线。然后我们看到最右边Foo.prototype这里,我们知道Foo.prototype也是个对象,那么对象的原型即Foo.prototype的 __ proto __ 就是图中间的Object()构造函数的原型(Object.prototype)。因为由上面的console.log(bruce. _ proto_ ===Person.prototype)的值为true我们可以知道,创建出来的对象的' _ proto _ '等于构造函数的prototype。这是第二根线。 好了现在我们目光看向图的最左边,最左边乍一看都是对象(你看有new关键字)是不;既然这是对象,那么它就有对象的原型‘ __ proto __ ’与其构造函数的原型(Foo.prototype)连起来了。这是第三根线。 现在让我们将这三根线连接起来这就形成了一个简单的原型链。所以我们再来看看原型链的定义:在原型上再加一个原型,再加一个原型...将原型连成链 。是不是有点感觉了,被创建出来的对象的'_ proto _'属性(隐式属性)等于其构造函数的'prototype'属性(显示属性),而其构造函数的'prototype'属性(隐示属性,因为它是个对象)又等于Object()构造函数的'prototype'属性(显示属性),所以这样就形成了一条最简单的原型链。

小结

  • 对象的'_ proto _'属性(隐式属性)等于其构造函数的'prototype'属性(显示属性)
  • 原型也是对象
  • 原型链其实就是一个原型套一个原型,理解起来跟作用域链一样

(这只是本人对原型与原型链的一点理解,如果上面内容有误还望赐教,不胜感激!)