Javascript原型与原型链由浅入深,让你再不害怕大小厂面试官,满满干货,遇到原型链的题就开心

前言

       来啦,来啦,来啦,兄弟们,时隔多年,我又再次回到了CSDN更新学习类的文章,这次咱们把原型链好好讲一讲,这次咱们把原型链彻底给它搞懂了,再也不害怕面试的时候被问到对原型链的理解。

       知道很多铁子们,不管是还在学校里准备参加秋招的,还是已经工作2~3年准备跳槽的,有时候面对原型链的面试总会头疼,特别是那种有时候觉得对原型链这一块已经理解的非常深刻了,可是被面试官问起的时候却也总会回答的一脸懵逼,模棱两可,自己都不知道自己在讲什么。

       特别是有时候面试官总还总会让你在原型链方面做选择题或者判断题,(1)Array.prototype.__proto__ 和 Object.prototype,Function.prototype之间的关系

(2)Array.__proto__和Object.prototype,Function.prototype之间的关系

(3)Function.__proto__ 和 Function.prototype之间的关系

        当问到以上几点时,可能你面试的时候直接懵了,你可能会在心里嘀咕:咱问点正常的问题不行吗?问这个干啥?

        学习前端这么多年,咱们会发现好像原型链这一块并未搞得很深刻,也并没有拿捏的像呼吸一样简单,想去靠死记硬背,可是有时又感觉根本记不住。所以且听我接下来的分析,让你彻底对原型链爱不释手。请各位铁子仔仔细细,一个字一个字的把我下面搞笑而不失体面的讲解阅读完毕,你就彻底理解js的原型和原型链啦。

什么是原型?

       什么是原型?有铁子肯定会霸气回怼,ES6没出class 类的时候,Javascript想要实现面向对象这一层面的数据共享,想要占用的内存更小一点,想要保持自己面向对象特征下的封装,继承,多态,然后提供了原型和原型链来方面实现这一特性。当然,这话是没错的,可是你拿这个东西真正去理解原型和原型链的时候,你又懵了,因为面试官又会问,ES6的class跟原型之间什么关系,有什么共性?然后又答不上来。

       原型与原型链本身就是前端Javascrip的基础,我们却无法正确的回答上来,这些年也陆陆续续的临时抱了很多的佛脚,看了很多博客,文章也好,图片也罢,可是就是无法更好的理解原型与原型链,继承等到底是什么东西。因为有时候咱们大多数会自己创建出一个function函数,function apple(name){ this.name = name},然后用new 出一个 apple()来:let app = new apple('苹果');然后咱们可能会在原型上面挂东西,app.prototype.color,app.prototype.eat等等,然后咱们可能就去学习继承去了,就觉得原型和原型链这一块搞懂了,就是面向对象,没有那么难。可是真的等到面试官问了,咱们才会知道,没复习好。

       好,咱们现在从最简单的模式开始分析原型:先创建一个函数。

以下我用最简单的方式创造出了两个函数,apple和banana,咱们使用普通函数调用的时候是apple(), banana(), 咱们使用构造函数创建对象时,使用new apple(),new banana(),这都JS是司空见惯的方式,但是不管使用哪种调用方式,它都是函数(function)。那么是函数,在创建出来的时候就会被赋予prototype属性,咱们先别想prototype会指向原型的情况,咱们先把这个prototype只当作一个属性来看,因为函数(function)本身是一种特殊的对象,

Function instanceOf Object === true,不信你自己试试。Function是Object的一个实例,多恐怖你看

那么prototype既然是每个函数创建出来以后固有的属性,那么apple.prototype和 banana.prototype输出来是啥?

咱们发现apple的prototype属性输出后,其实是一个Object对象类型,里面又有两个属性:constructor:f apple(),[[Prototype]]: Object

[[Prototype]]:Object这个先不讲,讲了你们现在也听不懂,先剧透一下,这个就是链,用来一层层找属性的,把实例,原型等关联起来的链(就像曹操赤壁之战时把战船连在一起的铁锁链),找不到属性的时候就沿着这条链向上找。

所以咱们先讲讲constructor,一看到这个属性前端学习者们会大声吼出来,构造函数!!!yes,构造函数,就像你们现在使用ES6 的class。不是像本身就是,class是prototype的语法糖,本质上没区别。好,咱们写一个正规点儿的构造函数看一下:

上图中的Person类是不是有个原型Person.prototype,输出来里面有constructor属性以及三个get函数。而且这个constructor输出来指向了class Person这个类。

好,咱们把上面的那个构造函数的定义形式放开,看看是什么样的。

这个构造函数的prototype是不是跟那个class属性创建出来的prototype属性很像,甚至构造函数的prototype属性跟class类里面的布局都很像,至少从从结构上一模一样。class类里面有constructor,Person.prototype里面也有constructor。class类里面有三个get函数,Person.prototype里面也有三个get函数。

此时有人可能又要发生疑问了,这两种写法真的一样吗?你有种把上下两种写法的注释全部放开看看。

报错啦,兄弟们,Person早已经被定义,也就是上下两种写法不能说是很像,只能说是一模一样,连google浏览器都看不下去了,直接给你来个报错。

下面这句话非常重要,介绍了prototype不能完全去等价于class类的原因,prototype相当于class类只是为了好记。别到时候看完我的文章,你跟面试官说,prototype原型就是它那个class类,这锅我背不起!!!

        所以,什么是prototype,prototype咱们来按照一个咱们能记住的,非常好理解的办法来定义,一个函数的prototype,其实就相当于是一个class类,Person.prototype 相当于 Person Class,但是这种说法对不对呢?不完全对,但是好记。为啥说这种说法不对呢?因为[[Prototype]]属性,你定义类的时候会用考虑把这个属性也写上么?那么Person.prototype自带[[Prototype]]属性,这是一个非常关键的东西,它叫做链,你定义类的时候会有么?原型链,原型链,没有链那原型的灵魂就没有了。可是你把Person.prototype去相当于 Person Class,好记啊。

咱们创建一个function Person() {}的时候,它的Person.prototype(原型) 是这样的,里面有constructor函数

你创建一个Person class类的时候,咱们是这样的。

       有的铁子,有时候为了理解原型链,把原型这个东西当作是“父类”,可不可以呢?可以,那会不会搞混就不好说了。举个例子,你马上会觉得以前把原型当“父类”,真的会搞混。

       按照传统的面向对象语言的写法,是定义一个父类:

class Father {

    constructor() {}

},

然后定义一个子类:

class Son extends Father {

   constructor() {

     super();

   }

}

然后用子类Son去创建对象,let son = new Son();

那此时son.__proto__  (__proto__是实例对象的专有属性,是实例专门用来访问创建出自己的原型的),所以son.__proto__ === Son.prototype 这样没错吧,son.__proto__指向了创建出自己的原型,也就是那个类Son,上面讲到的Son.prototype 相当于是 class Son。

那Son.prototype是谁呢?有铁子开始懵逼了啊,prototype不是父类吗?那Son.prototype === Father 这是对的,那就啼笑皆非了。

所以咱们应该记住prototype不要记成是父类,容易搞混,prototype相当于 那个你熟知的class 类,Son.prototype <---->  class Son,
Son.prototype.constructor  就等价于class Son里面定义的那个constructor,也就等价于你的那个function Son() {};就如同下图:

讲到这,各位一直迷茫的铁子们是不是醍醐灌顶。

那么实例对象有原型吗?let person = { name: '曹操', age: 54, sex: '男'}; 这个person有原型吗?只能说person没有prototype属性,但是每个实例对象是有__proto__属性的,那么person.__proto__是什么呢?是把这个person实例创建出来的那个类,person对象没有任何的其他的构造函数创建出来,如let person = new Person(); 那么它属于谁呢?属于Object这个大类。是由Object类创建出来的。既然属于Object这个大类 => Object.prototype(是不是马上得出这个结论?今天学的用上了吧)请看下图:


最后用官方的解释来解释一下什么是原型:(原型和原型链的目的,为了实现数据共享,减少内存消耗,实现继承,封装和多态)

1,原型是一个对象,为其他对象提供共享属性的对象,又称原型对象。可以说原型不是一个固定的对象,它只是承担了某种职责。当某个对象,承担了为其他对象提供共享属性的职责时,它就成了该对象的原型。换而言之,不同对象的原型可能都是不一样的。 2,几乎所有对象在创建时都会被赋予一个非空的值作为原型对象的引用,来实现共享数据的效果。

什么是原型链?

讲完了原型,咱们来讲讲什么是原型链,很形象的表示就是刚才咱们截图中所看到的,constructor属性的下面那个兄弟[[Prototype]]: Object

原型链是原型的灵魂,没有链,原型的数据共享就无法实现了,那去实现继承还有什么意义呢?

说到原型链,咱们就得理解理解[[Prototype]],__proto__,和那些name,age,sex,getName(),getAge(),getSex()之间的爱恨情仇了。

__proto__属性

__proto__ 一定要去找prototype属性,不要用__proto__属性去等于任何一个类名或者函数名。

__proto__只能去找prototype原型(咱们上文说到的类,好记!!!)。

如:Array.__proto__ === Function.prototype。为什么?下面会徐徐道来。

首先咱们来讲讲__proto__的事情,我们说,实例对象没有prototype属性,但是有__proto__属性,__proto__属性指向了创造出它的那个类,哪个类?当然是创造出对象的那个prototype啦。

