简介

下面这张图大家应该很很熟悉了,各位大佬讲原型及原型链的时候是大部分都会用到下面这张图片的

我想以自己的方式来讲述一遍,一是帮助我自己更好的复习,二是希望能够帮助到想要复习或者学习原型的同学

  • 在讲解之前,我先讲点基础概念
  • JS对象中他有一个内置原型[[prototype]],这个原型就是隐式原型__proto__,而构造函数因为其本身就是 一个对象,所以它有一个隐式原型,但是构造函数又是一个函数,所以他有一个特殊的属性显式原型prototype,大家也可以看到constructor这个属性,默认情况下,函数对象上都会有一个constructor属性,里面存放着是函数的原型对象
  • 好,接下来我会分步讲解这张图的内容,同时会辅以图片来讲解

构造函数与实例化的关系

  • 我们先看这个地方function Foo() 他是一个构造函数
  • 那么它的显式原型一定是指向Foo的原型对象上
  • 同时我们看f1这个实例化Foo的对象,因为是对象,所以它只有一个__proto__隐式原型的
  • 我们可以看到图片中,隐式原型是指向了Foo的原型对象上的,这是为什么呢?
  • 其实这里我要讲下 new这个操作 到底干了些什么,这应该是一道经典的面试题吧
    • 1️⃣ 首先会创建一个空对象 {}
    • 2️⃣ 将this指向该空象,然后将构造函数的显式原型赋予给这个对象的隐式原型上去 __proto__=prototype
    • 3️⃣ 开始执行构造函数内的代码,将构造函数内部属性和方法赋值给该对象
  • 所以大家看,f1的隐式原型是指向了Foo的显式原型的吧,是不是很容易理解呢

  • 接下来我们接着往下看,Foo的原型对象也就是一个普通的对象,所以他也就一个隐式原型,他的隐式原型就是指向Object的原型对象的,小伙伴看到这里会不会很懵? 觉得为啥就是指向Object的原型对象呢?
  • 我们重点看向o1这个普通对象,大家可能对new Object会有点陌生,因为大家创建对象的时候,往往都是通过创建一个字面量来申明一个对象的const obj={},实际上这个操作就是等于new Object().所以看到了new Object()大家有没有明白什么呢?
  • 哦 原来 Object是一个构造函数,那么他的显式原型就是指向Object.Prototype了,即原型对象,那么在实例化Object的时候,就会做我上面讲的new操作符操作了,所以那个Foo.prototype的隐式原型指向那个Object.prototype是不是理解了呢?

  • 接下来,我们要把重点放在构造函数Foo的隐式原型上,函数的隐式原型是谁给的呢? 上文讲到,隐式原型是在实例化的时候,构造函数所赋予的,那么谁会是Foo的构造函数呢?
  • 看图可知,它便是Function,实例化了Function所得到的Foo,所以Foo的隐式原型是指向了Function的显式原型Function.prototype

站在高层的两个构造函数

它便是 Function和Object构造函数,他们两个有点特殊,所以单独拿出来讲讲

  • 大家看上面的图,首先关注Object这个构造函数
  • Object的显式原型是指向了Object.prototype,这一点大家肯定都没有问题
  • 我们看,Object.prototype的隐式原型是指向了null,这说明了一个什么问题,隐式原型是在实例化的时候才会被赋予的,但是他是为空,所以我们可以得到一个结论,就是在JS中,顶层原型是Object.prototype,Object.prototype是站在最顶峰的辣个男人
  • 好,思绪收回来,我们看下Object的隐式原型是指向哪的,会发现它的隐式原型是指向函数的显式原型的,说明Object这个构造函数是通过Function这个构造函数所实例化得到的
  • 我们接着看下Function这个构造函数的显式原型和隐式原型
    • 首先 它的显式原型是Function.prototype,这点没问题
    • 然后 它的隐式原型也是 Function.prototype 嗯? 嗯? 好怪,不行,我再看一眼
    • 然后你会发觉不对劲,很不对劲 这不就是说明了Function是通过实例化自己得到的吗?
    • 有点类似于 是先有鸡还是先有蛋这一说法.不知道他们哪个是先出来的,应该是JS内部自己做了特殊处理,这一部分需要小伙伴记好了
  • 然后我们在看下Function.prototype的隐式原型指向了Object.prototype说明了函数的原型对象是通过实例化Object所得到的
  • 讲到这 这副图的内容我已经讲的差不多了,最后我自己画了一幅图来帮助大家更好的理解

