乐趣区

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

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

明天我就以一个老手过去者的角度去谈一谈这两个对于 JS 来说非常重要的概念的了解,我向来不喜爱刻板地背概念似的介绍某一个知识点,我会以最小的代码量,起码的专业名词试着让你本人去了解这个概念。

如果你对 原型 构造函数 的概念很含糊,或者说了解的不是那么的分明,又或者说能说上个一二三,然而让你残缺表达出来却又无从下嘴,那么本文将非常适合你浏览。

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


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

  1. 首先咱们不思考什么是 原型,咱们先来定义一个对象。

    能够很清晰的看到,这个名叫 hzf 的对象身上有三个属性,其中有一个叫 say 的属性,它的值是一个 函数。当初我想让你调取这个 say 函数,你怎么办呢?

  2. “简略,我反手就是一个 hzf.say()”。祝贺你,你当初间隔领悟 原型 又近了一步。是的,我没和你开玩笑。
  3. ok,难度减少。咱们当初定义了一个数组,内容很简略,只是简略的三个数组组成。
  4. ok,当初的需要是,别离在控制台输入数字中的每一个元素。
  5. “这还不简略?下面你不是都写了答案了吗?用 forEach 啊!“于是你简直是条件反射的想法写了上面的代码。

    你看着管制台上的输入开始狐疑这篇文章到底在讲什么。

  6. 你还记得刚刚咱们如何调用对象 hzf 身上的 say 办法吗?hzf.say()。对!你不感觉咱们调用 forEach 的形式和它十分相似吗?
  7. 然而有一个十分重要的前提是:say 这个属性对咱们来说是非常清晰的,什么意思呢?因为 say 是我本人亲手定义在 hzf 对象身上的办法,是我敲下键盘上的一个字母一个字母定义在下面的,所以我能够很天经地义的 hzf.sya() 这样来调用对吧?。
    那么问题来了,你什么时候给 arr 身上加过叫 forEach 的办法吗?那你为什么居然能够调用一个你素来没定义过的办法呢?
  8. 上面引出咱们题目要讲的 js 的外围概念之一 —-原型

