对于原型和原型链,相信有很多伙伴都说的上来一些,但有具体讲不清楚。但面试的时候又经常会碰到面试官的死亡的追问,我们慢慢来梳理这方面的知识!

要理解原型和原型链的关系,我们首先需要了解几个概念;
1、什么是构造函数?
2、构造函数与普通函数有什么区别?

3、原型链的顶端是什么?

4、prototype、__proto__、constructor在什么对象下存在?

OK  我们暂时带着这些疑问往下看;


一、什么是构造函数?构造函数与普通函数有什么区别?

构造函数其实就是一个普通函数,只是我们为了区分普通函数,通常建议构造函数name首字母大写;

// 这是一个构造函数
function Parent(){};

你说我就不首字母大写,那也不影响一个函数是构造函数的事实:

// 这也是一个构造函数
function parent(){
this.name = '不秃头';
};
let child = new parent();
console.log(child);//parent {name: "不秃头"}

有同学就纳闷了,这普通函数居然也能使用new操作符构造调用,没错,不仅普通函数能new调用,构造函数同样也能普通调用:

// 这是一个构造函数
function Parent() {
console.log(1);
};
Parent() //

其实到这里,我们已经解释了 构造函数与普通函数有什么区别 这个问题,构造函数其实就是一个普通函数,且函数都支持new调用与普通调用。也正因如此导致了ES5中构造函数没有区别于普通函数的尴尬局面,这也是为何在ES6中JavaScript正式推出Class类的原因,你会发现Class只支持new调用,如果直接调用会报错:

class Parent {
sayName() {
console.log('不秃头');
};
};
var child = new Parent();
child.sayName(); //不秃头
var child = Parent();//报错,必须使用new调用

解释了构造函数,那么构造函数能用来做什么呢?最基本的就是属性继承了,我们先不聊继承模式,就从最基本的继承说起。

假设现在我们要定制一批蓝色的杯子,杯口直径与高度可互不相同,那么我们可以用构造函数表示:

//定制杯子
function CupCustom(diameter, height) {
this.diameter = diameter;
this.height = height;
};
CupCustom.prototype.color = 'blue';
var cup1 = new CupCustom(8, 15);
var cup2 = new CupCustom(5, 10);
console.log(cup1.height);//
console.log(cup2.color);//blue

那么我们可以将构造函数CupCustom理解成一个制作杯子的模具,cup1与cup2是模具制作出来的杯子,我们称之为实例。大家可以尝试输出实例,可以看到两个实例都继承了构造函数的构造器属性(直径,高)与原型属性(颜色),颜色存放的地方还有点不同,它放在__proto__中,说到这咱们解释了为什么实例能读取height与color两个属性。

出于好奇,咱们也输出打印了构造函数的属性,有同学不知道怎么打印查看函数的属性,这里可以借用console.dir(函数),打印结果如下图:

对比图1与图2可以发现,构造函数除了自身属性与__proto__属性外还多出了一个prototype属性,这里我们其实能先给出一个结论:

所有的对象都有__proto__属性,但只有函数拥有prototype属性;


二、prototype与__proto__

"万物皆对象",这句话我想不止前端的同学,应该搞开发的同学都听过吧。

我们知道JavaScript中数据类型分类基本数据类型与引用数据类型:

  • 基本数据类型:Number,String,Boolean,Undefined,Null,Symbol。
  • 引用数据类型:Object,Function,Date,Array,RegExp等。

不知道大家有没有想过这样一件事,为什么随便声明一段字符串就能使用字符串的方法?如果字符串真的就是简单类型,方法又是从哪来的呢?

经实验,在这些类型中,基本类型中除了undefined与null之外,任意数字,字符,布尔以及symbol值都有__proto__属性,以字符串为例,我们打印它的__ptoto__并展开,如下可以看到大量我们日常使用的字符串方法均在其中:

所有的对象都有__ptoto__属性,而字符串居然也有__proto__属性,__proto__是一个访问器属性,它指向创建它的构造函数的原型prototype。还记得前面做杯子的构造函数吗?每实例个杯子其实只有直径与高度属性,但通过实例的__proto__属性我们找到了构造函数CupCustom的原型prototype,从而成功访问了prototype上的color属性。

prototype:是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。

__proto__:是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,__proto__是对象的内置属性),是JS内部使用寻找原型链的属性。

那为什么函数的prototype属性下还有一个__proto__属性呢?

