js原型以及原型链
目录
- 原型
- 隐式原型
- 显式原型
- constructor
- new操作符
- 重写原型对象
- 原型链
- 继承
- 原型链继承
- 借用构造函数继承
- 组合构造继承
- 原型继承
- 寄生继承
- 组合寄生继承
- 原型继承关系
原型
在JavaScript中,每个对象都有一个内置属性[[prototype]],这个属性指向一个另一个对象
当我们访问对象中的属性时,会触发[[GET]]操作
这个操作会现在自己对象内部寻找对应的值,如果找不到就会在[[prototype]]中所指向的对象中寻找
可以通过__proto__和Object.getPrototypeOf两个属性来访问这个对象
可以通过__proto__和Object.setPrototypeOf两个属性来设置这个对象
注意,__proto__是早期浏览器自行添加的属性,而Object.getPrototypeOf和Object.setPrototypeOf是标准添加的
如下代码所示
var obj = {}console.log(obj.__proto__)console.log(Object.getPrototypeOf(obj))console.log(obj.__proto__ === Object.getPrototypeOf(obj))var obj2 = {}var obj3 = {a: 1}obj2.__proto__ = obj3console.log(obj2.__proto__)console.log(obj2.a)Object.setPrototypeOf(obj2, obj)console.log(obj2.__proto__)
控制台结果如下

隐式原型
每个对象都会有一个__proto__属性,这个属性不建议直接访问或修改,是只在JavaScript内部使用的属性,因此被称之为隐式原型
显式原型
函数也是一个特殊的对象,是对象也就意味着也拥有隐式原型
但与普通对象不同的是,函数同时也拥有显式原型
和隐式原型不同的是,显式原型可以直接访问,并且经常使用
显示原型的作用就是用来构造对象
函数的显式原型可以通过prototype属性来访问
如下代码
function foo() {}var obj = {}console.log(foo.prototype)console.log(obj.prototype)
控制台结果如下

constructor
在说明显式原型的用处之前需要先知道一个函数constructor
constructor在函数的显式原型上
constructor也被称之为构造函数
这个constructor指向函数本身
function foo() {}console.log(foo.prototype.constructor)console.log(foo === foo.prototype.constructor)
控制台结果

new操作符
在之前的this绑定规则一文中new关键字做了以下操作
- 创建一个
空对象 - 将
空对象的this绑定到这个空对象 - 执行函数体里的代码
其实还有第四步
即将函数的显式原型赋值到空对象中的隐式原型上
这意味着如果我们通过某一个函数来构建一个对象,这个对象的隐式原型指向的是函数的显式原型
function Person() {}var obj = new Person()console.log(obj.__proto__)console.log(obj.__proto__ === Person.prototype)
控制台结果如下

我们说new关键字会执行函数体里的代码,这句话不能说错
但更精确的说法是new关键字会执行显式原型中constructor函数里的代码
重写原型对象
如果我们需要在显式原型上添加许多属性,通常我们会重写整个显式原型
function Person() {}Person.prototype = {a: 1,b: 2,foo: function () {console.log(this.a)}}var obj = new Person()console.log(obj.b)obj.foo()console.log(Person.prototype.constructor)
控制台结果如下

可以看到,如果我们重写显式原型的话constructor会指向Object
Person.prototype.constructor = Person
我们可以通过这种方式来修改Person的constructor,但这样修改得到的constructor的[[Enumerable]]被设置成了true
默认情况下的constructor的[[Enumerable]]是false
如果想要解决这个问题,可以通过Object.defineProperty函数
Object.defineProperty(Person.prototype, "constructor", {enumerable: false,value: Person})
这样得到的constructor就是不可枚举的了
关于对象的属性描述符可以看我这篇文章
(未动笔,未来可寄)
原型链
在JavaScript中,如果要实现继承,就必须要理解一个重要概念,即原型链
当我们从一个对象获取一个属性时,如果在当前对象中没有获取到对应的值时就会通过对象的隐式原型来寻找
如果也没有找到的话就会一直向上寻找
所有对象的顶层原型为 [Object: null prototype] {}
所有通过Object创建出来的对象其隐式原型都指向这个
这个原型其实也有对应的隐式原型,但指向的是null
综上所述,在JavaScript中所有类的父类是Object
原型链的顶层对象就是Object的隐式原型
在理解了原型链之后我们就能实现继承
继承
以下是几种继承的实现方式
原型链继承
原型链继承是通过JavaScript对象属性查找规则实现的一种继承方式
function Person() {this.age = 18}var p = new Person()function Student() {this.id = "101"}Student.prototype = pvar stu = new Student()console.log(stu.age)console.log(stu.id)
控制台结果如下

