面试官想看我写一篇关于“原型链”和“构造函数”深入理解的文章

前言: 在参加工作的面试过程中,我搬出了我的个人掘金账号写在了简历里,面试官很感兴趣,他不仅关注了我的账号,还想让我写一篇《原型链》的见解,由于老早就想总结一篇关于原型的文章,奈何自己刚开始的知识储备不足,导致无从下手。今天正好回顾一下基础知识,本文也就此诞生。

今天我就以一个新手过来者的角度去谈一谈这两个对于 JS 来说十分重要的概念的理解,我向来不喜欢刻板地背概念似的介绍某一个知识点,我会以最小的代码量,最少的专业名词试着让你自己去理解这个概念。

如果你对原型构造函数的概念很模糊,或者说理解的不是那么的清楚,又或者说能说上个一二三,但是让你完整表达出来却又无从下嘴,那么本文将非常适合你阅读。

tips: 读完本文你会深刻理解 new 关键词背后发生的故事,接下来的内容可能会推翻你之前对于构造函数的认知。


一. 开胃小菜(一个简单的思考题)

  1. 首先我们不考虑什么是原型,我们先来定义一个对象。
    image.png
    可以很清晰的看到,这个名叫 hzf 的对象身上有三个属性,其中有一个叫 say 的属性,它的值是一个函数。现在我想让你调取这个 say 函数,你怎么办呢?

  2. “简单,我反手就是一个hzf.say()”。恭喜你,你现在距离领悟原型又近了一步。是的,我没和你开玩笑。

  3. ok,难度增加。我们现在定义了一个数组,内容很简单,只是简单的三个数组组成。
    image.png

  4. ok,现在的需求是,分别在控制台输出数字中的每一个元素。

  5. “这还不简单?上面你不是都写了答案了吗?用 forEach 啊!“于是你几乎是条件反射的想法写了下面的代码。
    image.png
    你看着控制台上的输出开始怀疑这篇文章到底在讲什么。
    image.png

  6. 你还记得刚刚我们如何调用对象 hzf 身上的 say 方法吗?hzf.say()。对!你不觉得我们调用 forEach 的方式和它十分相似吗?

  7. 但是有一个非常重要的前提是:say 这个属性对我们来说是十分清晰的,什么意思呢?因为 say 是我自己亲手定义在 hzf 对象身上的方法,是我敲下键盘上的一个字母一个字母定义在上面的,所以我可以很理所当然的 hzf.sya() 这样来调用对吧?。
    那么问题来了,你什么时候给 arr 身上加过叫 forEach 的方法吗?那你为什么竟然可以调用一个你从来没定义过的方法呢?

  8. 下面引出我们标题要讲的 js 的核心概念之一----原型

