[JS基础] 带你深入了解JS原型
简介
下面这张图大家应该很很熟悉了,各位大佬讲原型及原型链的时候是大部分都会用到下面这张图片的
我想以自己的方式来讲述一遍,一是帮助我自己更好的复习,二是希望能够帮助到想要复习或者学习原型的同学
- 在讲解之前,我先讲点基础概念
- JS对象中他有一个内置原型
[[prototype]]
,这个原型就是隐式原型__proto__,而构造函数因为其本身就是 一个对象,所以它有一个隐式原型,但是构造函数又是一个函数,所以他有一个特殊的属性显式原型prototype
,大家也可以看到constructor
这个属性,默认情况下,函数对象上都会有一个constructor
属性,里面存放着是函数的原型对象 - 好,接下来我会分步讲解这张图的内容,同时会辅以图片来讲解
构造函数与实例化的关系
- 我们先看这个地方
function Foo()
他是一个构造函数 - 那么它的显式原型一定是指向
Foo
的原型对象上 - 同时我们看
f1
这个实例化Foo的对象,因为是对象,所以它只有一个__proto__
隐式原型的 - 我们可以看到图片中,隐式原型是指向了
Foo
的原型对象上的,这是为什么呢? - 其实这里我要讲下
new
这个操作 到底干了些什么,这应该是一道经典的面试题吧- 1️⃣ 首先会创建一个空对象
{}
- 2️⃣ 将
this
指向该空象,然后将构造函数的显式原型赋予给这个对象的隐式原型上去__proto__=prototype
- 3️⃣ 开始执行构造函数内的代码,将构造函数内部属性和方法赋值给该对象
- 1️⃣ 首先会创建一个空对象
- 所以大家看,
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.普通对象
* 2.构造函数
* 3.Function
* 4.Object
**/
function Foo(name) {
this.name = name;
}
const obj = {};
const obj2 = {
name: "给他一个新的原型",
}
const foo = new Foo("czx");
console.log(foo.__proto__ === Foo.prototype);
console.log(obj.__proto__ === Object.prototype)
console.log(Foo.prototype === Foo.__proto__);
console.log(Foo.__proto__ === Function.prototype);
console.log(Function.prototype === Function.__proto__);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.__proto__ === Function.prototype);
console.log(Object.prototype.__proto__);
Foo.prototype = obj2;
console.log(Foo.prototype.__proto__ === Object.prototype)
console.log(foo.__proto__ === Foo.prototype);
答案
大家一定要自己写完后再来看答案啊,这样印象才深刻
console.log(foo.__proto__ === Foo.prototype);//true
console.log(obj.__proto__ === Object.prototype)//true
console.log(Foo.prototype === Foo.__proto__);//false
console.log(Foo.__proto__ === Function.prototype);//true
console.log(Function.prototype === Function.__proto__); //true
console.log(Function.prototype.__proto__ === Object.prototype);//true
console.log(Object.__proto__ === Function.prototype);//true;
console.log(Object.prototype.__proto__);//null
Foo.prototype = obj2;
console.log(Foo.prototype.__proto__ === Object.prototype)//true;
console.log(foo.__proto__ === Foo.prototype);//false
原型链
只要理解了上面我讲的原型,原型链自然而然就会了
- 大家可以将原型链理解为,如果我在这一层没找到的东西,我可以去对应的上一层找,直到顶层为止,我来给大家出几道题,就能很快知道了解了
题目一
function Foo(name, age) {
this.name = name;
this.age = age;
}
const bar = {
say: () => {
console.log("执行说话");
},
age2: 32,
}
Foo.prototype = bar;
const foo = new Foo("Spirit", 18)
foo.say();
console.log(foo.age2)
- 大家可以自己想下这段代码执行出来会是什么结果
- 可以知道,我将Foo的显式原型进行了替换,那么我foo去调用原型对象上的方法是可以调用到的,而我构造函数Foo上是没有这两个属性的,但因为他的显式原型对象即bar,它上面是有的
- 所以foo是可以调用到的
题目二
var A = function () { };
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
}
var c = new A();
console.log(b.n);
console.log(b.m);
console.log(c.n);
console.log(c.m);
️ 大家可以想下这道题的输出结果是什么,这道题也是很考察大家对原型链的理解的,接下来我就会开始讲解了
- 首先
A
是一个构造函数,它有显式原型和隐式原型 A
在显式原型上添加了一个属性n,其值为1- 通过实例化A得到了b,有隐式原型,这时候就考察大家对
new
操作熟不熟悉,这时候有b.__proto__===A.prototype
- 这时候A的显式原型被整个替换了,所以之前的实例化的b就取不到这个更换原型之后的值了
- 实例化A得到了c,这时候有
c.__proto__===A.prototype
只不过这个显式原型不再是之前的了 - 开始输出
b.n
是输出1的,因为是加在了最开始A的显式原型上面,所以是能取到值的b.m
是取不到值的,因为这个m
是后面更换了A的显式原型所加上的值,此时的b是取不到的c.n
输出为2,因为更换后的显式原型上面是有n 是2c.m
输出为3,同理- 大家听懂了吗,这道题主要就是考你
new
操作做了什么,让你来判断到底实例化之后是赋予了哪个显式原型,下面的代码是带注解版的
var A = function () { };
A.prototype.n = 1; //显示原型上加了一个 n属性 值为1
var b = new A(); //实例化了的b __proto__===A.prototype
A.prototype = {
n: 2,
m: 3
} //A的显式原型被替换掉了
var c = new A(); //实例化的c 的__proto__===A更换后的显式原型
console.log(b.n); //1
console.log(b.m); //undefined
console.log(c.n);//2
console.log(c.m);//3
题目三
var F = function () { };
Object.prototype.a = function () {
console.log('a');
};
Function.prototype.b = function () {
console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b();
这道题是我认为比较难的一道题,大家可以好好想下这道题的输出到底是什么,接下来我会开始讲解这道题
F
是一个构造函数,它拥有显式原型和隐式原型- 此时
F.prototype.__proto__===Object.prototype
F.__proto__===Function.prototype
- 这个时候,他们是有这种关系的
- 此时
- 在
Object.prototype
上加上了一个函数a
- 在
Function.prototype
加上了一个函数b
- 实例化F得到了
f
,这个时候他有隐式原型f.__proto__===F.prototype
- 接下来我们看输出
f.a()
首先它的隐式原型是等于构造函数的显式原型的,而F.prototype.__proto__
是等于Object.prototype
,也就是说f
是可以沿着这条原型链一路往上找到,最后是可以找到这个a
函数的,所以可以输出a
f.b()
我们可以想下,沿着原型链我们可以找到这个函数b嘛,并不可以,对不对,所以这里会报错,说不存在这么一个函数F.a()
我们来看下这个构造函数是有显式原型和隐式原型的,所以我们看回刚开始的那个解释,它是能找到Object.prototype
和Function.prototype
的,所以能够输出aF.b()
也是同理,所以输出b
- 这道题大家看懂了嘛,主要是考察了函数和普通对象的区别,也是很好的考察了原型链的关系的,下面是带注解的代码
var F = function () { }; //构造函数
//他有隐式原型和显式原型 此时他的隐式原型__proto__===Function.prototype.__proto__===Object.prototype
Object.prototype.a = function () { //Object.prototype是顶层原型
console.log('a');
};
Function.prototype.b = function () {
console.log('b');
}
var f = new F(); //实例化了的f 此时是一个对象 只有隐式原型 __proto__ 所以此时有 __proto__===F.prototype
//️ F.prototype是一个对象,是Object所实例化得到的 所以F.prototype.__proto__===Object.prototype
f.a(); //a
f.b(); //报错 没有这个函数
F.a(); //a
F.b();//b
[JS基础] 带你深入了解JS原型的更多相关文章
- js基础到精通全面教程--JS教程
适合阅读范围:对JavaScript一无所知-离精通只差一步之遥的人 基础知识:HTML JavaScript就这么回事1:基础知识 1 创建脚本块 1: <script language=”J ...
- js基础知识温习:构造函数与原型
构造函数 构造函数主要用于初始化新对象.按照惯例,构造函数名第一个字母都要大写. 构造函数有别于其它函数在于它使用new操作符来调用生成一个实例对象.换句话说,如果一个函数使用new操作符来调用,则将 ...
- Js基础知识(四) - js运行原理与机制
js运行机制 本章了解一下js的运行原理,了解了js的运行原理才能写出更优美的代码,提高运行效率,还能解决开发中遇到的不理解的问题. 进程与线程 进程是cpu资源分配的最小单位,进程可以包含多个线程. ...
- [妙味JS基础]第四课:JS数据类型、类型转换
知识点总结 JS数据类型:number数字(NaN).string字符串.boolean布尔值.函数类型.object对象(obj.[].{}.null).undefined未定义 typeof 用来 ...
- NodeJs>------->>第三章:Node.js基础知识
第三章:Node.js基础知识 一:Node.js中的控制台 1:console.log.console.info 方法 console.log(" node app1.js 1> ...
- 9.29学习的js基础
js基础 1.三种js引入方式 a).<input type="button" value="点击事件" onClick="documen ...
- JS基础-该如何理解原型、原型链?
JS的原型.原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对 ...
- AJAX学习前奏----JS基础加强
AJAX学习前奏----JS基础加强 知识概要: 1.js类&属性&方法的定义 2.静态属性与方法 3.构造方法 4.原型的使用 5.Object对象直接加属性和方法 6.JSO ...
- 前端面试题目汇总摘录(JS 基础篇)
JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string typeof null; // o ...
随机推荐
- 「游记」NOIP 2021 爆零记
推荐访问本人自建博客 \(\text{cjwen.top}\) 欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉,第一次参加 \(NOIP\),欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉. 第一题比较简单,用类似于筛质数的做法 ...
- linux下gdb如何处理coredump错误
linux下gdb如何处理coredump错误 在编写C++程序中,我们经常会遇到一种错误,segment fault, 这种coredump错误 会导致程序运行时异常退出或者终止,这种错误没有明显错 ...
- Nginx--Sorry, the page you are looking for is currently unavailable
- 操作系统实验一:进程管理(含成功运行C语言源代码)
目录 操作系统实验一:进程管理 1.实验目的 2.实验内容 3.实验准备 3.1.1进程的含义 3.1.2进程的状态 3.1.3进程状态之间的转换 3.2 进程控制块PCB 3.2.1进程控制块的作用 ...
- pytest(5)-断言
前言 断言是完整的测试用例中不可或缺的因素,用例只有加入断言,将实际结果与预期结果进行比对,才能判断它的通过与否. unittest 框架提供了其特有的断言方式,如:assertEqual.asser ...
- centos7对外开放端口号
前提:防火墙处于打开状态 1:查看防护墙启动状态:systemctl status firewalld 2:开启:systemctl start firewalld 3:关闭:systemctl s ...
- [VIP] openstack环境配置VIP
描述 当你在openstack平台创建一个network,在这个network上创建两台虚拟机,当这两台虚拟机还需要VIP时,你从这个network分配一个地址做为VIP,配置到虚拟机上,你会发现这个 ...
- 领导满意,客户喜欢的数据报表怎么做,交给Smartbi!
财务分析是以会计核算和报表资料及其他相关资料为依据,采用一系列专门的分析技术和方法,对企业等经济组织过去和现在有关筹资活动.投资活动.经营活动.分配活动的盈利能力.营运能力.偿债能力和增长能力状况等进 ...
- 【C# 线程】开篇 线程
概述 线程主要学习什么,通过一个月的摸索.终于总结出来了:学习Thread类涉及到学习Thread类涉及到线程单元状态: ApartmentState.GetApartmentState\SetApa ...
- WPS:添加公式后,行间距变宽的解决方法
找到公式所属段落的样式,右键修改样式 左下角'格式'中选择'段落' 段落间距设置为0,不要勾选与文档网格对齐