let person = { name: '刘皇叔', age: 58, sex: '男', message: '终是皇叔负了蜀,接着奏乐接着舞'}

person.__proto__ === Object.prototype;

或者说

function Person() {};

let person = new Person();

person.__proto__ === Person.prototype;  // true

实例对象没有prototype属性,但是有__proto__属性 这句话对不对呢?错了,真的是大错特错了。为何?举个例子就能反驳它。

Array,String,Boolean,Number,这些函数都是Function这个大类创造出的实例,不信啊?来

连Object都是Function大类创造出来的实例,其他的那些函数就更别说了,因为他们的本质都是函数。

Array,String,Boolean,Number它们是Function创建出的实例么?是,但是它们既有prototype属性,又有__proto__属性。所以上头那句话是错的。

现在铁子们可以说一下Array.prototype,String.prototype,Boolean.prototype,Number.prototype他们是啥呀?

Array.prototype 是 Array自己归属的那个类,里面有很多属于Array自己的函数。是不是感觉上一个小节中原型的知识用上啦?铁子们,切记不要用(Array.prototype === Array) //false,上面红字部分说的很清楚了,看了上面的介绍,你很快就会知道Array.prototype是个什么东西,不是指向Array本身,而是相当于是Array的那个class类。

那么以此类推,Number.prototype,String.prototype,Boolean.prototype都是那样的,里面有咱们熟悉的函数。但是有一个东西是对的,请看下图:

别问为什么?问就是new Array(),new String(),new Number(),new Boolean() 全部都是构造函数。

那Array.__proto__,String.__proto__,Number.__proto__,Boolean.__proto__等于什么呢?

Array,String,Number,Boolean都是Function创建出的实例,那么以上的答案就是:

Array,String,Number,Boolean 的 __proto__ 去找 Function这个大类的 Function.prototype。

那像 let arr  = new Array(3);

        let str = 'fruit', str1 = new String('sqry');

        let num = 5, num1 = new Number(6);

        let isFood = true, isOpq = new Boolean(true);

        这些基本类型变量,它们有没有__proto__属性呢?答案是:有。

基本类型变量的__proto__指向产生这些变量的那个大类的prototype原型

讲到这,铁子们一定会觉得Function大类简直统治了一切哈,String,Array,Boolean,Number全都由Function产生,Object也是由Function产生的,new Object()嘛,也是一个构造函数,但是很多人都会在Function和Object这两个东西的原型链上搞得很头疼,来兄弟们,不要头疼,且听我慢慢道来~~~

Object,Function 和 null

讲这三个顶层对象的时候,首先讲个鬼故事,咱们在开头不是说function是特殊的对象吗?

console.log(function instanceOf Object) // true,但是,各位老铁,恐怖就恐怖在,

Object本身是个对象的构造函数,new Object() 可以创建对象实例,可是Function却又是特殊的Object。Object,Function两个处于顶层的两个大类,变成了你中有我,我中有你。

所以 Function.prototype.constructor === Function   Function的prototype就像构成Function的那个类class Function,那么Function.prototype中的constructor属性就是Function()构造函数本身,能new Function()的那种。

Function 作为顶层构造函数:(上面已经没人了)

Function.__proto__ === Function.prototype 只能自己消遣自己。

Object.prototype.constructor === Object;

Object.__proto__ === Function.prototype; // true

Object作为构造函数,是由Function new出来的实例,所以Object的__proto__肯定会找到Function.prototype,跟Array,String,Number,Boolean一样。

总结:A.prototype 相当于类class A(好记),A.__proto__是造出A这个实例对象的类B的prototype。

prototype.__proto__

很多铁子不知道这个东西该怎么推,我今天教会大家。

首先告诫各位一句,没有__proto__.prototype,你设置任何的变量,对象或者类去推导,__proto__.prototype的答案都是 undefined,没有其他,所以各位不用关心这个,如果面试的时候面试官问起 A.__proto__.prototype是啥?直接回答undefined。

为何?解释一下,假设A.__proto__ === B.prototype,

那么 A.__proto__.prototype === B.prototype.prototype,JS任何一个函数的原型中没有另外的prototype属性,所以是undefined。

那么 prototype.__proto__又是什么呢?这个完全不需要举实例,比方说A,抛开A是什么玩意儿不谈,请问A.prototype是什么?A.prototype本质是一个实例对象吧,相当于class A那个类嘛。这里实例对象是说let obj = { name: '曹操', age: 54 } 这样的obj,不是Object那个大类,那么obj.__proto__ === Object.prototype,所以A.prototype.__proto__ === Object.prototype。前面讲到过,let obj = {},obj的__proto__ 是指向由创建出obj的类Object的prototype原型。这里的A可以是以下构造函数:String,Function,Boolean,Number,Array。