二. 初识原型 prototype

  1. 首先我们不要质疑为什么叫 prototype 这个单词,因为这个单词本身就是原型的意思。
    image.png

  2. 回到上面的 arr,我们先在控制台输出一下 arr
    image.png

  3. 虽然说 JS 万物皆对象,但是你就算让我拼命想,我也真想不到 forEach 这个单词和这个 arr 到底有什么联系。

  4. 等等,我好像注意到了什么,为什么这下面还有个 prototype 属性?我得点开看看。
    image.png
    我们不仅发现了 forEach,并且好像还发现了很多很多我们压根没在 arr 身上定义的方法。

  5. 好奇怪啊?我明明记得我没定义过 prototype 这个属性啊?等等,我好像想起来了,NDN 上介绍的写法是,Array.prototype。那这个 prototypearr 现在的这个 prototype 有没有什么联系呢?

  6. 咱们一步一步来。接着再来看一张我在 MDN 上截的一张图。
    image.png

  7. 不知道大家最开始和我有没有相同的疑问🤔,这作者到底重复写这么多遍这个 “prototype” 干啥啊?我知道你想告诉我这是数组身上的一些方法,你直接写 Array.forEach 不就完事了吗?不禁让我想起了在写大学本科论文时拼命凑字数的场景。难道网站作者也是在疯狂凑字数?(不是

  8. MDN 它甚至在自己举例子的时候都没加 forEach 之前的这一串 Array.prototype
    image.png

  9. 能想到这里,说明你还是很棒的,经过上面的介绍,我们知道了存在 prototype 这样一个东西,但是要想了解原型的全部,我们就必须先了解什么是构造函数

三. 构造函数

  1. 我们先来理解为什么会有所谓的构造函数,在编程领域,任何一段代码或者任何一个名词都有它存在的意义,要想深刻理解它,你就得知道为什么有它

  2. 我们来模拟一个场景需求: 假设现在给你一个名单,上面有1个人的 name age 信息,现在让你把这个信息表示出来。

  3. “这还不简单?”于是你得意满满地写下了下面这段代码。
    image.png

  4. ok,现在增加一点难度,现在有3个人的信息,你还是要把这3个人的信息表示出来。

  5. 你皱了皱眉头,还是一行一行代码敲着。
    image.png
    你或许只是仅仅简单地 ctrl C+ V 了一下,然后改了名字就完成了这个需求。嗯,不错,很聪明~

  6. 那我们再加一点点难度,现在需要你创建10个用户的信息。来吧,展示~👋

  7. 难道你还要再去 ctrl C+ V 10 份,然后一个一个改名字?No,No,No. 程序员都是很懒的,我们有没有更好的方法去实现这个需求呢?啊~要是有一个可以帮我写代码,呸,说错了。啊~要是有一个可以重复生成对象,并自动帮我们填充信息,然后把生产的东西给我们就行了的东西就好了。有这样的一个东西吗?还真有!没错,就是函数

  8. 知道该使用函数以后,我们很轻易的就可以写出这样的代码。
    image.png
    这个名叫 personInfo 的函数可以接收两个参数,一个 name,一个 age。然后我们去下面传递参数拿返回值就行了。不仅代码行数减少了,而且排版更加清爽简洁。

  9. 先别着急下结论,这还不是一个构造函数,我们接着往下走。

四. new 关键词背后做了什么

  1. 在上面我们了解到,我们可以制造一个函数来专门生产对象返回,其实 JS 还存在另外一种更加简洁的方法。那就是使用 new 关键词。

  2. 这里我们对 personInfo 不进行任何的改动。
    image.png
    这是控制台的结果:
    image.png

  3. 你可能会想,这不是一模一样?耶~我会用 new 了。其实这里你完全误解了,new 关键词帮你自动做了很多操作你都没有用上。说白了,这里的 new 完全被你浪费了,new 帮我们做了什么呢?我们继续往下看。

  4. 我们稍微改造一下,替换成一个当想使用 new 关键词生成对象时,函数正确的写法。
    image.png
    你甚至连返回值都不需要写,这些脏活累活都交给了 new 来做,你只需要专心传递正确你的数据信息即可。

  5. 接下来我们来测试一下看看是否可行。
    image.png
    嗯~很好,代码又少写了两行,领导直夸我内行。

  6. 让我们对比一下,这是之前的 personInfo 函数。
    image.png
    这是后来的函数:
    image.png
    从对比中你就不知不觉领悟了 new 背后干的事情。

    1. 帮我们自动生成了一个空对象。
    1. 帮我们自动将 this 指向了上一步创建的那个空对象。
    1. 帮我们自动返回了这个空对象。
  1. 看到这里,你可能都开始抢答了,“啊,我知道了,我知道了,这个用 thispersonInfo 就是构造函数!”。等等,接下来我说的话可能要推翻你之前的认知。

其实在 JS 中压根不存在所谓的构造函数。这个名词用更精确的术语的来讲应该是函数的构造式 调用。

  1. 接下来我们去验证这一点。你甚至可以自己测试一下,当你写一个空函数,我函数体内什么也不干。我就纯 new 一个东西出来,我要看看这个 kong 到底是什么?
    image.png

  2. 如果按照之前的构造函数的说法,那这个 test 也叫构造函数了。你能说它没构造什么东西吗?这不是下面放着了吗?一个空对象 kong。这里面用为什么用双括号包裹【【prototype】】我会在下面讲解到,请仔细阅读。
    image.png
    等等,这个不是 new 帮我们做的事情吗?关你这个 test 函数什么事啊?

  3. 还有你注意到了吗?我所有的函数都并没有以大写开头,我是故意这样写的,为了就是让我们清楚一点,所谓构造函数大写开头是人们约定俗成的规则,并不是 JS 层面的语法要求。

  4. 是的,没错,刚刚我们其实验证了两个事情。一个是 new 关键词确实帮我们生成了一个空对象。第二个是如果存在构造函数,那么所有函数都可以叫做构造函数。所以我们得出结论:

js 里没有所谓真正意义上的构造函数这样一种特殊函数,而是只存在函数的构造式调用,也就是使用 new 关键词去调用一个函数。 我们只是为了方便去表达这样的使用方式,而将这种专门用来被 new 关键词调用的函数称为了构造函数

  1. 上面讲解的构造函数只是让读者有一个思想上的认知,目的并不是让你觉得构造函数这种叫法就是错误的,也不必非得纠结于哪种叫法更优。所以在下面的内容里为了方便,我还是会将这种创建方式直接称为构造函数来讲解。

五. 构造函数的不足

  1. OK,现在我们已经了解了什么是构造函数

  2. 假设我们现在有了一个新的需求,现在每个人都会喊自己的名字,也就是每个人都有一个 say 的方法,你该怎么做?
    image.png

  3. 经过上面的学习,soeasy,很自然的就可以写出下面的代码。
    image.png
    控制台的效果:
    image.png

  4. 现在我再把张三叫过来,于是我 new 了一个 zs
    image.png

  5. 我们分析一下上面的代码,我们很可以轻松的想到,hzfzs 两个人确实年龄名字应该不同,所以它们的 name 属性和 age 属性不相等是很正常的,因为即使是两个空对象,它们是不相等的,更别说属性不同了。
    image.png

  6. 但是!这个 say 方法都是喊出各自的名字而已,它没有什么特别之处,也不像 nameage 通过参数来显示出自己独一无二。
    image.png

  7. 对应上面代码在内存中的关系,假设一个属性占一份内存,hzf 有三个属性,所以它占了三份。zs 也有三个属性,所以他也占了三份。可以吗?非常可以,我也觉得很行,他俩各自都不影响,挺好的。
    image.png
    但是你考虑过内存的感受吗?这还只是两个数据,数据量大的时候,内存空间是非常宝贵的,我们需要节省着点用。

  8. 可能上面有点抽象,让我们抛开代码,我们类比一下现实生活,假如现在你需要一把剪刀,但是目前你没有自己的剪刀。

  • 1.你首先想到的是找自己的爸爸要,你跑到了爸爸面前,爸爸看了看柜子,说自己也没有。
  • 2.于是你又去找爷爷要,爷爷一看,自己有一把剪刀,于是递给了你。
  1. 上面的场景中,我,爸爸,爷爷,三个人都有一把剪刀可以吗?怎么不可以?非常可以!但是有必要吗?没有必要。因为剪刀不是常用的东西,我们也不知道谁会再次使用,所以我们可以就可以先放到爷爷那里,爷爷自己用的时候,可以拿出来用;爸爸想用的时候,可以找爷爷要;我想用的时候,也可以去找爷爷要到就行了(这里其实需要再找爸爸要一次),我们三个人共用一个剪刀完全可以满足需求。

  2. 通过这种思路,我们可以设计出这样的模式,我们把一些通用的东西放到内存的一个地址上,谁用的时候找我要就行了。
    image.png
    一个很简单的行为,我们就从占用6份空间降低到了5份

  3. 你也许会吐槽,“不就节约了一份吗,有这么抠抠搜搜吗?” 这个想法没有任何问题,在我们数据量小的时候完全没有任何错误,但是假如这个函数我需要执行100次呢?按照之前的3个属性3份内存来讲。我就需要占用300份内存。但是如果我换成第二种方法,我就仅仅只需要 201份内存。数据量越大,节约的内存空间也是呈指数级增长的。

  4. 那 JS 里有充当上面 “爷爷” 角色的东西吗?你别说,还真有。它就是我们的 prototype 这个属性,它是专门用来管理一些我们需要重复使用,但是使用方法完全相同的一些属性。

六. 函数的 prototype 属性

  1. 我们依旧从 personInfo 函数讲起。
    image.png

  2. 我们先来打印一下 personInfo 这个函数的信息,看看它身上有哪些属性。
    image.png
    我们暂时先忽略上面的 callerarguments 等属性,本文重点不是它们,感兴趣的读者可以自行查阅。
    我们可以看到一个特殊的属性,prototype

  3. 注意!函数的 prototype 并不是 [[prototype]] 带两个括号的写法[[]]。这个区别非常重要!
    image.png

  4. [[]]的原因是浏览器所设定的,浏览器想让它用【⭐️prototype⭐️】五角星包裹,它也可以用五角星包裹。和 JS 的语法没有任何关系。这些带双括号的属性是编译引擎用到的,它能在控制台上打印出来的原因完全是为了方便我们调试和观察,你在 JS 层面是无法访问到的。(你可以暂时这样理解)

  5. 但是我们注意到 personInfoprototype 属性是货真价实存在的属性,你可以直接使用点语法看看效果。
    image.png

  6. 请注意 kong[[prototype]] 是无法访问的,你也访问不到。
    image.png

  7. 回到我们 personInfo,我们可以看到 prototype 也是一个普通对象,这个对象里原始只有 constructor 属性。
    image.png

  8. 并且你观察的仔细的话,这个 constructor 的属性值就是这个 personInfor 本身。我们验证一下:
    image.png

  9. 你也许会好奇有什么用?对于函数本身来讲,它就只是静静躺在那里,这个属性值任何一丁点作用。但是一旦你把它当作构造函数使用的时候,它的意义就不同了。

七. object 的 “_proto_” 属性

  1. 让我们重新调用 new 关键词来初始化一个 hzf
    image.png
    你会发现在控制台上除了我们上面所说的,无法获取的 【【prototype】】属性以外,也没别的属性了。
    image.png

  2. 其实不然,在对象身上还存在一个隐藏的属性 __proto__。该属性虽然已经被弃用,但是不影响我们今天深入理解原型,而且你在实际开发中也不会真正去调用这个属性 。
    image.png

  3. 说干就干,我们来打印一下 hzf.__prototype__ 属性,看看它到底是什么。
    image.png
    不对劲,怎么感觉这么熟悉呢?你没看错,它就是我们刚刚到函数 personInfo 函数身上的 prototype 属性。它们两个指向的是内存上同一块地址。我们赶紧验证一下:
    image.png

  4. 那这件事又是谁干的?哪有什么岁月静好,只不过是有人在帮你负重前行罢了。其实就是我们的 new关键词,脏活累活它都干了。在调用 new 关键词的时候,还有一个十分关键的步骤,就是将 hzf__proto__ 属性指向 personInfo.prototype 属性,结合上面的几个步骤,最终完成了我们 new 关键词的调用后发生的所有步骤。

八. 改造 personInfo

  1. 所有前置知识我们都了解了,接下来就是让我们完成我们最初的需求,将 personInfosay 方法放到 personInfoprototype 属性就ok了。

  2. 你要知道 prototype 就是一个普通对象,普通对象怎么赋值?直接点语法就完事啦~。就是这么简单
    image.png
    然后我们看一看调用的结果:
    image.png

  3. 我们再去看一眼,personInfoprototype 属性,果然对象多了一个 say 属性。
    image.png

  4. 再看一眼 hzf.__proto__ 属性。
    image.png

  5. 我们并没有为 hzf 单独设置 say 属性,然而它却可以调取 personInfo 函数 prototype 的属性。

这种 对象的__proto__ 属性和构造函数 prototype 属性,好像一根线互相连接在一起的表现形式,就构成了我们俗称原型链

  1. 并且当有一天 hzf 有了自己的剪刀say方法)时,它就不需要向他爸爸personInfo.prototype)询问了。
    image.png
    当我定义了 hzf 自己的 say 方法以后,控制台的效果:
    image.png

