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文字流输出有道智云语音合成的结合(略有遗憾),下面我记载…...

(学习笔记-内存管理)内存分段、分页、管理与布局
内存分段 程序是由若干个逻辑分段组成的,比如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段的形式把这些分段分离出来。 分段机制下,虚拟地址和物理地址是如何映射的? 分段机制下的虚拟地址由…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...

渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用
阻止除自定义标签之外的所有标签 先输入一些标签测试,说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时(如通过点击或键盘导航&…...
python打卡day49@浙大疏锦行
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 一、通道注意力模块复习 & CBAM实现 import torch import torch.nn as nnclass CBAM(nn.Module):def __init__…...