我们知道函数有函数表达式,函数声明以及new创建三种模式,而函数声明其实等同于new Function(),我们定义的任意函数本质上也属于原始构造函数Function的实例,那么函数有一个__proto__属性指向构造函数Function的原型不是理所应当的事情么。所以这里我们又得出了一个结论:

每一个函数都属于原始构造函数Function的实例,而每一个函数又能做为构造函数生产属于自己的实例。


三、关于prototype

上面已经知道。prototype是函数特有的属性,__proto__是每个对象都有的属性;所以函数对象下面有两个属性,下图1,而不是函数对象就只有一个__proto__属性(实例化的对象)下图2;

每个对象都有__proto__属性,对象都能通过此属性找到创建自己构造函数的原型。那么什么是原型呢?原型其实就是一个对象。
上图3中,prototype下面有两个属性:__proto__和constructor,constructor它指向创建它的构造函数,

 实例的__proto__指向的是创建自己的构造函数的prototype,这个prototype是一个对象;实验是检验真的唯一标准;

a.__proto__ === Foo.prototype // true  说明:实例化的对象的__proto__ 恒等于构造函数的原型对象prototype;

让我们来用图形转化来表达;

通过这个图我们就把上面所说的都总结了;
实例对象的__proto__ 指向构造函数的原型prototype;
构造函数原型对象下面的constructor指向创建自己的构造函数;

我们补充一点知识:

数字 123 本质上由构造函数Number()创建,所以数字123通过__proto__访问构造函数Number()原型上的方法属性。

字符串 abc 本质上由构造函数 String()创建,所以abc也能通过__proto__访问构造函数String()原型上的方法属性。

函数本质上由原始构造函数Function创建,所以函数也能通过__proto__访问原始构造函数Function上的原型属性方法,别忘了,我们任意创建的函数都能使用call、apply等方法,不然你以为这些方法是哪来的呢。

上文也说了,我们自己创建构造函数其实和普通函数没任何区别,毕竟每个函数都能使用new调用用于创建属于自己的实例,这种继承方式是不是神似java的类,只是在JavaScript中改用原型prototype了。每一个函数都有作为构造函数的潜力,所以每一个函数都自带了prototype原型。

原始构造函数Function()扮演着创世主女娲的角色,她创造了Object()、Number()、String()、Date()、function fn(){}等第一批人类(也就是构造函数),而人类同样具备了繁衍的能力(使用new操作符),于是Number()繁衍出了数据类型数据,String()诞生了字符串,function fn(){}作为构造函数也诞生了各种各样的对象后代。

我们通过代码证实这一点:

// 所有函数对象的__proto__都指向Function.prototype,包括Function本身
Number.__proto__ === Function.prototype //true
Number.constructor === Function //true String.__proto__ === Function.prototype //true
String.constructor === Function //true Object.__proto__ === Function.prototype //true
Object.constructor === Function //true Array.__proto__ === Function.prototype //true
Array.constructor === Function //true Function.__proto__ === Function.prototype //true
Function.constructor === Function //true

所以当实例访问某个属性时,会先查找自己有没有,如果没有就通过__proto__访问自己构造函数的prototype有没有,前面说构造函数的原型是一个对象,如果原型对象也没有,就继续顺着构造函数prototype中的__proto__继续查找到构造函数Object()的原型,再看有没有,如果还没有,就返回undefined,因为再往上就是null了,这个过程就是我们熟知的原型链,说的再准确点,就是__proto__访问过程构成了原型链;

那对象可以一直__proto__往下找吗?答案是否定的。实例通过访问器属性__proto__访问创建自己的构造函数原型,相等是很正常的。原型下面的prototype.__proto__返回的是一个对象构造函数的原型Object.prototype,因为prototype是一个对象,对象的构造函数指向的是Object,Object.prototype.__proto__就是原型链的顶端null;上代码,根据下面代码就能理解原型和原型链的关系了;

function Parent() {};
var son = new Parent();
console.log(son.__proto__); //找到了构造函数Parent的原型
console.log(son.__proto__.__proto__); //原型是对象,它的__proto__指向构造函数Object的原型
console.log(son.__proto__.__proto__.__proto__); //null,到头了,null不是对象,没有原型,所以不会继续往上了 


总结: 这篇文章写起来说实话我的思路有点乱,但在最后面这张图如果你能理解的话,说明你已经对原型和原型链已经理解了,貌似好像知道了什么是原型和原型链,工作上用的地方好像不多,有一说一,确实~;但它并不影响 我们加深对函数的认识和理解,而且前端面试的时候,这百分之八九十都会问的原型和原型链,如果你理解了的话,相信你就能在面试的过程中迎刃有余;
欢迎大家一起讨论和指导;谢谢大家!

