js中常见的几种经典的对象继承方式:
在前端开发中会提到继承,那什么是继承?什么是对象继承?它有什么作用?如何实现继承?
前言:在之前学习面向对象语言时候会发现有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。
但是在JavaScript中没有类的概念,它的对象也与基于类的对象有所不同。实际上,JavaScript语言是通过一种叫做原型(prototype)的方式来实现面向对象编程的。
继承: 大白话就是后代实例化的方法可以父类型已有的属性和方法。
对象继承: 就是继承的个体是以对象作为单位。
作用: 那实现继承有一个最大的好处就是子对象可以使用父对象的属性和方法,从而简化了一些代码。
js中常见的几种经典的对象继承方式:
- 原型链继承
- 借用构造函数的继承
- 组合继承(使用频率较高
一、原型链继承
核心思想: 子类型的原型为父类型的一个实例对象
基本做法:
- 首先定义父类型构造函数
- 给父类型的原型添加方法
- 再定义子类型的构造函数
- 创建父类型的对象赋值给子类型的原型
- 将子类型原型的构造属性设置为子类型
- 给子类型原型添加方法
- 创建子类型的对象: 可以调用父类型的方法
// 首先定义父类型构造函数
function fatherType() {
this.fatherProp = 'father property';
}
// 给父类型的原型添加方法
fatherType.prototype.showfatherProp = function () {
console.log(this.supProp);
};
// 定义子类型的构造函数
function SubType() {
this.subProp = 'Sub property';
}
// 创建父类型的对象赋值给子类型的原型
fatherType.prototype = newfather1Type();
// 将子类型原型的构造属性设置为子类型
fatherType.prototype.constructor = SubType;
// 给子类型原型添加方法
SubType.prototype.showSubProp = function () {
console.log(this.subProp)
};
// 创建子类型的对象: 可以调用父类型的方法
var subType = new SubType();
subType.showfather1Prop();
subType.showSubProp();
但是这样的继承模式也有缺点:
1、原型链继承多个实例的引用类型属性指向相同,一个实例修改了原型属性,另一个实例的原型属性也会被修改
2、不能传递参数
3、继承单一
二、借用构造函数继承
核心思想: 使用.call()和.apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类
基本做法:
- 首先定义父类型构造函数
- 定义子类型的构造函数
- 给子类型的原型添加方法
- 创建子类型的对象然后调用
// 定义父类型构造函数
function SuperType(name) {
this.name = name;
this.showSupperName = function () {
console.log(this.name);
};
}
// 定义子类型的构造函数
function SubType(name, age) {
// 在子类型中调用call方法继承自SuperType
SuperType.call(this, name);
this.age = age;
}
// 给子类型的原型添加方法
SubType.prototype.showSubName = function () {
console.log(this.name);
};
// 创建子类型的对象然后调用
var subType = new SubType("孙悟空", 20);
subType.showSupperName();
subType.showSubName();
console.log(subType.name);
console.log(subType.age);
该方法逻辑感比较好但是也有一些缺点:
只能继承父类的实例属性和方法,不能继承原型属性和方法
无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿
三、组合继承(五星级重要)
核心思想: 原型链+借用构造函数的组合继承
基本做法:
- 利用原型链实现对父类型对象的方法继承
- 利用super()借用父类型构建函数初始化相同属性
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
};
function Student(name, age, price) {
Person.call(this, name, age); // 为了得到父类型的实例属性和方法
this.price = price; // 添加子类型私有的属性
}
Student.prototype = new Person(); // 为了得到父类型的原型属性和方法
Student.prototype.constructor = Student; // 修正constructor属性指向
Student.prototype.setPrice = function (price) {
// 添加子类型私有的方法
this.price = price;
};
var s = new Student("赵子龙", 24, 15000);
console.log(s.name, s.age, s.price);
s.setName("赵云");
s.setPrice(16000);
console.log(s.name, s.age, s.price);
该模式的缺点:
父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性和方法 。