九. 回归 Array.prototype.forEach

  1. 现在让我们重新审视 MDN 上的这些方法,你是不是明白了作者为什么每一个都加上了 Array.prototype 呢?
    image.png

  2. 来看我们最初的代码,反应过来了吗?
    image.png

  3. 没反应过来,没关系,假如我这样写呢?我调用老朋友 new 去生成这个数组。
    image.png

  4. 回顾一下 new 关键词发生了什么,然后思考一下 arr 身上有 forEach 这个属性吗?没有的话去谁身上找呢?

总结:

写完本篇文章,其实自己也对原型链豁然开朗,自己之前的知识碎片今天都瞬间融合到了一起。希望大家可以真正理解 new 之后发生了什么,这是原型链的关键所在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1213.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

07平衡负载:gRPC是如何进行负载均衡的?

负载均衡(Load Balance),其含义就是指将请求负载进行平衡、分摊到多个负载单元上进行运行,从而协同完成工作任务。 负载均衡的主要作用: 提升并发性能:负载均衡通过算法尽可能均匀的分配集群中各节点的工作量,以此提高集群的整体的吞吐量。 提供可伸缩性:可添加或减少服…

Springboot新手开发 Cloud篇

前言: 👏作者简介:我是笑霸final,一名热爱技术的在校学生。 📝个人主页:个人主页1 || 笑霸final的主页2 📕系列专栏:后端专栏 📧如果文章知识点有错误的地方,…