二. 初识原型 prototype

  1. 首先咱们不要质疑为什么叫 prototype 这个单词,因为这个单词自身就是 原型 的意思。
  2. 回到下面的 arr,咱们先在控制台输入一下 arr
  3. 尽管说 JS 万物皆对象,然而你就算让我拼命想,我也真想不到 forEach 这个单词和这个 arr 到底有什么分割。
  4. 等等,我如同留神到了什么,为什么这上面还有个 prototype 属性?我得点开看看。

    咱们不仅发现了 forEach,并且如同还发现了很多很多咱们压根没在 arr 身上定义的办法。

  5. 好奇怪啊?我明明记得我没定义过 prototype 这个属性啊?等等,我如同想起来了,NDN 上介绍的写法是,Array.prototype。那这个 prototypearr 当初的这个 prototype 有没有什么分割呢?
  6. 咱们一步一步来。接着再来看一张我在 MDN 上截的一张图。
  7. 不晓得大家最开始和我有没有雷同的疑难🤔,这作者到底反复写这么多遍这个 “prototype” 干啥啊?我晓得你想通知我这是数组身上的一些办法,你间接写 Array.forEach 不就完事了吗?不禁让我想起了在写大学本科论文时拼命凑字数的场景。难道网站作者也是在疯狂凑字数?(不是
  8. MDN 它甚至在本人举例子的时候都没加 forEach 之前的这一串 Array.prototype
  9. 能想到这里,阐明你还是很棒的,通过下面的介绍,咱们晓得了存在 prototype 这样一个货色,然而要想理解 原型 的全副,咱们就必须先理解什么是 构造函数

三. 构造函数

  1. 咱们先来了解为什么会有所谓的 构造函数 ,在编程畛域,任何一段 代码 或者任何一个 名词 都有它存在的意义,要想深刻理解它,你就得晓得 为什么有它
  2. 咱们来模仿一个场景需要: 假如当初给你一个名单,下面有 1 集体的 name age 信息,当初让你把这个信息示意进去。
  3. “这还不简略?”于是你得意满满地写下了上面这段代码。
  4. ok,当初减少一点难度,当初有 3 集体的信息,你还是要把这 3 集体的信息示意进去。
  5. 你皱了皱眉头,还是一行一行代码敲着。

    你或者只是仅仅简略地 ctrl C+ V 了一下,而后改了名字就实现了这个需要。嗯,不错,很聪慧~

  6. 那咱们再加一点点难度,当初须要你创立 10 个用户的信息。来吧,展现~👋
  7. 难道你还要再去 ctrl C+ V 10 份,而后一个一个改名字?No,No,No. 程序员都是很懒的,咱们有没有更好的办法去实现这个需要呢?啊~要是有一个能够帮我写代码,呸,说错了。啊~要是有一个能够反复生成对象,并主动帮咱们填充信息,而后把生产的货色给咱们就行了的货色就好了。有这样的一个货色吗?还真有!没错,就是 函数
  8. 晓得该应用 函数 当前,咱们很轻易的就能够写出这样的代码。

    这个名叫 personInfo 的函数能够接管两个参数,一个 name,一个 age。而后咱们去上面传递参数拿返回值就行了。不仅代码行数缩小了,而且排版更加清新简洁。

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

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

  1. 在下面咱们理解到,咱们能够制作一个 函数 来专门生产对象返回,其实 JS 还存在另外一种更加简洁的办法。那就是应用 new 关键词。
  2. 这里咱们对 personInfo 不进行任何的改变。

    这是控制台的后果:

  3. 你可能会想,这不是截然不同?耶~我会用 new 了。其实这里你齐全误会了,new 关键词帮你主动做了很多操作你都没有用上。说白了,这里的 new 齐全被你节约了,new 帮咱们做了什么呢?咱们持续往下看。
  4. 咱们略微革新一下,替换成一个当想应用 new 关键词生成对象时,函数 正确的写法。

    你甚至连返回值都不须要写,这些脏活累活都交给了 new 来做,你只须要分心传递正确你的数据信息即可。

  5. 接下来咱们来测试一下看看是否可行。

    嗯~很好,代码又少写了两行,领导直夸我外行。

  6. 让咱们比照一下,这是之前的 personInfo 函数。

    这是起初的函数:

    从比照中你就人不知; 鬼不觉领悟了 new 背地干的事件。

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

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

  8. 接下来咱们去验证这一点。你甚至能够本人测试一下,当你写一个空函数,我函数体内什么也不干。我就纯 new 一个货色进去,我要看看这个 kong 到底是什么?
  9. 如果依照之前的 构造函数 的说法,那这个 test 也叫 构造函数 了。你能说它没结构什么货色吗?这不是上面放着了吗?一个空对象 kong。这外面用为什么用双括号包裹【【prototype】】我会在上面解说到,请仔细阅读。

    等等,这个不是 new 帮咱们做的事件吗?关你这个 test 函数什么事啊?

  10. 还有你留神到了吗?我所有的函数都并没有以 大写结尾 ,我是成心这样写的,为了就是让咱们分明一点,所谓 构造函数 大写结尾是人们约定俗成的规定,并不是 JS 层面的语法要求。
  11. 是的,没错,刚刚咱们其实验证了两个事件。一个是 new 关键词的确帮咱们生成了一个空对象。第二个是如果存在 构造函数 ,那么所有函数都能够叫做 构造函数。所以咱们得出结论:

    js 里没有所谓真正意义上的构造函数这样一种非凡函数,而是只存在 函数的结构式调用,也就是应用 new 关键词去调用一个函数。 咱们只是为了不便去表白这样的应用形式,而将这种专门用来被 new 关键词调用的函数称为了 构造函数

  12. 下面解说的 构造函数 只是让读者有一个思维上的认知,目标并不是让你感觉 构造函数 这种叫法就是谬误的,也不用非得纠结于哪种叫法更优。所以在上面的内容里为了不便,我还是会将这种创立形式间接称为 构造函数 来解说。

五. 构造函数的有余

  1. OK,当初咱们曾经理解了什么是 构造函数
  2. 假如咱们当初有了一个新的需要,当初每个人都会喊本人的名字,也就是每个人都有一个 say 的办法,你该怎么做?
  3. 通过下面的学习,soeasy,很天然的就能够写出上面的代码。

    控制台的成果:

  4. 当初我再把 张三 叫过去,于是我 new 了一个 zs
  5. 咱们剖析一下下面的代码,咱们很能够轻松的想到,hzfzs 两个人的确 年龄 名字 应该不同,所以它们的 name 属性和 age 属性不相等是很失常的,因为即便是两个空对象,它们是不相等的,更别说属性不同了。
  6. 然而!这个 say 办法都是喊出各自的名字而已,它没有什么特别之处,也不像 nameage 通过参数来显示出本人举世无双。
  7. 对应下面代码在内存中的关系,假如一个属性占一份内存,hzf 有三个属性,所以它占了三份。zs 也有三个属性,所以他也占了三份。能够吗?十分能够,我也感觉很行,他俩各自都不影响,挺好的。

    然而你思考过内存的感触吗?这还只是两个数据,数据量大的时候,内存空间是十分贵重的,咱们须要节俭着点用。

  8. 可能下面有点形象,让咱们抛开代码,咱们类比一下现实生活,如果当初你须要一把剪刀,然而目前你没有本人的剪刀。

    • 1. 你首先想到的是找本人的 爸爸 要,你跑到了爸爸背后,爸爸看了看柜子,说本人也没有。
    • 2. 于是你又去找 爷爷 要,爷爷一看,本人有一把剪刀,于是递给了你。
  9. 下面的场景中,我,爸爸,爷爷,三个人都有一把剪刀能够吗?怎么不能够?十分能够!然而有必要吗?没有必要。因为剪刀不是罕用的货色,咱们也不晓得谁会再次应用,所以咱们能够就能够先放到爷爷那里,爷爷本人用的时候,能够拿进去用;爸爸想用的时候,能够找爷爷要;我想用的时候,也能够去找爷爷要到就行了(这里其实须要再找爸爸要一次),咱们三个人共用一个剪刀齐全能够满足需要。
  10. 通过这种思路,咱们能够设计出这样的模式,咱们把一些通用的货色放到内存的一个地址上,谁用的时候找我要就行了。

    一个很简略的行为,咱们就从占用 6 份 空间升高到了 5 份

  11. 你兴许会吐槽,“不就节约了一份吗,有这么抠抠搜搜吗?”这个想法没有任何问题,在咱们数据量小的时候齐全没有任何谬误,然而如果这个函数我须要执行 100 次呢?依照之前的 3 个属性 3 份内存 来讲。我就须要占用300 份内存。然而如果我换成第二种办法,我就仅仅只须要 201 份内存。数据量越大,节约的内存空间也是呈指数级增长的。
  12. 那 JS 里有充当下面 “爷爷” 角色的货色吗?你别说,还真有。它就是咱们的 prototype 这个属性,它是专门用来治理一些咱们须要重复使用,然而应用办法完全相同的一些属性。

六. 函数的 prototype 属性

  1. 咱们仍旧从 personInfo 函数讲起。
  2. 咱们先来打印一下 personInfo 这个函数的信息,看看它身上有哪些属性。

    咱们临时先疏忽下面的 callerarguments 等属性,本文重点不是它们,感兴趣的读者能够自行查阅。
    咱们能够看到一个非凡的属性,prototype

  3. 留神!函数的 prototype 并不是 [[prototype]] 带两个括号的写法[[]]。这个区别十分重要!
  4. [[]] 的起因是浏览器所设定的,浏览器想让它用【⭐️prototype⭐️】五角星包裹,它也能够用五角星包裹。和 JS 的语法没有任何关系。这些带双括号的属性是 编译引擎 用到的,它能在管制台上打印进去的起因齐全是为了不便咱们调试和察看,你在 JS 层面是无法访问到的。(你能够临时这样了解)
  5. 然而咱们留神到 personInfoprototype 属性是货真价实存在的属性,你能够间接应用点语法看看成果。
  6. 请留神 kong[[prototype]] 是无法访问的,你也拜访不到。
  7. 回到咱们 personInfo,咱们能够看到 prototype 也是一个一般对象,这个对象里原始只有 constructor 属性。
  8. 并且你察看的认真的话,这个 constructor 的属性值就是这个 personInfor 自身。咱们验证一下:
  9. 你兴许会好奇有什么用?对于函数自身来讲,它就只是静静躺在那里,这个属性值任何一丁点作用。然而一旦你把它当作 构造函数 应用的时候,它的意义就不同了。

七. object 的 “\_proto_\” 属性

  1. 让咱们从新调用 new 关键词来初始化一个 hzf

    你会发现在管制台上除了咱们下面所说的,无奈获取的【【prototype】】属性以外,也没别的属性了。

  2. 其实不然,在对象身上还存在一个暗藏的属性 __proto__。该属性尽管曾经被弃用,然而不影响咱们明天深刻了解原型,而且你在理论开发中也不会真正去调用这个属性。
  3. 说干就干,咱们来打印一下 hzf.__prototype__ 属性,看看它到底是什么。

    不对劲,怎么感觉这么相熟呢?你没看错,它就是咱们刚刚到函数 personInfo 函数身上的 prototype 属性。它们两个指向的是内存上同一块地址。咱们连忙验证一下:

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

八. 革新 personInfo

  1. 所有前置常识咱们都理解了,接下来就是让咱们实现咱们最后的需要,将 personInfosay 办法放到 personInfoprototype 属性就 ok 了。
  2. 你要晓得 prototype 就是一个一般对象,一般对象怎么赋值?间接点语法就完事啦~。就是这么简略

    而后咱们看一看调用的后果:

  3. 咱们再去看一眼,personInfoprototype 属性,果然对象多了一个 say 属性。
  4. 再看一眼 hzf.__proto__ 属性。
  5. 咱们并没有为 hzf 独自设置 say 属性,然而它却能够调取 personInfo 函数 prototype 的属性。

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

  6. 并且当有一天 hzf 有了本人的 剪刀 say 办法)时,它就不须要向他 爸爸personInfo.prototype)询问了。

    当我定义了 hzf 本人的 say 办法当前,控制台的成果:

九. 回归 Array.prototype.forEach

  1. 当初让咱们从新扫视 MDN 上的这些办法,你是不是明确了作者为什么每一个都加上了 Array.prototype 呢?
  2. 来看咱们最后的代码,反馈过去了吗?
  3. 没反馈过去,没关系,如果我这样写呢?我调用老朋友 new 去生成这个数组。
  4. 回顾一下 new 关键词产生了什么,而后思考一下 arr 身上有 forEach 这个属性吗?没有的话去谁身上找呢?

总结:

写完本篇文章,其实本人也对原型链恍然大悟,本人之前的常识碎片明天都霎时交融到了一起。心愿大家能够真正了解 new 之后产生了什么,这是原型链的关键所在。

退出移动版