前端【JS】,深入理解原型和原型链
对于原型和原型链,相信有很多伙伴都说的上来一些,但有具体讲不清楚。但面试的时候又经常会碰到面试官的死亡的追问,我们慢慢来梳理这方面的知识!
要理解原型和原型链的关系,我们首先需要了解几个概念;
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】,深入理解原型和原型链的更多相关文章
- js深入理解构造函数和原型对象
1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例.但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propoty ...
- 第186天:js深入理解构造函数和原型对象
1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例.但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propoty ...
- 前端总结·基础篇·JS(一)原型、原型链、构造函数和字符串(String)
前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...
- JS基础-该如何理解原型、原型链?
JS的原型.原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对 ...
- js基础篇——原型与原型链的详细理解
js中的对象分为两种:普通对象object和函数对象function. function fn1(){}; var fn2 = function(){}; var fn3 = new Function ...
- 对于js原型和原型链继承的简单理解(第一种,原型链继承)
原型是js中的难点加重点,也是前端面试官最爱问的问题之一,因为面试官可以通过被面试者对原型的理解.来判断被面试者对js的熟悉程度. 原型的定义 Js所有的函数都有一个prototype属性,这个属性引 ...
- js原型和原型链理解到面向对象
一.js中的两种对象,普通对象和函数对象 var obj1 = {}; var obj2 =new Object(); var obj3 = new obj1(); function fun1(){} ...
- 攻略前端面试官(三):JS的原型和原型链
本文在个人主页同步更新~ 背就完事了 介绍:一些知识点相关的面试题和答案 使用姿势:看答案前先尝试回答,看完后把答案收起来检验成果~ 面试官:什么是构造函数 答:构造函数的本质是一个普通函数,他的特点 ...
- Js中关于构造函数,原型,原型链深入理解
在 ES6之前,在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但 ...
随机推荐
- c++存储区
全局变量与静态变量区.常量区.局部变量区(栈).动态存储区(堆).自由存储区 1.全局变量与静态变量区->存放全局变量.静态变量,程序运行结束后释放 2.常量区->存放常量 3.局部变量区 ...
- 011-指针(上)-C语言笔记
011-指针(上)-C语言笔记 学习目标 1.[掌握]字符串常用函数 2.[掌握]指针变量的声明 3.[掌握]指针变量的初始化 4.[掌握]函数与指针 5.[掌握]指针的数据类型 6.[掌握]多级指针 ...
- 前端学习笔记 --ES6新特性
前言 这篇博客是我在b站进行学习es6课程时的笔记总结与补充. 此处贴出up主的教程视频地址:深入解读ES6系列(全18讲) 1.ES6学习之路 1.1 ES6新特性 1. 变量 2. 函数 3. 数 ...
- L16 LeNet
**本小节用到的数据下载 1.涉及语句 import d2lzh1981 as d2l 数据1 : d2lzh1981 链接:https://pan.baidu.com/s/1LyaZ84Q4M75G ...
- L6循环神经网络
循环神经网络 本节介绍循环神经网络,下图展示了如何基于循环神经网络实现语言模型.我们的目的是基于当前的输入与过去的输入序列,预测序列的下一个字符.循环神经网络引入一个隐藏变量HHH,用HtH_{t}H ...
- I - Fill The Bag codeforces 1303D
题解:注意这里的数组a中的元素,全部都是2的整数幂.然后有二进制可以拼成任意数.只要一堆2的整数幂的和大于x,x也是2的整数幂,那么那一堆2的整数幂一定可以组成x. 思路:位运算,对每一位,如果该位置 ...
- vue element多文件多格式上传文件,后台springmvc完整代码
template: <el-upload class="upload-demo" ref=&quo ...
- 详解 ServerSocket与Socket类
(请观看本人博文 -- <详解 网络编程>) 目录 ServerSocket与Socket ServerSocket 类: Socket类: ServerSocket与Socket 首先, ...
- Java集合案例(产生不重复随机数)
获取10个1-20之间的随机数,要求不能重复 用数组实现,但是数组的长度是固定的,长度不好确定.所以我们使用集合实现. 分析:A:创建产生随机数的对象B:创建一个存储随机数的集合C:定义一个统计变量. ...
- coding++:@DisallowConcurrentExecution 注解的作用
Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞. 在Spring中这时需要设置concurrent ...