为什么要继承
举个例子,你有一个class A(如下),同时你还有class B,class C,class D,......,他们有一个共同的特点就是,都具有属性name
,age
,以及toString()
方法。你此时的做法,方法一:剩下的每个类中都分别写这两个属性,以及toString
方法,但是当toString
的连词符改为/
时,你不得不去修改每个类的toString
方法;方法二:让其他的类都去继承A
,这样toString
这个方法就只在一个地方声明,并且变化的时候,只需修改一个地方即可。
class A {
name: string;
age: int;
toString() {
return `${this.name}——${this.age}`;
}
}
/*
方法一
*/
class B {
name: string;
age: int;
toString() {
return `${this.name}——${this.age}`;
}
}
// ...
/*
方法二
*/
class B extends A {
// ....
}
class C extends A {
// ...
}
// ...
类式继承
JavaScript也可以像其他类式继承的语言一样,通过函数声明类,用new
创建实例,例如:
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
// 使用
const person = new Person('Tom');
person.getName();
原型链
function A(name, books) {
Person.call(this, name); // 调用Person构造函数
this.books = books;
}
A.prototype = new Person();
A.prototype.constructor = A;
A.prototype.getBooks = function() {
return this.books;
}
- 创建一个构造函数。在构造函数中调用超类的构造函数,并传参
- 使用
new
运算符:创建一个空对象;调用构造函数,空对象处于作用域链的最前端 - 设置原型链
原型式继承
用类的方法创建对象:首先用一个雷的声明定义对象的结构;然后实例化该类创建一个新对象。用这种方式创建的对象都有一套该类的所有实例属性的副本(每个实例方法都只存在一份)。
而原型继承不需要用类来定义对象的结构,直接创建一个对象即可。该对象被称为原型对象,因为他为其他对象应用的模样提供了一个原型。
const Person = {
name: 'name',
getName: function() {
return this.name;
}
}
const reader = clone(Person);
console.log(reader.getName); // name
reader.name = 'Tom';
console.log(reader.getName); // Tom
clone
函数可以用来创建新的类Person
的对象,该对象的原型对象为Person
。这意味在这个新对象中查找某个方法或属性时,如果找不到,那么查找过程会在其原型对象中继续进行
const A = clone(Person);
A.books = [];
A.getBooks = function() {
return this.books;
}
然后我们还可以重新定义该clone
中的方法和属性。修改在Person
中提供的默认值,或者添加新的属性和方法。
clone
在前面肯定纠结于clone
里面究竟做了什么,且看下面:
function clone(object) {
function F() {};
F.prototype = object;
return new F;
}
- 创建空函数
F
- 将其
prototype
属性设置为作为参数object
传入的原型对象 - 然后把新对象作为返回值返回
在第二步中,prototype
属性就是通过原型链机制用来指向原型对象的,提供了所有继承而来的成员的链接。
掺元类
先创建一个包含各种 通用方法的类,然后用它扩充其他类。其中这个通用方法类是掺元类。
//定义Teacher类
var Teacher=function(){
this.name='T';
this.age=25;
};
//定义Student类
var Student=function(){
this.name='S';
this.age=1;
};
//定义掺元类
var Mixin=function(){};
//定义一个方法,只是序列化除function和object类的所有类型
Mixin.prototype.serialize=function(){
var serializes=[];
for(key in this){
serializes.push(this[key]);
}
return serializes.join(", ");
};
/**
将掺元类的所有方法扩充给接收类
*/
function augment(receivingClass,givingClass){
for(methodName in givingClass.prototype){//如果已有同名方法则略过
if(!receivingClass.prototype[methodName]){
receivingClass.prototype[methodName]=givingClass.prototype[methodName];
}
}
};
//扩充Teacher类
augment(Teacher,Mixin);
//扩充Student类
augment(Student,Mixin);
//初始化Teacher类
var t=new Teacher();
//初始化Student类
var s=new Student();
//序列化Teacher
alert(t.serialize());//结果:T,25
//序列化Student
alert(s.serialize());//结果:S,1
上面的例子中,serialize
遍历this
所有的成员
augment
遍历掺元类的所有方法,并判断接受类是否有同名成员,没有则添加,有则跳过
继承适合场景
- 建立父子继承关系,继承的方法只需定义一次即可。需要修改的时候,也只要修改一个位置
- 内存效率比较重要的场合原型式继承;开发者熟悉其他面向对象语言中的继承,使用类式继承;类之间差异较大,用掺元类中的方法来扩充。
结语
- 类式继承是模仿Java等面向对象语言中类继承方式,适合于内存效率要求不高或开发人员不熟悉原型式继承的场合
- 原型继承工作机制:先创建一些对象然后再对其进行clone,从而得到创建子类和实例。因为这种方式会共享未被改写的属性和方法,所以创建的对象往往有较高的内存效率
- 掺元类:提供一个既能让对象和类共享一些方法又不需要让它们成为父子关系的途径。这个方式在于,让彼此有着较大差异的类共享一些通用方法
网友评论