前言
ES6时代的来临,使得类继承变得如此的圆滑。但是,你有思考过ES6的类继承模式吗?如何去实现它呢?
类继承对于JavaScript来说,实现方式与Java等类语言大不相同。熟悉JavaScript的开发者都清楚,JavaScript是基于原型模式的。那么,在es6没有出来之前,js是如何继承的呢?这是一个非常有意思的话题。希望带着疑问看文章,或许对你的提升会更加巨大。如果你喜欢我的文章,欢迎评论,欢迎Star~。欢迎关注我的github博客
正文
让我来——构造函数
其实,js模拟一个类的方式非常的简单——构造函数。或许,这是所有人都在普遍使用的方式。我们先来看一个例子:
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log(this.name);
}
const person = new Person('zimo');
person.sayName(); //zimo
这里通过构造函数模拟出来的类,其实和其他语言的类行为上是基本一致的,唯一的区别就是它不具备私有方法。而且,他们同样是通过new操作符来进行实例化的,但是js的new操作与其它语言的new操作又会有所不同。比方说:
const person = Person('zimo');
console.log(person) //undefined
构造函数前面没有加new的情况下,会导致这个对象没有返回值来进行赋值。但是,在类语言中,在类名前不使用new是会报错的。
下面,我们应该进一步来看一下js的new操作符的原理,以及实现。
你懂我——new操作符
new方法原理:
- 创建一个新的对象
- 将对象的proto指向构造函数的原型
- 调用构造函数
- 返回新对象
js代码实现部分:
const person = new Person(args);
//相当于
const person = Person.new(args);
Function.prototype.new = function(){
let obj = new Object();
obj.__proto__ = this.prototype;
const ret = this.apply(obj, arguments);
return (typeof ret == 'object' && ret) || obj;
}
到此为止,js如何去模拟类,我们已经讲述完了。接下来,我们应该看一下如何去实现类似与其他语言的类继承模式。
尽管ES6已经对extends关键词进行了实现,但是原理性的知识,我们应该需要明白。
初印象——类继承
先来看一个场景,无论是狗或者是猫,它们都有一个共同的类animal,如图:
在真实开发中,我们必须去实现类与类之间的继承关系,不然的话,我们就必须重复地去命名构造函数(这样的方式是丑陋的)。
所以,像上述的场景,开发过程中多的数不胜数,但是本质都是不变的。接下来,那我们以一个例子来做说明,并且明白大致是如何去实现的。
例子:我们需要去构造一个交通工具类,该类具备属性:轮子、速度和颜色(默认为黑),它还具备方法run(time)返回距离。之后,我们还需要去通过该类继承一个‘汽车’类和一个‘单车’类。
如图:
实现:
function Vehicle(wheel, speed){ //首先构造一个交通工具类
this.wheel = wheel;
this.speed = speed;
this.color = 'black';
}
Vehicle.prototype.run = function(time){ //在它的原型上定义方法
return this.speed * time;
}
function Car(wheel, speed, brand){ //通过在汽车类中去调用父类
Vehicle.call(this, wheel, speed);
this.brand = brand;
}
Car.prototype = new Vehicle(); //将汽车类的原型指向交通工具的实例
function Bicycle(wheel, speed, owner){ //同样,构造一个自行车类,在其中调用父类
Vehicle.call(this, wheel, speed);
this.owner = owner;
}
Bicycle.prototype = new Vehicle(); //将其原型指向交通工具实例
const car = new Car(4, 10, 'baoma');
const bicycle = new Bicycle(2, 5, 'zimo');
console.log(car.run(10)); //100
console.log(bicycle.run(10)); //50
这样子,就实现了类的继承。
大致的思路是:在继承类中调用父类,以及将继承类的原型赋值为父类的实例。
但是,每次实现如果都是这样子的话,又会显得非常的累赘,我们并没有将可以重复使用的部分。因此,我们需要将不变的部分进行封装,封装成一个方法,然后将可变的部分当中参数传递进来。
接下来,我们来分析一下类继承的封装方法extend。
真实的我——继承封装
首先,我们来看一下,我们需要实现怎样的继承:
function Animal(name){ //构造一个动物类
this.name = name;
}
Animal.prototype.sayName = function(){
console.log('My name is ' + this.name);
}
/**
extends方法其中包含子类的constructor、自身的属性、和来自父元素继承的属性
*/
var Dog = Animal.extends({ //使用extends方法来实现类的封装
constructor: function(name, lan){
this._super(name);
this.lan = lan
},
sayname: function(){
this._super.sayName();
},
sayLan: function(){
console.log(this.lan);
}
});
var animal = new Animal('animal');
var dog = new Dog('dog', '汪汪汪');
animal.sayName(); //My name is animal
dog.sayName(); // My name is dog
dog.sayLan(); // '汪汪汪'
其中的extend方法是我们需要去实现的,在实现之前,我们可以来对比一下ES6的语法
class Animal {
constructor(name){
this.name = name;
}
sayName(){
console.log('My name is ' + this.name);
}
}
/**
对比上面的extend封装和es6的语法,我们会发现,其实差异并没有太大
*/
class Dog extends Animal{
constructor(name, lan){
super(name);
this.lan = lan;
}
sayName(){
super.sayName();
}
sayLan(){
console.log(this.lan);
}
}
其实,很多地方是相似的,比方说super和this._super。这个对象其实是看起来是父构造函数,因为他可以直接调用this._super(name),但它同时还具备父构造函数原型上的函数,因此我们可以把它称为父包装器。但是,必须保证的是_super中的函数对象上下文必须都是指向子构造函数的。
使用一张简陋的图来表示整个关系的话,如图:
下面我们来实现一下这个extend方法。
Function.prototype.extend = function(props){
var Super = this;
var Temp = function(){};
Temp.prototype = Super.prototype;
var superProto = new Temp(); //去创建一个指向Super.prototype的实例
var _super = function(){ //创建一个父类包装器
return Super.apply(this, arguments);
}
var Child = function(){
if(props.constructor){
props.constructor.apply(this, arguments);
}
for(var i in Super.prototype){
_super[i] = Super.prototype[i].bind(this); //确保Super的原型方法拷贝过来时,this指向Child构造函数
}
}
Child.prototype = superProto; //将子类的原型指向父类的纯实例
Child.prototype._super = _super; //构建一个引用指向父类的包装器
for(var i in props){
if( i !== 'constructor'){
Child.prototype[i] = props[i]; //将props中方法放到Child的原型上面
}
}
return Child;
}
总结
继承的一些内容就分析到这里。其实,自从ES6标准出来之后,类的继承已经非常普遍了,因为真心好用。但是,也是越来越有人不懂得如何去理解这个继承的原理了。其实ES6中的继承的实现,也是挺简单的。
如果你对我写的有疑问,可以评论,如我写的有错误,欢迎指正。你喜欢我的博客,请给我关注Star~呦。大家一起总结一起进步。欢迎关注我的github博客