Javascript知识点详解:对象的继承、原型对象、原型链
目录
对象的继承
原型对象概述
构造函数的缺点
prototype 属性的作用
原型链
constructor 属性
instanceof 运算符
构造函数的继承
多重继承
对象的继承
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,本章介绍 JavaScript 的原型链继承。
原型对象概述
构造函数的缺点
JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。
function Cat (name, color) {this.name = name;this.color = color;
}
var cat1 = new Cat('大毛', '白色');
cat1.name // '大毛'
cat1.color // '白色'
上面代码中,Cat
函数是一个构造函数,函数内部定义了name
属性和color
属性,所有实例对象(上例是cat1
)都会生成这两个属性,即这两个属性会定义在实例对象上面。
通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点:同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
function Cat(name, color) {this.name = name;this.color = color;this.meow = function () {console.log('喵喵');};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
上面代码中,cat1
和cat2
是同一个构造函数的两个实例,它们都具有meow
方法。由于meow
方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow
方法。这既没有必要,又浪费系统资源,因为所有meow
方法都是同样的行为,完全应该共享。
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。
prototype 属性的作用
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
下面,先看怎么为对象指定原型。
JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。
function f() {}
typeof f.prototype // "object"
上面代码中,函数f
默认具有prototype
属性,指向一个对象。
对于普通函数来说,该属性基本无用。
但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
function Animal(name) {this.name = name;
}
Animal.prototype.color = 'white';var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');console.log(cat1.color) // 'white'
console.log(cat2.color) // 'white'
上面代码中,构造函数Animal
的prototype
属性,就是实例对象cat1
和cat2
的原型对象。原型对象上添加一个color
属性,结果,实例对象都共享了该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';
上面代码中,实例对象cat1
的color
属性改为black
,就使得它不再去原型对象读取color
属性,后者的值依然为yellow
。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。
一方面,任何一个对象,都可以充当其他对象的原型;
另一方面,由于原型对象也是对象,所以它也有自己的原型。
因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。
也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
Object.getPrototypeOf(Object.prototype)
// null
上面代码表示,Object.prototype
对象的原型是null
,由于null
没有任何属性,所以原型链到此为止。
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined
。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
举例来说,如果让构造函数的prototype
属性指向一个数组,就意味着实例对象可以调用数组方法。
var MyArray = function () { };MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;var mine = new MyArray();
console.log(mine.push(1, 2, 3));
console.log(mine.length);
console.log(mine instanceof Array);
上面代码中,
mine
是构造函数MyArray
的实例对象,由于MyArray.prototype
指向一个数组实例,使得mine
可以调用数组方法(这些方法定义在数组实例的prototype
对象上面)。
最后那行instanceof
表达式,用来比较一个对象是否为某个构造函数的实例,结果就是证明mine
为Array
的实例。
上面代码还出现了原型对象的constructor
属性,这个属性的含义下一节就来解释。
constructor 属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
function P() {}
P.prototype.constructor === P // true
由于constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承。
function P() { }
var p = new P();
console.log(p.constructor === P);
console.log(p.constructor === P.prototype.constructor);
console.log(p.hasOwnProperty('constructor'));
上面代码中,p
是构造函数P
的实例对象,但是p
自身没有constructor
属性,该属性其实是读取原型链上面的P.prototype.constructor
属性。
constructor
属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
function F() {};
var f = new F();
//这里的f对象是由F构造函数产生的
f.constructor === F // true
f.constructor === RegExp // false
上面代码中,constructor
属性确定了实例对象f
的构造函数是F
,而不是RegExp
。
另一方面,有了constructor
属性,就可以从一个实例对象新建另一个实例。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
//相当于 var y = new x.Constr.prototype.constructor
y instanceof Constr // true
上面代码中,x
是构造函数Constr
的实例,可以从x.constructor
间接调用构造函数。这使得在实例方法中,调用自身的构造函数成为可能。
Constr.prototype.createCopy = function () {return new this.constructor();
};
上面代码中,createCopy
方法调用构造函数,新建另一个实例。
constructor
属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor
属性,防止引用的时候出错。
function Person(name) {this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
上面代码中,构造函数Person
的原型对象改掉了,但是没有修改constructor
属性,导致这个属性不再指向Person
。由于Person
的新原型是一个普通对象,而普通对象的constructor
属性指向Object
构造函数,导致Person.prototype.constructor
变成了Object
。
所以,修改原型对象时,一般要同时修改constructor
属性的指向。
// 坏的写法
C.prototype = {method1: function (...) { ... },// ...
};
// 好的写法
C.prototype = {constructor: C,method1: function (...) { ... },// ...
};
// 更好的写法
C.prototype.method1 = function (...) { ... };
上面代码中,要么将constructor
属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证instanceof
运算符不会失真。
如果不能确定constructor
属性是什么函数,还有一个办法:通过name
属性,从实例得到构造函数的名称。
function Foo() { }
var f = new Foo();
console.log(f.constructor.name) // "Foo"
instanceof 运算符
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true
上面代码中,对象v
是构造函数Vehicle
的实例,所以返回true
。
instanceof
运算符的左边是实例对象,右边是构造函数,它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。
因此,下面两种写法是等价的:
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
上面代码中,Vehicle
是对象v
的构造函数,它的原型对象是Vehicle.prototype。
由于instanceof
检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true
。
var d = new Date();
d instanceof Date // true
//d是Date构造函数的实例对象
d instanceof Object // true
所有对象都是Object的实例对象
上面代码中,d
同时是Date
和Object
的实例,因此对这两个构造函数都返回true
。
由于任意对象(除了null
)都是Object
的实例,所以instanceof
运算符可以判断一个值是否为非null
的对象。
var obj = { foo: 123 };
obj instanceof Object // true
null instanceof Object // false
//Object的实例对象时null,null不是Object的实例对象
上面代码中,除了null
,其他对象的instanceOf Object
的运算结果都是true
。
instanceof
的原理是检查右边构造函数的prototype
属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null
对象。这时,instanceof
判断会失真。
var obj = Object.create(null);
typeof obj // "object"
obj instanceof Object // false
上面代码中,Object.create(null)
返回一个新对象obj
,它的原型是null
(Object.create()
的详细介绍见后文)。右边的构造函数Object
的prototype
属性,不在左边的原型链上,因此instanceof
就认为obj
不是Object
的实例。
注意:这是唯一的instanceof
运算符判断会失真的情况(一个对象的原型是null
)。
instanceof
运算符的一个用处,是判断值的类型。
var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
上面代码中,instanceof
运算符判断,变量x
是数组,变量y
是对象。
注意:instanceof
运算符只能用于对象,不适用原始类型的值。
var s = 'hello';
s instanceof String // false
上面代码中,字符串不是String
对象的实例(因为字符串不是对象),所以返回false
。
此外,对于undefined
和null
,instanceof
运算符总是返回false
。
undefined instanceof Object // false
null instanceof Object // false
利用instanceof
运算符,还可以巧妙地解决,调用构造函数时,忘了加new
命令的问题。
function Fubar(foo, bar) {if (this instanceof Fubar) {this._foo = foo;this._bar = bar;} else {return new Fubar(foo, bar);}
}
上面代码使用instanceof
运算符,在函数体内部判断this
关键字是否为构造函数Fubar
的实例。如果不是,就表明忘了加new
命令。
构造函数的继承
让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。
第一步是在子类的构造函数中,调用父类的构造函数。
function Sub(value) {Super.call(this);this.prop = value;
}
上面代码中,Sub
是子类的构造函数,this
是子类的实例。在实例上调用父类的构造函数Super
,就会让子类实例具有父类实例的属性。
第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';
上面代码中,Sub.prototype
是子类的原型,要将它赋值为Object.create(Super.prototype)
,而不是直接等于Super.prototype
。否则后面两行对Sub.prototype
的操作,会连父类的原型Super.prototype
一起修改掉。
另外一种写法是Sub.prototype
等于一个父类实例。
Sub.prototype = new Super();
上面这种写法也有继承的效果,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法。
举例来说,下面是一个Shape
构造函数。
function Shape() {this.x = 0;this.y = 0;
}
Shape.prototype.move = function (x, y) {this.x += x;this.y += y;console.info('Shape moved.');
};
我们需要让Rectangle
构造函数继承Shape
。
// 第一步,子类继承父类的实例
function Rectangle() {Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {this.base = Shape;this.base();
}
// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
采用这样的写法以后,instanceof
运算符会对子类和父类的构造函数,都返回true
。
var rect = new Rectangle();
rect instanceof Rectangle // true
rect instanceof Shape // true
上面代码中,子类是整体继承父类。有时只需要单个方法的继承,这时可以采用下面的写法。
ClassB.prototype.print = function() {ClassA.prototype.print.call(this);// some code
}
上面代码中,子类B
的print
方法先调用父类A
的print
方法,再部署自己的代码。这就等于继承了父类A
的print
方法。
多重继承
JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。
function M1() {this.hello = 'hello';
}
function M2() {this.world = 'world';
}
function S() {M1.call(this);M2.call(this);
}
// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);
// 指定构造函数
S.prototype.constructor = S;
var s = new S();
s.hello // 'hello'
s.world // 'world'
上面代码中,子类S
同时继承了父类M1
和M2
。这种模式又称为 Mixin(混入)。
相关文章:

Javascript知识点详解:对象的继承、原型对象、原型链
目录 对象的继承 原型对象概述 构造函数的缺点 prototype 属性的作用 原型链 constructor 属性 instanceof 运算符 构造函数的继承 多重继承 对象的继承 面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B …...

学之思开源考试系统部署至Centos7
学之思开源考试系统部署至Centos7 1、下载源码 源码下载: https://gitee.com/mindskip/xzs-mysql 数据库脚本下载: https://www.mindskip.net:999/ 2、项目打包 分别在\source\vue\xzs-student目录和source\vue\xzs-admin目录,执行前端打…...

如何利用浏览器的可见性API优化网站性能
最近在使用微软AI聊天工具Bing时,发现一个有趣的东西。我向它提问后,它在持续输出的过程中,如果我离开了当前它的浏览器会话,比如切屏,看当前浏览器的其它标签页,它会默认停止它的输出,等我回来…...

还不知道IP地址不够用是怎么被大牛们解决的?(NAT/NAPT, IPv6, DHCP)
文章目录 前言1. DHCP网络管理协议什么是 DHCPDHCP 两种分配机制 2. NAT网络地址转换协议什么是 NATNAT 技术使用NAT网络设备间如何通信两个内网设备相互通信不同内网中的设备相互通信NAT IP转换过程 NAPT 技术NAT 技术的缺陷 3. IPv6 协议什么是 IPv6 总结 前言 在之前的文章…...

使用决策树预测隐形眼镜类型
任务描述 本关任务:编写一个例子讲解决策树如何预测患者需要佩戴的隐形眼镜类型。使用小数据集,我们就可以利用决策树学到很多知识:眼科医生是如何判断患者需要佩戴的镜片类型,一旦理解了决策树的工作原理,我们甚至也…...

[ACTF2020 新生赛]BackupFile 1
题目环境: 好好好,让找源文件是吧?咱们二话不说直接扫它后台 使用dirsearch工具扫描网站后台(博主有这个工具的压缩包,可以私聊我领取)python dirsearch.py -u http://0d418151-ebaf-4f26-86b2-5363ed16530…...

解决vuex刷新数据丢失
Vuex 是一个 Vue.js 的状态管理库,它使得你可以在 Vue 组件之间共享状态。当你在 Vuex 中更新状态时,如果你遇到数据丢失或数据不一致的问题,可能需要进行深度复制或者使用其他方式来确保数据的完整性。 假设你有一个 Vuex 存储,…...

linux系统下读取当前硬盘的温度
这个其实很简单,借助于smartctl工具(Ubuntu默认安装好了),标红的部分就是当前温度,单位是摄氏度。 sudo smartctl -l scttempsts /dev/sda...

python 深度学习 解决遇到的报错问题8
本篇继python 深度学习 解决遇到的报错问题7-CSDN博客 目录 一、OSError: [WinError 127] 找不到指定的程序。 Error loading "D:\my_ruanjian\conda-myenvs\deeplearning\lib\site-packages\torch\lib\caffe2_detectron_ops.dll" or one of its dependencies. 二、…...

Linux pipe()系统调用示例
Linux系统调用pipe函数,创建一个pipe,通过传入的fd数组返回pipe的读、写两端。 其中fd[ 0 ]用于读,fd[ 1 ]用于写。 一个pipe是单向数据传输的,不用用于父子进程双向读写。创建2个pipe实现父子进程间的双线读写。 #include <u…...

音频中的采样率和比特率
音频中的采样率和比特率 采样频率千比特率音频比特率 采样频率 参考:https://blog.csdn.net/qq_38907791/article/details/88925224 采样频率,也称为采样速度或者采样率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它…...

Python常用脚本
1.解压指定文件夹内的zip包,解压到当前位置 import os import zipfile# 指定文件夹路径 folder_path "/path/to/your/folder"# 获取文件夹下所有的zip文件 zip_files [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.e…...

Redis5 分布式系统之主从模式
目录 分布式系统 引子 分布式系统类型 主从模式 一个主节点和多个从节点 创建多个节点方法 配置主从结构 主从模式知识 主从复制 拓扑结构 1.一主一从 2.一主多从 3.树形主从 主从实现原理 psync数据同步 全量复制和部分复制 psync流程 1.全量数据同步 2.部…...

【黑马程序员】Maven 进阶
文章目录 前言一、分模块开发与设计1. 分模块开发意义2. 分模块开发(模块拆分)2.1 创建 Maven 模块2.2 书写模块代码2.3 通过 Maven 指令安装模块到本地仓库(install 指令) 二、依赖管理1. 依赖传递1.1 依赖传递冲突问题 2. 可选依…...

231108 C语言memset当第三个参数为0,即设置个数为零也不报错
memset语法: void *memset(void *s, int c, size_t n); 犹豫第三个参数为0会不会报错,测试不会。 代码: #include"stdio.h" #include"stdlib.h" // memset memcpy int main() { int sig[100] { 0 }; int …...

HMM与LTP词性标注之马尔科夫模型(HMM原理剖析)
文章目录 问题描述viterbi算法联合概率与条件概率维特比算法实例 问题描述 viterbi算法 联合概率与条件概率 维特比算法实例...

Python自动化测试selenium指定截图文件名方法
这篇文章主要介绍了Python自动化测试selenium指定截图文件名方法,Selenium 支持 Web 浏览器的自动化,它提供一套测试函数,用于支持 Web 自动化测试,下文基于python实现指定截图文件名方法,需要的小伙伴可以参考一下 前…...

Linux 实现文件后半部分的复制
继上次实现文件从后往前数2k的数据进行复制,此次要求是文件的一半且是后半部分。 即复制源文件sour_file的后半部分到dest_file 除了数据上从后2K变化到后一半之外,其他的几乎没有什么变化。 这道题的关键点就在于后一半怎么求,在经历了用 …...

阿里开源中间件一览
1. 概述以及竞品对比 间件介绍官方链接竞品竞品介绍异同点对比Dubbo高性能的RPC框架,用于实现分布式服务的调用和管理。DubbogRPC gRPC是由Google开源的一款高性能、通用的RPC框架,支持多种编程语言 链接:gRPC Dubbo更注重于服务治理和可扩展…...

Ubuntu20.04下Salome_meca 2022软件安装(支持GPU加速)
一、什么是Salome_meca ? Salome_meca 是一个开源的有限元分析软件套件,主要用于模拟和分析复杂的力学问题。它是 Salome 平台的一部分,Salome 是一个通用的集成化软件环境,用于建模、预处理、模拟和后处理各种复杂的工程和科学问…...

uniapp:打包ios配置隐私协议框
使用uniapp打包ios 上架商店需要配置隐私协议政策弹窗。当用户点击确定后才能继续操作。 首先manifest.json中配置使用原生隐私政策提示框是不支持ios的。不用勾选。 解决思路: 1、新建页面:iosLogin.vue,pages.json中 这个页面需要放在第一…...

JS逆向爬虫---请求参数加密③【比特币交易爬虫】
查询参数确定 t无加密 请求头参数加密 X-Apikey参数加密确定 X-Apikey逆向 const API_KEY "a2c903cc-b31e-4547-9299-b6d07b7631ab" function encryptApiKey(){ var t API_KEY, e t.split(""), n e.splice(0, 8);return t e.concat(n).join("&…...

云计算:未来科技的超级英雄
随着科技的不断发展,云计算已经成为了现代社会的核心驱动力之一。从智能家居到无人驾驶,从虚拟现实到人工智能,云计算的崭新时代已经到来,为我们的生活带来了智能、便捷和有趣的体验。本文将带领读者穿越时空,探索未来…...

【Node.js入门】1.3 开始开发Node.js应用程序
1.3 开始开发Node.js应用程序 学习目标 (1)熟悉开发工具Visual Studio Code的基本使用; (2)掌握Node.js应用程序的编写、运行和调试的基本方法。 构建第一个 Node.js应用程序 代码 const http require("htt…...

ansible-playbook之file模块
1、file模块功能说明 file模块主要用于远程主机上的文件操作,file模块包含如下选项: force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软…...

Vue路由介绍及使用
一、单页应用程序介绍 1.概念 单页应用程序:SPA【Single Page Application】是指所有的功能都在一个html页面上实现,当切换不同的功能时,页面不会进行刷新,类似Ajax请求,但请求地址会发生部分变化。 2.具体示例 单…...

案例 - 拖拽上传文件
直接看效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>拖拽上传文件</title>&l…...

github 上传代码报错 fatal: Authentication failed for ‘xxxxxx‘
问题 今天一时兴起创建了个 github 新仓库,首次上传本地代码时,遇到了一个报错。本来以为是账号密码的问题,搞了好几次,发现都没错的情况下还是上传不上去。目测判断是认证相关问题,具体报错信息如下: rem…...

Linux虚拟网络设备之bridge
继前两篇介绍了tun/tap和veth之后,本篇将介绍Linux下常用的一种虚拟网络设备,那就是bridge(桥)。 Linux ip netns 命令-CSDN博客 veth-pair,netns-CSDN博客 图解几个与Linux网络虚拟化相关的虚拟网卡-VETH/MACVLAN/MACVTAP/IPVLAN_veth 转发 有拷贝-C…...

最后一个大更新!Win11 2023正式发布:Copilot终于来了
美国当地时间周二, 微软正式宣布为所有符合条件的Windows 11电脑发布Windows 11 2023更新(Windows 11 23H2)。 考虑到微软计划在2024年发布下一代Windows(可能是Windows 12),这也使得23H2成为Windows 11的最后一个重大更新。 此次更新的重点就是AI功能&a…...