但是有一个不行,Object.prototype.__proto__ === null; 记住这个,Object.prototype.__proto__指向最顶层 null,这个是唯一的。

内置属性[[prototype]],原型链的构成

在此讲两个例子就可以讲明白这个东西

(1) 第一个例子,自己创建一个构造函数

比如说,我们定义了一个构造函数person,里面放了三个属性参数:name,age,sex

我们定义完person函数以后,new出两个对象,personA, personB。

输出personA和personB以后,咱们可以看出,personA进行参数传入后有值,personB的属性都是undefined,当然这不是重点,重点在于作为一个普通的object对象,他们都有[[Prototype]]属性,当然,一个普通对象当然有[[Prototype]]属性,且这个[[Prototype]]属性也是一个Obj对象类型,那它里面有什么,又是起到什么作用的呢?

personA和personB中的[[Prototype]]属性其实是Person.prototype,造出他们这两个实例对象的那个大类,类似于class Person的那个东西。

而我们发现[[Prototype]]属性里面的内容又包含了两个属性constructor和[[Prototype]],当然constructor就是构造函数function person()那个东西。

而当我们点开[[Prototype]]中的那个[[Prototype]]属性时,我们发现里面其实还有东西,这个里面也有constructor构造函数,这个构造函数是Object()。这说明我们从一个小小的personA找到了Perosn.prototype,又从Person.prototype找到了Object.prototype,而此时下图的第二个红框有一个__proto__,我尝试着打开了这个__proto__。

最后发现这个__proto__其实是,person.__proto__ === person.prototype,原来这个personA.__proto__被放在这了。

那么接下来这个personA.__proto__中的[[Prototype]]属性里面我想得是Object.prototype,而Object.prototype里面又有一个[[Prototype]]属性,这个[[Prototype]]属性里面又有一个__proto__,里面指向了null。咱们验证一下是不是,请看下图:

上面的情节有点绕,所以总结一句话就是,顺着一条线走,personA.__proto__指向了原型person.prototype,而person.prototype本身也作为一个对象,那么person.prototype身为实例对象,是由Object这个构造函数new出来的,所以person.prototype.__proto__指向了Object.prototype,而最终Object.prototype.__proto__指向了null。讲明白了。

 咱们知道person.prototype本身作为一个对象类型,一个实例对象,它的person.prototype.__proto__是指向Object.prototype的。那function person(){}作为一个构造函数,那么它的形式是函数,所以person.__proto__是指向了Function.prototype的。

即:person.__proto__ === Function.prototype

 (2) 第二个例子,用现成的实例,如:Array,Number,String,Boolean等构造函数

Array,Number,String,Boolean本身由Function构造函数派生而来,是Function构造出来的实例,所以Array.__proto__,Number.__proto__,String.__proto__,Boolean.__proto__是Function.prototype

而Array.prototype,Number.prototype,String.prototype,Boolean.prototype本身作为对象类型,其实 Array.prototype.__proto__是指向Object.prototype的,而Object.prototype.__proto__指向了null。而Object本身作为构造函数,本质上是函数,所以Object.__proto__ 指向了Function.prototype。

原型链的用法

当然如果像上面讲的,咱们学习和使用原型链需要找到最顶层,Function也好,Object也好,null也罢,那这个东西就太麻烦了,没人想去理解了,所以上面讲到的去找最顶层只是给读者一个印象,就是说所有的函数也好,底层实例对象也罢,都能最终找到Function,找到Object,找到null那边去。而真正的原型链的用法咱们不用到Object,Function那么深,在他们前面那层停下即可。再来举个例子:

创建一个person构造函数,不传任何参数,给person原型的属性name,age,sex进行初始化,类似于class person { name: '小明', age: 12, sex: '男', constructor(){} }; 如下图所示:

那么给personA对象进行属性初始化,而personB什么都不传,他们会是什么样的呢?咱们一起来看看

我们会发现personA有值确实是这样,而personB没有值,我们却看见里面每个属性都有赋值,而且值的内容是我们在person的原型上赋的初始化值,原因是什么?因为我们作为用户在personB对象上访问name,age,sex属性的时候,personB知道自己没有,就沿着它的原型链找到了创造出它的person.prototype,而此时的person.prototype中正好有name,age,sex属性,所以personB就用其person.prototype中的name,age,sex属性了。 如同下图:

此时肯定会有人问,那现在我给person2赋值,person2.name = "孙悟空"; 会不会修改掉原型上的属性值啊,不会的,因为person2.name就是给person2自己添加属性了不会修改掉原型的person.prototype.name 属性的。

原型链在赋值方面的第一坑

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luolvzhou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值