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

要理解原型和原型链的关系,我们首先需要了解几个概念;
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. tf.get_variable

    使用tf.get_variable()时,如果检测到命名冲突,系统不会处理冲突,而会报错. 如果已经创建的变量对象,就把那个对象返回,如果没有创建变量对象的话,就创建一个新的. tf.get_vari ...

  2. SVG案例:动态去创建元素createElementNS

    案例一: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <titl ...

  3. window 10 安装paddlepaddle 1.7 GPU版本

    window 10 安装paddlepaddle 1.7 GPU版本 1)更新显卡驱动 2)安装cuda 10 https://developer.nvidia.com/cuda-10.0-downl ...

  4. JS 中的自定义事件和模拟事件

    在 JS 中模拟事件指的是模拟 JS 中定义的一些事件,例如点击事件,键盘事件等. 自定义事件指的是创建一个自定义的,JS 中之前没有的事件. 接下来分别说一下创建这两种事件的方法. 创建自定义事件 ...

  5. Robberies 杭电

    可怜的POIUYTREWQ最近想买下dota2的商品,但是手头缺钱.他想起了之前看过的一部大片,觉得抢银行也许是个不错的选择.他认为,坏人被抓是因为没有预先规划.于是他在之前的几个月对各大银行进行了一 ...

  6. Spring Cloud 系列之 Gateway 服务网关(三)

    本篇文章为系列文章,未读第一集的同学请猛戳这里: Spring Cloud 系列之 Gateway 服务网关(一) Spring Cloud 系列之 Gateway 服务网关(二) 本篇文章讲解 Ga ...

  7. 你自学半年也搞不懂的go底层,看这篇。这篇讲 go的数组、切片、Maps

    目录 数组 1.定义数组 2.使用数组 3.定义并赋值 4.数组的大小是类型的一部分 5.数组是值类型(当参数传递到函数中,修改不会改变原来的值) 6.数组长度 7.循环数组 8.多维数组 切片 1. ...

  8. Matlab学习-(1)

    1. 认识Matlab (1)MATLAB是美国MathWorks公司出品的商业数学软件,用于算法开发.数据可视化.数据分析以及数值计算的高级技术计算语言和交互式环境,主要包括MATLAB和Simul ...

  9. 好用的mitmproxy代理抓包

    安装证书 浏览器输入 `mitm.it` 下载证书有时候打不开,可能是起的服务卡死了,回车下命令行,再再网页刷新下载证书就可以了. mitmweb Chrome浏览器代理设置 打开的话,记得保存点一下 ...

  10. Spring Boot 中使用自定义注解,AOP 切面打印出入参日志及Dubbo链路追踪透传traceId

    一.使用背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用 ELK 来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿 ...