JS 实现继承的 5 种方式
文章目录
- 继承
- 原型链继承
- 原型链继承的优缺点
- 构造继承
- 构造继承的优缺点
- 复制继承
- 复制继承的优缺点
- 组合继承
- 组合继承的优缺点
- 寄生组合继承
- 参考
继承
继承作为面向对象语言的三大特性之一(继承、封装和多态),可以在不影响父类对象实现的情况下,使得子类对象具有父类对象的特性,同时还能在不影响父类对象行为的情况下扩展子类对象独有的特性,为编码带来了极大的便利。
定义一个父类 Animal 并增加属性,实例方法和原型上的属性和方法:
/**父类 Animal
*/
function Animal(name = "动物") {// 属性this.name = name;this.type = "animal";this.love = ["唱", "跳", "rapper"];// 方法this.sleep = function () {console.log(this.name + "正在睡觉");};
}// 原型上的属性和方法
Animal.prototype.age = 100;
Animal.prototype.eat = function (food = "苹果") {console.log(this.name + "正在吃" + food);
};
原型链继承
原型链继承的主要思想是:重写子类的prototype
属性,将其指向父类的实例。
定义一个子类 Cat,用于继承父类 Animal,子类 Cat 的实现代码如下:
/**子类
*/
function Cat(name = "卡菲猫") {this.name = name;
}/**原型继承
*/
Cat.prototype = new Animal();
// 很关键的一句,将 Cat 的原型对象指向自身
// 如果不将 Cat 原型对象的 constructor 属性指向自身的话,那将会指向父类 Animal 的构造函数。
Cat.prototype.constructor = Cat;const cat = new Cat("大肥猫");console.log(cat.name); // '大肥猫'
console.log(cat.type); // 'animal'
console.log(cat.age); // 100
cat.eat(); // '大肥猫正在吃苹果'
在子类 Cat 中,存在name
属性,所以在创建一个 Cat 实例的时候,name
属性值会覆盖父类 Animal 的name
属性值,因此输出“大肥猫”。
在子类 Cat 中,不存在type
属性,因此会继承父类 Animal 的type
属性,输出“animal”
,age
属性和eat()
方法同理。
继承示意图:
原型链继承的优缺点
优点:
1️⃣ 简单,容易实现
只需要设置子类的prototype
属性为父类的实例即可,实现起来简单。
2️⃣ 继承关系纯粹
生成的实例即是子类的实例,也是父类的实例
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
3️⃣ 可以通过子类直接访问父类原型链上属性和方法
通过原型链继承的子类,可以直接访问到父类原型链上新增的函数和属性。
// 新增属性和方法
Animal.prototype.color = "pink"
Animal.prototype.getColor = function () {return this.color;
}// 访问新增的属性和方法
console.log(cat.color); // 'pink'
console.log(cat.getColor); // 'pink'
缺点:
1️⃣ 子类的所有实例将共享父类的属性
Cat.prototype = new Animal();
在使用原型链继承时,直接改写了子类 Cat 的prototype
属性,将其指向一个 Animal 的实例,那么所有生成的 Cat 实例都将会共享 Animal 实例的属性和方法。
以上的描述可以理解为以下的代码:
// 生成一个 Animal 的实例
let animal = new Animal();// 通过改写 Cat 的 prototype 属性,所有 Cat 实例将共享 animal 中的属性和方法
Cat.prototype = animal;
这会带来一个很严重的问题,如果父类 Animal 中有个值为引用数据类型,那么改变 Cat 某个实例的属性值将会影响其他实例的属性值。
// 创建两个实例
const cat1 = new Cat()
const cat2 = new Cat();console.log("cat1", cat1.love); // "cat1" ["唱", "跳", "rapper"];
console.log("cat2", cat2.love); // "cat2" ["唱", "跳", "rapper"];// 修改 cat1.love 属性值
cat1.love.push("打篮球");console.log("cat1", cat1.love); // "cat1" ["唱", "跳", "rapper", "打篮球"];
console.log("cat2", cat2.love); // "cat2" ["唱", "跳", "rapper", "打篮球"];
2️⃣ 在创建子类实例时,无法向父类构造函数传递参数。
在通过new
操作符创建子类的实例时,会调用子类的构造函数,而在子类的构造函数中并没有设置与父类的关联,从而导致无法向父类的构造函数传递参数。
3️⃣ 无法实现多继承
由于子类 Cat 的prototype
属性只能设置为一个值,如果同时设置为多个值的话,后面的值会覆盖前面的值,导致 Cat 只能继承一个父类,而无法实现多继承。
4️⃣ 为子类的原型对象添加属性和方法时,必须放在new Animal
语句之后。
实现继承的关键语句是下面这句代码,它实现了子类的prototype
属性的改写
Cat.prototpe = new Animal();
如果想要为子类的原型对象添加的属性和方法,那么需要在这个语句之后再进行添加,因为如果在这个语句之前设置了prototype
属性,后面执行的语句会直接重写prototype
属性,导致之前的设置全部失效。
构造继承
构造继承的主要思想是在子类的构造函数中通过call()
函数改变this
的指向,调用父类的构造函数,从而能将父类的实例的属性和方法绑定到子类的this
上。
// 子类
function Cat() {// 通过 call() 改变 this 的指向实现继承Animal.call(this);
}const cat = new Cat();
console.log(cat.name); // "Tom"
console.log(cat.age); // undefined
cat.sleep(); // "Tom正在睡觉"
cat.eat(); // cat.eat() is not a function
通过代码可以发现,子类可以正常调用父类的属性和方法,而无法调用父类原型对象上的属性和方法,这是因为子类并没有和父类的原型对象建立关联。
构造继承的优缺点
优点:
1️⃣ 可以解决子类实例共享父类属性的问题。
call()
方法实际是改变了父类 Anima l构造函数中this
的指向,调用后this
指向了子类 Cat,相当于父类的name
、type
、love
和sleep
等直接绑定到了子类的this
中,成了子类实例的属性和函数,因此生成的子类实例中各自拥有自己的name
、type
、love
和sleep
,不会互相影响。
2️⃣ 创建子类的实例时,可以向父类传递参数
在call()
方法中,可以传递参数,这时候参数是传递给父类的,这样就可以对父类的属性进行设置,同时由子类继承下来。
// 定义父类
function Animal(age) {this.age = age;
}// 定义子类
function Cat(age) {// 在子类生成实例时,传递参数给 call(),间接地传递给父类,然后被子类继承Animal.call(this, age);
}const cat = new Cat(100);
console.log(cat.age); // 100
3️⃣ 可以实现多继承
在子类的构造函数中,可以通过多次调用call()
方法来继承多个父对象,每调用一次call()
方法就会将父类的实例的属性和方法绑定到子类的this
中。
// 定义第一个父类
function Animal(name) {this.name1 = name;
}// 定义第二个父类
function Car(name) {this.name2 = name;
}// 定义第三个父类
function Person(name) {this.name3 = name;
}// 定义子类
function Cat(name1, name2, name3) {Animal.call(this, name1);Car.call(this, name2);Person.call(this, name3);
}const cat = new Cat("卡菲猫", "兰博基尼", "钢铁侠");
console.log(cat.name1); // "卡菲猫"
console.log(cat.name2); // "兰博基尼"
console.log(cat.name3); // "钢铁侠"
缺点:
1️⃣ 实例只是子类的实例,并不是父类的实例
因为没有将子类与父类进行关联起来,所以生成的实例与父类并没有关系,这样就失去了继承的意义。
const cat = new Cat();
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // false
2️⃣ 只能继承父类的属性和方法,并不能继承原型对象上的属性和方法。
3️⃣ 无法复用父类的方法
由于父类的方法将通过call()
绑定到子类的this
中,因此子类生成的每一个实例都会拥有自己的方法,这会造成不必要的内存消耗,影响性能。
const cat1 = new Cat();
const cat2 = new Cat();console.log(cat1.sleep === cat2.sleep); // false
复制继承
复制继承的主要思想是首先生成父类的实例,然后通过for...in
遍历父类实例的属性和函数,并将其依次设置为子类实例的属性和函数或者原型对象上的属性和方法。
function Cat(name = "卡菲猫") {const animal = new Animal();for (key in animal) {if (animal.hasOwnProperty(key)) {this[key] = animal[key];} else {Cat.prototype[key] = animal[key];}}
}const cat = new Cat();console.log(cat.name); // "Tom"
console.log(cat.age); // 100
cat.sleep(); // Tom正在睡觉
cat.eat(); // Tom正在吃苹果
复制继承的优缺点
优点:
1️⃣ 支持多继承
只需要在子类的构造函数中生成多个父类的实例,然后通过相同的for...in
处理即可。
2️⃣ 能同时继承实例的属性和方法与父类的原型对象上的属性和方法
因为对所有的属性进行遍历时,会通过hasOwnProperty()
判断其是实例的属性和方法还是原型对象上的属性和方法,并根据结果进行不同的设置,从而既能继承实例的属性和方法又能继承原型对象上的属性和方法。
3️⃣ 可以向父类构造函数中传值
在生成子类的实例时,可以先把值传给子类的构造函数,然后在子类构造函数中,再将值传递给父类的构造函数。
function Cat(name) {const animal = new Animal(name);
}const cat = new Cat("汤姆");
缺点:
1️⃣ 父类的所有属性都需要复制,消耗内存。
对于父类的所有属性值都需要复制一遍,这会造成内存的重复利用,降低性能。
2️⃣ 实例只是子类的实例,并不是父类的实例。
实际上只是通过遍历父类的属性和方法并将其复制到子类上,并没有通过原型对象串联起父类和子类,因此子类的实例并不是父类的实例。
const cat = new Cat();console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // false
组合继承
组合继承的主要思想是结合了构造继承和原型继承两种方法,一方面在子类的构造函数中通过call()
函数调用父类的构造函数,将父类的实例的属性和方法绑定到子类的this
中,另一方面,通过改变子类的prototype
属性,继承父类的原型对象上的属性和方法。
// 子类;
function Cat() {// 构造继承Animal.call(this);
}// 原型链继承
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;const cat = new Cat();console.log(cat.name); // "Tom"
console.log(cat.age); // 100
cat.sleep(); // "Tom正在睡觉"
cat.eat(); // "Tom正在吃苹果"
组合继承的优缺点
优点:
1️⃣ 既能继承父类的实例的属性和方法,又能继承父类原型对象的属性和方法。
2️⃣ 即是子类的实例,又是父类的实例。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
3️⃣ 不存在引用属性共享的问题。
4️⃣ 可以向父类的构造函数中传递参数
通过call()可以向父类的构造函数中传递参数。
缺点:
1️⃣ 组合继承的缺点为父类的实例的属性和方法会绑定 2 次
在子类的构造函数中,通过call()
函数调用了一次父类的构造函数,在改写子类的prototype
属性,生成父类的实例时调用了一次父类的构造函数。
寄生组合继承
此继承方法是针对组合继承的优化。组合继承在进行子类的prototype
属性的设置时,可以去掉父类实例的属性和方法。
// 子类;
function Cat(name = "卡菲猫") {// 构造继承Animal.call(this);
}(function () {// 声明任意函数 Super()var Super = function () {};// 关键语句:Super() 函数的原型指向父类 Animal 的原型,去掉父类的实例的属性和方法Super.prototype = Animal.prototype;Cat.prototype = new Super();Cat.prototype.constructor = Cat;
})();const cat = new Cat();console.log(cat.name); // "Tom"
console.log(cat.age); // 100
cat.sleep(); // "Tom正在睡觉"
cat.eat(); // "Tom正在吃苹果"
参考
点击跳转
JS 实现继承的 5 种方式相关推荐
- (转)js实现继承的5种方式
js是门灵活的语言,实现一种功能往往有多种做法,ECMAScript没有明确的继承机制,而是通过模仿实现的,根据js语言的本身的特性,js实现继承有以下通用的几种方式 1.使用对象冒充实现继承(该种实 ...
- JavaScript(js)实现继承的几种方式
1.原型链继承 核心:将父类的实例做为子类的原型对象 //动物类function Animal(name,sex) {this.name = name || 'Animal';this.sex = s ...
- 面试--js实现继承的几种方式
基于原型的继承 function father() {this.faName = 'father';this.names=['11','22']}father.prototype.getfaName ...
- JS 总结之原型继承的几种方式
在之前的总结中,我们详细分析了原型<JS 总结之原型>,原型很大作用用于模拟继承,这一次,我们来聊原型继承的几种方式. function Person (age) {this.age = ...
- js中继承的几种用法总结(apply,call,prototype)
本篇文章主要介绍了js中继承的几种用法总结(apply,call,prototype) 需要的朋友可以过来参考下,希望对大家有所帮助 一,js中对象继承 js中有三种继承方式 1.js原型(proto ...
- 继承有几种方式,分别是什么,想要实现继承可以使用哪些方法
这里是修真院前端小课堂,每篇分享文从 [背景介绍][知识剖析][常见问题][解决方案][编码实战][扩展思考][更多讨论][参考文献] 八个方面深度解析前端知识/技能,本篇分享的是: [继承有几种方式 ...
- 可以实现继承的几种方式
继承的几种方式 说起继承,又是一个老生常谈的问题了.今天来讲讲继承的几种方法以及他们的优缺点吧. 源码地址:点击这里 一.原型链继承 原型链继承:通过原型将一个引用类型继承另一个引用类型的属性和方法. ...
- JS数组遍历的几种方式
JS数组遍历的几种方式 JS数组遍历,基本就是for,forin,foreach,forof,map等等一些方法,以下介绍几种本文分析用到的数组遍历方式以及进行性能分析对比 第一种:普通for循环 代 ...
- Django中Model继承的三种方式
Django中Model继承的三种方式 Django中Model的继承有三种: 1.抽象继承 2.多表继承 3.proxy model(代理model) 1.抽象继承 第一种抽象继承,创建一个通用父类 ...
最新文章
- 搭建yum源,配置yum源
- 用于文档上下文感知推荐的卷积矩阵分解
- PHP常量PHP_SAPI与函数php_sapi_name()简介,PHP运行环境检测
- CABR:Beamer的内容自适应速率控制算法
- Android系统性能优化(60)---LeakCanary使用详解
- 50-20-190-配置-Flink 配置文件详解-flink-conf.yaml
- Android记录日志方式,关于Android中处理崩溃异常和记录日志的另一种实现思路
- 【最短路】Walls
- Android开发指南(32) —— Multimedia and Camera - Audio Capture
- 2. vi 简介(2)
- java html报表设置_快逸报表中html事件属性的使用
- LoadRunner压力测试:详细操作流程
- LTE技术对PTN的影响
- 快速学习探索性测试,什么是探索性测试?
- Microsoft Edge导出浏览历史记录
- android手机什么架构图,从架构图看Android分为几层呢?
- 深度学习中number of training epochs中的,epoc h到底指什么?
- 借鉴FCoin商业模式,写了一份白皮书去参赛
- Enumerating Trillion Triangles on Distributed Systems
- 市场营销策划书的设计与撰写
热门文章
- FL STUDIO 最新20.9汉化中文版软件更新
- SQLServer证书过期,重做证书
- JavaScript练手题 原生制作论坛发帖工具
- HTML 模仿百度首页 (html+css)
- 基于node.js开发的聚美酒店住宿管理系统的设计(论文设计word文档)
- 如何用ChatGPT进行剧本/广告脚本写作?
- [python]天地地图瓦片下载
- 小米、金山、猎豹、YY一起崩塌:“雷军帝国”何去何从?
- 安卓zip解压软件_啥格式都能解压的神器,大神必装中文解压码都不怕
- 四针手表指的是什么_欧米茄碟飞系列手表四针用途