汇编语言与微机原理(1)基础知识

前言(1)本人使用的是王爽老师的汇编语言第四版和学校发的微机原理教材配合学习。(2)推荐视频教程通俗易懂的汇编语言(王爽老师的书);贺老师C站账号网址;(3)文…

在visual studio 2022 C++中配置最新版OpenCV和可能错误解决方案

前面我们写了一篇博文有关在C#中配置OpenCV,但C#版本的OpenCV的学习资源相对较少,C版的和Python版的比较多。这里先说说C版的如何配置吧!总共完成四步即可使用起来。 文章目录一、下载并安装OpenCV1、下载OpenCV2、安装OpenCV二、配置环境1、…

【python】喜欢XJJ?这不得来一波大采集?

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 俗话说的好:技能学了~就要用在自己喜欢得东西上!! 这我不得听个话~我喜欢小姐姐,跳舞的小姐姐 这不得用python把小姐姐舞采集下来~嘿嘿嘿 完整源码、素材皆可点击文章下方名片…

条款20:当std::shared_ptr可能悬空时使用std::weak_ptr

自相矛盾的是,如果有一个像std::shared_ptr(见条款19)的但是不参与资源所有权共享的指针是很方便的。换句话说,是一个类似std::shared_ptr但不影响对象引用计数的指针。这种类型的智能指针必须要解决一个std::shared_ptr不存在的问…