习题练习

我出几道练习题,大家自己练习一下,看看自己掌握的怎么样

  1. /** 1.普通对象
  2. * 2.构造函数
  3. * 3.Function
  4. * 4.Object
  5. **/
  6. function Foo(name) {
  7. this.name = name;
  8. }
  9. const obj = {};
  10. const obj2 = {
  11. name: "给他一个新的原型",
  12. }
  13. const foo = new Foo("czx");
  14. console.log(foo.__proto__ === Foo.prototype);
  15. console.log(obj.__proto__ === Object.prototype)
  16. console.log(Foo.prototype === Foo.__proto__);
  17. console.log(Foo.__proto__ === Function.prototype);
  18. console.log(Function.prototype === Function.__proto__);
  19. console.log(Function.prototype.__proto__ === Object.prototype);
  20. console.log(Object.__proto__ === Function.prototype);
  21. console.log(Object.prototype.__proto__);
  22. Foo.prototype = obj2;
  23. console.log(Foo.prototype.__proto__ === Object.prototype)
  24. console.log(foo.__proto__ === Foo.prototype);

答案

大家一定要自己写完后再来看答案啊,这样印象才深刻

  1. console.log(foo.__proto__ === Foo.prototype);//true
  2. console.log(obj.__proto__ === Object.prototype)//true
  3. console.log(Foo.prototype === Foo.__proto__);//false
  4. console.log(Foo.__proto__ === Function.prototype);//true
  5. console.log(Function.prototype === Function.__proto__); //true
  6. console.log(Function.prototype.__proto__ === Object.prototype);//true
  7. console.log(Object.__proto__ === Function.prototype);//true;
  8. console.log(Object.prototype.__proto__);//null
  9. Foo.prototype = obj2;
  10. console.log(Foo.prototype.__proto__ === Object.prototype)//true;
  11. console.log(foo.__proto__ === Foo.prototype);//false

原型链

只要理解了上面我讲的原型,原型链自然而然就会了

  • 大家可以将原型链理解为,如果我在这一层没找到的东西,我可以去对应的上一层找,直到顶层为止,我来给大家出几道题,就能很快知道了解了

