前端【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)实现的.但 ...
随机推荐
- Visual C++ 6.0踩坑记录---在Win10下安装Visual C++ 6.0安装成功后点击“打开”按钮闪退问题
前言: 为了更好的学习C及C++,前段时间下载了Microsoft Visual C++ 6.0(以下简称VC6),原因是VC6具有查看反汇编代码.监视内存.寄存器等功能,并且因为本人正在学习滴水逆向 ...
- Three.js如何选中外部模型
1.问题 three.js中模型选中使用的是射线法,根据摄像机角度,鼠标点击位置和模型选中的distance参数判断来选中模型.对于原生的矢量模型完全没有问题,但是当遇到导入的外部模型,如obj.st ...
- Salesforce 开发 | Salesforce与微信集成实操指南
配置前须知 Salesforce通过试点对特定客户提供Lightning WeChat Messaging,该试点需要同意特定的条款.除非Salesforce宣布WeChat Messaging全面可 ...
- python这门语言为什么要起这个名字
我只是一只可爱的小虫 前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:Liz喵 PS:如有需要Python学习资料的小 ...
- Daily Scrum 1/4/2015
Process: After New year's Day, we start our project in plan. Zhanyang: Add some useful UI in the IOS ...
- c++全排列
一.概念 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列.当m=n时所有的排列情况叫全排列.如果这组数有n个,那么全排列数为n!个. 比如a ...
- A - Number Sequence 哈希算法(例题)
Given two sequences of numbers : a[1], a[2], ...... , a[N], and b[1], b[2], ...... , b[M] (1 <= M ...
- 掌握MySQL连接查询到底什么是驱动表
准备我们需要的表结构和数据 两张表 studnet(学生)表和score(成绩)表, 创建表的SQL语句如下 CREATE TABLE `student` ( `id` int(11) NOT NUL ...
- 微信小程序标签常见知识点归纳整理
1. <image src='/images/logo.png' mode='widthFix'></image> mode 属性表示图片随着指定的宽度自动拉伸以显示原图的正确 ...
- UnicodeDecodeError: 'gbk' codec can't decode byte 0x8a in position 2: illegal multibyte sequence
pycharm报错UnicodeDecodeError: 'gbk' codec can't decode byte 0x8a in position 2: illegal multibyte seq ...