Mysql 竟然还有这么多不为人知的查询优化技巧,还不看看?

前言 Mysql 我随手造200W条数据,给你们讲讲分页优化 MySql 索引失效、回表解析 今天再聊聊一些我想分享的查询优化相关点。 正文 准备模拟数据。 首先是一张 test_orde 表: CREATE TABLE test_order (id INT(11) NOT NULL AUTO_INCREMENT,p_sn VARCHA…

Spring事务和事务传播机制

目录 Spring中事务的实现 1、通过代码的方式手动实现事务 2、通过注解的方式实现声明式事务 2.1、Transactional作用范围 2.2、Transactional参数说明 2.3、注意事项 2.4、Transactional工作原理 事务隔离级别 1、事务特性 2、Spring中设置事务隔离级别 2.1、MySQL事…

Linux:函数指针做函数参数

#include <stdio.h> #include <stdlib.h> //创建带有函数指针做参数的函数框架api //调用者要先实现回调函数 //调用者再去调用函数框架 //所谓的回调是指 调用者去调用一个带有函数指针做参数的函数框架&#xff0c;函数框架反过来要调用调用者提供的回调函数 …

蓝桥杯冲击-02约数篇(必考)

文章目录 前言 一、约数是什么 二、三大模板 1、试除法求约数个数 2、求约数个数 3、求约数之和 三、真题演练 前言 约数和质数一样在蓝桥杯考试中是在数论中考察频率较高的一种&#xff0c;在省赛考察的时候往往就是模板题&#xff0c;难度大一点会结合其他知识点考察&#x…

全面剖析OpenAI发布的GPT-4比其他GPT模型强在哪里

最强的文本生成模型GPT-4一、什么是GPT-4二、GPT-4的能力三、和其他GPT模型比较3.1、增加了图像模态的输入3.2、可操纵性更强3.3、复杂任务处理能力大幅提升3.4、幻觉、安全等局限性的改善3.6、风险和缓解措施改善更多安全特性3.7、可预测的扩展四、与之前 GPT 系列模型比较五、…

QT入门Item Views之QListView

目录 一、QListView界面相关 1、布局介绍 二、代码展示 1、创建模型&#xff0c;导入模型 2、 设置隔行背景色 3、删除选中行 三、源码下载 此文为作者原创&#xff0c;创作不易&#xff0c;转载请标明出处&#xff01; 一、QListView界面相关 1、布局介绍 先看下界面…

高完整性系统工程(三): Logic Intro Formal Specification

目录 1. Propositions 命题 2.1 Propositional Connectives 命题连接词 2.2 Variables 变量 2.3 Sets 2.3.1 Set Operations 2.4 Predicates 2.5 Quantification 量化 2.6 Relations 2.6.1 What Is A Relation? 2.6.2 Relations as Sets 2.6.3 Binary Relations as…

ZYNQ硬件调试-------day2

ZYNQ硬件调试-------day2 1.ILA&#xff08;Integrated Logic Analyzer &#xff09; 监控逻辑内部信号和端口信号;可以理解为输出。可单独使用 2.VIO&#xff08;Virtual Input/Output &#xff09; 实时监控和驱动逻辑内部信号和端口信号&#xff0c;可以理解为触发输入。不可…

第十四届蓝桥杯三月真题刷题训练——第 14 天

目录 第 1 题&#xff1a;组队 题目描述 运行限制 代码&#xff1a; 第 2 题&#xff1a;不同子串 题目描述 运行限制 代码&#xff1a; 思路&#xff1a; 第 3 题&#xff1a;等差数列 题目描述 输入描述 输出描述 输入输出样例 运行限制 代码&#xff1a; 思…

Dubbo原理简介

Dubbo缺省协议采用单一长连接和NIO异步通讯&#xff0c;适合于小数据量大并发的服务调用&#xff0c;以及服务消费者机器数远大于服务提供者机器数的情况。 作为RPC&#xff1a;支持各种传输协议&#xff0c;如dubbo,hession,json,fastjson&#xff0c;底层采用mina,netty长连接…

nginx详解(概念、Linux安装、配置、应用)

1.nginx是什么 百度百科 看百度百科的解释&#xff0c;第一句话就是错的。“Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器”&#xff0c;从语法来看&#xff0c;去掉形容词就是&#xff1a;Nginx是服务器&#xff0c;nginx怎么会是服务器呢&#xff0c;nginx只是一…

Matlab进阶绘图第8期—聚类/分类散点图

聚类/分类散点图是一种特殊的特征渲染散点图。 聚类/分类散点图通过一定的聚类、分类方法&#xff0c;将特征相近的离散点划分到同一个类别中&#xff0c;进而将每个离散点赋予类别标签&#xff0c;并利用不同的颜色对不同的类别进行区分。 本文使用Matlab自带的gscatter函数…

C语言变量和数据类型的使用

文章目录前言一、将变量输出打印到控制台1.整形变量的输出2.浮点型变量的输出1.flaot的输出2.doble的输出3.float和double输出的区别4.%f,%10.2f......二、数据类型的大小总结前言 上一篇文章我们学习了C语言变量和数据类型的基本概念那么今天我们就具体的来看看如何在代码中使…

css实现文字大小自适应

在页面编写中经常会碰到页面自适应的问题&#xff0c;也就是页面内部的元素会随着窗口的放大缩小而放大缩小&#xff0c;box可以通过calc 百分比的形式做到页面自适应&#xff0c;但是box内的字体却无法做到这点&#xff0c;往往box自适应大小了&#xff0c;内部的字体还是原来…