题目一

  1. function Foo(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. const bar = {
  6. say: () => {
  7. console.log("执行说话");
  8. },
  9. age2: 32,
  10. }
  11. Foo.prototype = bar;
  12. const foo = new Foo("Spirit", 18)
  13. foo.say();
  14. console.log(foo.age2)
  • 大家可以自己想下这段代码执行出来会是什么结果
  • 可以知道,我将Foo的显式原型进行了替换,那么我foo去调用原型对象上的方法是可以调用到的,而我构造函数Foo上是没有这两个属性的,但因为他的显式原型对象即bar,它上面是有的
  • 所以foo是可以调用到的

题目二

  1. var A = function () { };
  2. A.prototype.n = 1;
  3. var b = new A();
  4. A.prototype = {
  5. n: 2,
  6. m: 3
  7. }
  8. var c = new A();
  9. console.log(b.n);
  10. console.log(b.m);
  11. console.log(c.n);
  12. console.log(c.m);

‍️ 大家可以想下这道题的输出结果是什么,这道题也是很考察大家对原型链的理解的,接下来我就会开始讲解了

  1. 首先 A 是一个构造函数,它有显式原型和隐式原型
  2. A在显式原型上添加了一个属性n,其值为1
  3. 通过实例化A得到了b,有隐式原型,这时候就考察大家对new操作熟不熟悉,这时候有b.__proto__===A.prototype
  4. 这时候A的显式原型被整个替换了,所以之前的实例化的b就取不到这个更换原型之后的值了
  5. 实例化A得到了c,这时候有c.__proto__===A.prototype 只不过这个显式原型不再是之前的了
  6. 开始输出
  7. b.n是输出1的,因为是加在了最开始A的显式原型上面,所以是能取到值的
  8. b.m是取不到值的,因为这个m是后面更换了A的显式原型所加上的值,此时的b是取不到的
  9. c.n输出为2,因为更换后的显式原型上面是有n 是2
  10. c.m输出为3,同理
  11. 大家听懂了吗,这道题主要就是考你new操作做了什么,让你来判断到底实例化之后是赋予了哪个显式原型,下面的代码是带注解版的
  1. var A = function () { };
  2. A.prototype.n = 1; //显示原型上加了一个 n属性 值为1
  3. var b = new A(); //实例化了的b __proto__===A.prototype
  4. A.prototype = {
  5. n: 2,
  6. m: 3
  7. } //A的显式原型被替换掉了
  8. var c = new A(); //实例化的c 的__proto__===A更换后的显式原型
  9. console.log(b.n); //1
  10. console.log(b.m); //undefined
  11. console.log(c.n);//2
  12. console.log(c.m);//3

题目三

  1. var F = function () { };
  2. Object.prototype.a = function () {
  3. console.log('a');
  4. };
  5. Function.prototype.b = function () {
  6. console.log('b');
  7. }
  8. var f = new F();
  9. f.a();
  10. f.b();
  11. F.a();
  12. F.b();

这道题是我认为比较难的一道题,大家可以好好想下这道题的输出到底是什么,接下来我会开始讲解这道题

  1. F是一个构造函数,它拥有显式原型和隐式原型

    1. 此时F.prototype.__proto__===Object.prototype
    2. F.__proto__===Function.prototype
    3. 这个时候,他们是有这种关系的
  2. Object.prototype上加上了一个函数a
  3. Function.prototype加上了一个函数b
  4. 实例化F得到了f,这个时候他有隐式原型f.__proto__===F.prototype
  5. 接下来我们看输出
    1. f.a()首先它的隐式原型是等于构造函数的显式原型的,而F.prototype.__proto__是等于Object.prototype,也就是说f是可以沿着这条原型链一路往上找到,最后是可以找到这个a函数的,所以可以输出a
    2. f.b()我们可以想下,沿着原型链我们可以找到这个函数b嘛,并不可以,对不对,所以这里会报错,说不存在这么一个函数
    3. F.a()我们来看下这个构造函数是有显式原型和隐式原型的,所以我们看回刚开始的那个解释,它是能找到Object.prototypeFunction.prototype的,所以能够输出a
    4. F.b()也是同理,所以输出b
  6. 这道题大家看懂了嘛,主要是考察了函数和普通对象的区别,也是很好的考察了原型链的关系的,下面是带注解的代码
  1. var F = function () { }; //构造函数
  2. //他有隐式原型和显式原型 此时他的隐式原型__proto__===Function.prototype.__proto__===Object.prototype
  3. Object.prototype.a = function () { //Object.prototype是顶层原型
  4. console.log('a');
  5. };
  6. Function.prototype.b = function () {
  7. console.log('b');
  8. }
  9. var f = new F(); //实例化了的f 此时是一个对象 只有隐式原型 __proto__ 所以此时有 __proto__===F.prototype
  10. //‍️ F.prototype是一个对象,是Object所实例化得到的 所以F.prototype.__proto__===Object.prototype
  11. f.a(); //a
  12. f.b(); //报错 没有这个函数
  13. F.a(); //a
  14. F.b();//b

[JS基础] 带你深入了解JS原型的更多相关文章

  1. js基础到精通全面教程--JS教程

    适合阅读范围:对JavaScript一无所知-离精通只差一步之遥的人 基础知识:HTML JavaScript就这么回事1:基础知识 1 创建脚本块 1: <script language=”J ...

  2. js基础知识温习:构造函数与原型

    构造函数 构造函数主要用于初始化新对象.按照惯例,构造函数名第一个字母都要大写. 构造函数有别于其它函数在于它使用new操作符来调用生成一个实例对象.换句话说,如果一个函数使用new操作符来调用,则将 ...

  3. Js基础知识(四) - js运行原理与机制

    js运行机制 本章了解一下js的运行原理,了解了js的运行原理才能写出更优美的代码,提高运行效率,还能解决开发中遇到的不理解的问题. 进程与线程 进程是cpu资源分配的最小单位,进程可以包含多个线程. ...

  4. [妙味JS基础]第四课:JS数据类型、类型转换

    知识点总结 JS数据类型:number数字(NaN).string字符串.boolean布尔值.函数类型.object对象(obj.[].{}.null).undefined未定义 typeof 用来 ...

  5. NodeJs>------->>第三章:Node.js基础知识

    第三章:Node.js基础知识 一:Node.js中的控制台 1:console.log.console.info  方法 console.log(" node app1.js 1> ...

  6. 9.29学习的js基础

    js基础 1.三种js引入方式    a).<input type="button" value="点击事件" onClick="documen ...

  7. JS基础-该如何理解原型、原型链?

    JS的原型.原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对 ...

  8. AJAX学习前奏----JS基础加强

     AJAX学习前奏----JS基础加强 知识概要: 1.js类&属性&方法的定义 2.静态属性与方法 3.构造方法 4.原型的使用 5.Object对象直接加属性和方法 6.JSO ...

  9. 前端面试题目汇总摘录(JS 基础篇)

    JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string typeof null; // o ...

随机推荐

  1. 无意进去UIView随笔闹腾着玩 -by 胡 xu

    1 @interface UIView : UIResponder<NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem> ...

  2. MHA高可用配置及故障切换

    MHA高可用配置及故障切换 目录 MHA高可用配置及故障切换 一.案例概述 二.案例前置知识点 1. MHA概述 2. MHA的组成 (1)MHA Manager(管理节点) (2)MHA Node( ...

  3. 基于GDAL库,读取.nc文件(以海洋表温数据为例)C++版

    对于做海洋数据处理的同学,会经常遇到nc格式的文件,nc文件的格式全称是NetCDF,具体的详细解释请查询官网[https://www.unidata.ucar.edu/software/netcdf ...

  4. 《PHP程序员面试笔试宝典》——在被企业拒绝后是否可以再申请?

    如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 很多企业为了能够在一年一度的招聘季节中,提前将优秀的程序员锁定到自己的麾下,往往会先下手为强.他们通常采取的措施有两种: ...

  5. Solution -「CCO 2019」「洛谷 P5532」Sirtet

    \(\mathcal{Description}\)   Link.   在一个 \(n\times m\) 的网格图中,每个格子上是空白 . 或沙子 #,四联通的沙子会连成一个整体.令此时所有沙子块同 ...

  6. OpenHarmony移植案例与原理:startup子系统之syspara_lite系统属性部件

    摘要:本文介绍下移植开发板时如何适配系统属性部件syspara_lite,并介绍下相关的运行机制原理. 本文分享自华为云社区<openharmony移植案例与原理 - startup子系统之sy ...

  7. mysql data local的使用导入与导出数据到.txt

    一.先创建表 CREATE TABLE stu(id INT UNSIGNED AUTO_INCREMENT,NAME VARCHAR(15) UNIQUE, /* 唯一约束 , 可以不填写,如果填写 ...

  8. HashMap(1.8)源码学习

    一.HashMap介绍 1.哈希表(hash table) 在哈希表中进行添加,删除,查找等操作,时间复杂度为O(1) 存储位置 = f(关键字) 其中,这个函数f一般称为哈希函数,这个函数的设计好坏 ...

  9. [LeetCode]4.寻找两个正序数组的中位数(Java)

    原题地址: median-of-two-sorted-arrays 题目描述: 示例 1: 输入:nums1 = [1,3], nums2 = [2] 输出:2.00000 解释:合并数组 = [1, ...

  10. k8s集群节点ping不通其他主机的ip

    文章目录 排查过程 本地宿主机网络检查 pod网络检查 tcpdump检查网络 检查flannel网卡 检查宿主机网卡 iptables检查 解决方法 测试环境服务出现问题,服务一直报错认证超时,检查 ...