这个方法需要构造一个父类的实例对象,再将子类的显式原型指向父类构造的实例对象,子类在构造实例对象时生成的对象其隐式原型就指向了父类构造的实例对象
这个方法也有自己的缺点
- 某些属性其实是
存储在父类的实例对象上的,直接打印子类的实例对象是看不到这些属性的 - 这个属性会
被多个对象共享 这个属性的值是唯一的
借用构造函数继承
借用构造函数继承的关键就在于子类中直接调用父类的构造函数
function Person() {this.age = 18}function Student() {Person.call(this)this.id = "101"}var stu = new Student()console.log(stu)
控制台结果如下

可以看到此时父类的属性也已经继承过来了
但这只是属性的继承,如果想要调用父类的方法的话还需要和原型链继承一起使用
组合构造继承
function Person() {this.age = 18}Person.prototype.foo = function () {console.log(this.age)}var p = new Person()function Student() {Person.call(this)this.id = "101"}Student.prototype = pvar stu = new Student()console.log(stu.age)console.log(stu.id)stu.foo()
控制台结果如下

这样我们就实现了属性和方法的一起继承
这种方法其实也有一些问题
- 这个方法会调用
两次构造函数- 一次在生成子类实例对象时调用了父类的构造函数
- 一次在创建子类原型的时候
- 所有的子类实例对象会拥有
两份父类属性
一份在自己这里,一份在自己的隐式原型中
默认访问时优先访问自己本身有的属性
原型继承
在2006年时道格拉斯·克罗克福德提出了一种新的继承方式
这种方法并不依靠constructor来实现
function Person() {this.age = 18}Person.prototype.foo = function () {console.log("this function")}function Student() {this.id = "101"}var obj = {}Object.setPrototypeOf(obj, Person.prototype)Student.prototype = objvar newObj = new Student()
我们使用借用构造函数继承的目的就是要一个新对象,新对象的隐式原型指向父类的显式原型,最后子类的显式原型再指向这个新对象
通过Object.setprototypeOf方法来设置新的obj对象的隐式原型指向父类的显式原型,子类的显式原型指向obj
这样就绕过了constructor
还有其他几种实现方法
function Person() {this.age = 18}Person.prototype.foo = function () {console.log("this function")}function Student() {this.id = "101"}var obj = {}function F() { }F.prototype = Person.prototypeStudent.prototype = new F()var newObj = new Student()
定义一个新函数,使新函数的显式原型直接指向父类的显式原型
在构造这个新函数的对象时实际上是构造了一个指向父类的空的新对象
再将子类的显式原型指向这个新对象
这也是道格拉斯·克罗克福德提出来的方法
function Person() {this.age = 18}Person.prototype.foo = function () {console.log("this function")}function Student() {this.id = "101"}var obj = Object.create(Person.prototype)Student.prototype = objvar newObj = new Student()
这里使用了Object.create方法,这个方法会创建一个空对象并将这个空对象的隐式原型指向你传入的对象
可能存在一些兼容性问题
寄生继承
最后我们将原型继承封装成一个函数
function inherit(Subtype, Supertype) {function F() { }F.prototype = Supertype.prototypevar obj = new F()Subtype.prototype = objObject.defineProperty(Subtype.prototype, "constructor", {enumerable: false,value: Subtype})}
这个inherit就是寄生继承的实现方法
这种方法同样由道格拉斯·克罗克福德提出
组合寄生继承
此时寄生继承已经能解决原型继承和借用构造函数继承中的绝大部分问题,剩下的一个最大的问题就是还需要继承父类中的属性
为了解决这个问题我们需要综合上面所有的方法来得到最终的解决方案
function inherit(Subtype, Supertype) {function F() { }F.prototype = Supertype.prototypevar obj = new F()Subtype.prototype = objObject.defineProperty(Subtype.prototype, "constructor", {enumerable: false,value: Subtype})}function Person() {this.age = 18}Person.prototype.foo = function () {console.log("this function")}function Student() {Person.call(this)this.id = "101"}inherit(Student, Person)var newObj = new Student()console.log(newObj.id)console.log(newObj.age)newObj.foo()
控制台结果如下