如果我的博客思路不够清晰的话,推荐大家看下这两篇博客:(ps:我也是看这两篇博客理解的)
https://www.cnblogs.com/echolun/p/12321869.html

https://www.cnblogs.com/echolun/p/12384935.html#4569574

前端【JS】,深入理解原型和原型链的更多相关文章

  1. js深入理解构造函数和原型对象

    1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例.但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propoty ...

  2. 第186天:js深入理解构造函数和原型对象

    1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例.但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propoty ...

  3. 前端总结·基础篇·JS(一)原型、原型链、构造函数和字符串(String)

    前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...

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

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

  5. js基础篇——原型与原型链的详细理解

    js中的对象分为两种:普通对象object和函数对象function. function fn1(){}; var fn2 = function(){}; var fn3 = new Function ...

  6. 对于js原型和原型链继承的简单理解(第一种,原型链继承)

    原型是js中的难点加重点,也是前端面试官最爱问的问题之一,因为面试官可以通过被面试者对原型的理解.来判断被面试者对js的熟悉程度. 原型的定义 Js所有的函数都有一个prototype属性,这个属性引 ...

  7. js原型和原型链理解到面向对象

    一.js中的两种对象,普通对象和函数对象 var obj1 = {}; var obj2 =new Object(); var obj3 = new obj1(); function fun1(){} ...

  8. 攻略前端面试官(三):JS的原型和原型链

    本文在个人主页同步更新~ 背就完事了 介绍:一些知识点相关的面试题和答案 使用姿势:看答案前先尝试回答,看完后把答案收起来检验成果~ 面试官:什么是构造函数 答:构造函数的本质是一个普通函数,他的特点 ...

  9. Js中关于构造函数,原型,原型链深入理解

    在 ES6之前,在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但 ...

随机推荐

  1. iOS 头条一面 面试题

    1.如何高效的切圆角? 切圆角共有以下三种方案: cornerRadius + masksToBounds:适用于单个视图或视图不在列表上且量级较小的情况,会导致离屏渲染. CAShapeLayer+ ...

  2. 002-IDE的使用与数据类型-C语言笔记

    002-IDE的使用与数据类型-C语言笔记 学习目标 1.[了解]IDE并熟悉Xcode基本使用技巧 2.[理解]C程序的入口和运行流程 3.[理解]变量的声明赋值和一些细节 4.[理解]变量的命名规 ...

  3. .net批量更新(插入、修改、删除)数据库

    思路: 1. 设置DataTable中每行的状态标识,即调用DataRow的方法setAdded().setModified().Delete() 2. 使用DataAdapter的Update(Da ...

  4. Css3 新增的属性以及使用

    Css3基础操作 . Css3? css3事css的最新版本 width. heith.background.border**都是属于css2.1CSS3会保留之前 CSS2.1的内容,只是添加了一些 ...

  5. Git应用详解第十讲:Git子库:submodule与subtree.md

    前言 前情提要:Git应用详解第九讲:Git cherry-pick与Git rebase 一个中大型项目往往会依赖几个模块,git提供了子库的概念.可以将这些子模块存放在不同的仓库中,通过submo ...

  6. 关于vue切换用户,路由表不更新问题

    简介 我想很多同学在项目中可能会遇到类似的问题,然后一顿操作,发现结果不尽人意.于是查阅各种资料,走进很多坑(可能你阅读的这篇随笔也是个坑).接下来我所描述的是关于我使用不同权限的用户切换登陆后,需要 ...

  7. python列表简介

    什么是列表?如何使用列表?https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range 列表相关知识: ...

  8. Linux-监控与安全运维之zabbix

    zabbix: Zabbix是一个开源分布式监控平台,包含诸多监控功能,用于构建一个符合企业级的监控解决方案.软件由开源社区提供开发和维护,遵循GPL协议,可以自由传播和使用,但开发团队提供收费的技术 ...

  9. 大部分人都不知道的8个python神操作

    01 print 打印带有颜色的信息 大家知道 Python 中的信息打印函数 Print,一般我们会使用它打印一些东西,作为一个简单调试. 但是你知道么,这个 Print 打印出来的字体颜色是可以设 ...

  10. <vector>常用操作

    如果不清楚vector是什么的话就去看我的另一篇随笔吧:https://www.cnblogs.com/buanxu/p/12791785.html 进入正题,vector和string一样,也是一种 ...