这也是目前在ES6以前使用最多的继承解决方案
原型继承关系
最后我们再来梳理一下在JavaScript中的原型继承关系
Object是所有类的父类
Object的显式原型的隐式原型指向null

因为Object也是一个函数,也同样拥有隐式原型,它的隐式原型指向Function的显式原型
Function的隐式原型同样指向自己的显式原型
因为Function的显式原型是通过new Object创建出来的,所以它的隐式原型指向Object的显式原型

我们创建的函数的显式原型指向函数自己的显式原型
我们的函数本质上是通过new Function创建出来的
所以函数的隐式原型则指向Function的显式原型

我们通过foo创建出来的对象的隐式原型指向foo的显式原型
通过new Object创建出来的对象的隐式原型指向Object的显式原型

以上就是在JavaScript中的原型继承关系图
最后附带一张更加形象的示例图

相关文章:
js原型以及原型链
目录 原型隐式原型显式原型constructornew操作符 重写原型对象原型链继承原型链继承借用构造函数继承组合构造继承 原型继承寄生继承组合寄生继承 原型继承关系 原型 在JavaScript中,每个对象都有一个内置属性[[prototype]],这个属性指向一个另一个对象…...
Java面向对象编程实战详解(图书管理系统示例)
文章目录 面向编程概念图书管理系统示例需求分析设计阶段编码实现创建目录结构Book类的编码BookList类的编码User类的编码AdminUser类的编码NormalUser类的编码启动类的编写具体的操作实现IOperation接口新增图书的实现借阅图书的实现删除图书的实现显示图书的实现查找图书的实…...
ubuntu设置主机ip
ubuntu 设置ip sudo dhclient -r enp67s0 # 是你的网卡,可以通过ifconfig 查,比如enp0 sudo ifconfig enp67s0 192.168.1.114 netmask 255.255.255.0 Ubuntu显示有线网已连接但无法上网,已经确认网口、交换机(路由器ÿ…...
CleanMyMac X4.14.1中文版如何清理 Mac系统?CleanMyMac 真的能断网激活吗?
CleanMyMac X4.14.1中文版如何清理 Mac系统?Mac系统在使用过程中都会产生大量系统垃圾,如不需要的系统语言安装包,视频网站缓存文件,mac软件卸载残留的注册表等。 随着时间推移,mac系统垃圾就会越来越多,电…...
详细介绍 React 中如何使用 redux
在使用之前要先了解它的配套插件: 在React中使用redux,官方要求安装其他插件 Redux Toolkit 和 react-redux Redux Toolkit:它是一个官方推荐的工具集,旨在简化 Redux 的使用和管理。Redux Toolkit 提供了一些提高开发效率的工具…...
VLOOKUP多条件查询
LOOKUP(1,0/((A3:A15A18)*(C3:C15C18)),F3:F15)...
分页插件Mybatis
<plugins><!-- com.github.pagehelper为PageHelper类所在包名 --><plugin interceptor"com.github.pagehelper.PageInterceptor"><!-- 配置方言:告诉分页插件使用底层数据库是什么--><property name"helperDialect" value"…...
AXI协议之AXILite开发设计(四)—Block Design使用
微信公众号上线,搜索公众号小灰灰的FPGA,关注可获取相关源码,定期更新有关FPGA的项目以及开源项目源码,包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 2、AXI interconnect互联组件的使用…...
音视频——帧内预测
H264编码(帧内预测) 在帧内预测模式中,预测块P是基于已编码重建块和当前块形成的。对亮度像素而言,P块用于44子块或者1616宏块的相关操作。44亮度子块有9种可选预测模式,独立预测每一个44亮度子块,适用于带有大量细节的图像编码&…...
2.uni-app项目文件
uni-app像是vue与微信小程序的合体,使用 uni-ui项目 模板创建的项目文件如下 目录 1 pages 2 pages.json 3 App.vue 4 index.html 5 static 6 uni_modules 7 manifest.json 8 main.js 9 uni.scss 1 pages 这个是放页面的,默认里面有…...
JavaScript学习 -- 对称加密算法DES
在现代的互联网时代,数据安全性备受关注。为了保护敏感数据的机密性,对称加密算法是一种常用的方法。在JavaScript中,DES(Data Encryption Standard)是一种常用的对称加密算法。本篇博客将为您展示如何在JavaScript中使…...
【Python数据分析】Python常用内置函数(二)
🎉欢迎来到Python专栏~Python常用内置函数(二) ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹 ✨博客主页:小夏与酒的博客 🎈该系列文章专栏:Python学习专栏 文章作者技术和水平有限,如果文…...
Api接口出现Required request body is missing的解决方法
目录 1.问题所示2.原理分析3.解决方法1.问题所示 在使用PostMan 测试接口的时候,出现如下问题: {"code": 400,"success": false,"data": {},"msg":...
【Kaggle】Kaggle数据集如何使用命令语句下载?
一、Kaggle数据集如何下载 1.1 问题的起因 最近看到了 Google 组织的 Kaggle 比赛,想自己试一下,但是数据集太大了,将近有370G的数据。直接下载的话,网速太慢,可能要下载3-4天,所以萌生了用命令语句下载的…...
android pdf框架,编译mupdf
因为mupdf编译的体积不小,之前也发过编译的文章,现在更新一下. 建一个mupdf_c目录,名字自己取,在里面git下载mupdf源码,把目录修改为libmupdf mupdf_c目录下建build.gradle文件,内容如下 apply plugin: com.android.library apply plugin: maven-publishgroup com.artifex.…...
线性表详细讲解
2.1 线性表的定义和特点2.2 案例引入2.3 线程表的类型定义2.4 线性表的顺序表示和实现2.4.1 线性表的顺序存储表示2.4.2 线性表的结构类型定义2.4.3 顺序表基本操作的实现2.4.4 顺序表总结 2.5 线性表的链式表示和实现2.5.1 线性表的链式存储表示2.5.2 单链表的实现(…...
代码随想录算法训练营day45
文章目录 Day45爬楼梯题目思路代码 零钱兑换题目思路代码 完全平方数题目思路代码 Day45 爬楼梯 70. 爬楼梯 - 力扣(LeetCode) 题目 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢…...
机器学习深度学习——softmax回归(上)
👨🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——线性回归的简洁实现 📚订阅专栏:机器学习&&深度学习 希望文章对你们有所…...
基于express调用chatgpt文字流输出和有道智云语音合成
express是基于node.js的一个web框架,可以更加简洁的去创建一个后台服务,由于项目的需要,引入和typescript,经过几天的努力实现了chatgpt文字流输出有道智云语音合成的结合(略有遗憾),下面我记载…...
(学习笔记-内存管理)内存分段、分页、管理与布局
内存分段 程序是由若干个逻辑分段组成的,比如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段的形式把这些分段分离出来。 分段机制下,虚拟地址和物理地址是如何映射的? 分段机制下的虚拟地址由…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁
赛门铁克威胁猎手团队最新报告披露,数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据,严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能,但SEMR…...
车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...
数据分析六部曲?
引言 上一章我们说到了数据分析六部曲,何谓六部曲呢? 其实啊,数据分析没那么难,只要掌握了下面这六个步骤,也就是数据分析六部曲,就算你是个啥都不懂的小白,也能慢慢上手做数据分析啦。 第一…...
JavaScript 标签加载
目录 JavaScript 标签加载script 标签的 async 和 defer 属性,分别代表什么,有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...
