es5与es6以及Typescript中类的创建与继承

457 阅读6分钟

es5与es6以及Typescript中类的创建与继承

闲来无事,准备总结一下js中类的创建相关知识。说到js中的类相关知识,自然不能没有原型链相关知识点,所以不懂原型链的小伙伴,建议先去学习一波原型链相关知识点。

es5中创建类与类的继承

创建Person类

//创建Person类构造函数,即以构造函数的方式创建Javascript对象
function Person(name){
	this.name = name
	this.sayname=function(){
		console.log('my name is '+this.name)
	}
}
//Person类原型上添加say方法
Person.prototype.say = function(){
	console.log('my name is '+this.name)
}
//Person类添加静态方法fnc
Person.fnc = function(){
	console.log('我是静态方法,只在构造函数上调用')
}
var aliang = new Person('aliang')
Person.fnc()        //我是静态方法,只在构造函数上调用
aliang.say()        //my name is aliang
aliang.sayname()    //my name is aliang
console.log(aliang instanceof Person)  //true

  在Person类的创建中我们设置了三个方法原型方法say和实例方法sayname以及一个静态方法fnc,可以看到fnc是只在构造函数上调用的,是访问不到实例的。另外两个方法执行中都能访问到当前实例aliangname属性,那他们之间到底有区别呢

var person1 = new Person('aming')
var person2 = new Person('ali')
console.log(person1.say === person2.say)    //true
console.log(person1.sayname === person2.sayname)    //false

  从上面代码的执行结果,我们可以看到,构造函数内的方法,在不同实例中是不一样的,也就是其指向的内存中的sayname函数是不一样的。而say方法是一样的,也就是person1和person2上的say方法指向同一个函数。   其实这是因为当我们通过Person创建这两个实例时,在构造函数内的代码分别执行了两次,从而创建了两个不同sayname方法,而say方法是定义在原型对象上,而我们知道当构造函数创建一个实例时,会让实例对象的_proto_指向构造函数的原型对象prototype,所以person1与person2的_proto_指向同一个对象,所以它们的say方法是一样的,只是方法中访问到的this肯定不一样了,所以在开发中我们可以合理利用这三种方法来实现我们的需求。

继承Person类

此处介绍ES5中常用的组合式继承

//创建Person类构造函数,即以构造函数的方式创建Javascript对象
function Person(name){
	this.name = name
	this.sayname=function(){
		console.log('my name is '+this.name)
	}
}
//Person类原型上添加say方法
Person.prototype.say = function(){
	console.log('my name is '+this.name)
}
Person.fnc = function(){
	console.log('我是静态方法,只在构造函数上调用')
}
//创建LIli类
function LIli(name,age){
	Person.call(this,name)   //执行Person构造函数,使LIli继承到Person内的属性
	this.age = age
}
LIli.prototype = new Person()  //让LIli的原型对象的_proto_属性指向Person的原型对象,来继承Person的原型对象
LIli.prototype.constructor = LIli   //使LIli的原型对象的constructor属性指向自身

LIli.prototype.sayall = function(){
	console.log('这是 '+this.name+',他'+this.age+'岁')
}
var lili = new LIli('lili',18)
LIli.fnc()       //LIli.fn is not a function
lili.sayall()    //这是 lili 他18岁
lili.say()       //my name is lili
lili.sayname()     //my name is lili

  可以看到这种继承方式中,LIli类继承了Person类中的方法与属性,但是没有继承Person的静态方法fnc; 这就是es5中类的创建与继承了,基本都是直接操作原型链来进行的,下面继续介绍ES6中类的创建与继承。

es6中创建类与类的继承

创建Person类

  es6中通过class关键字来创建类,extends关键字来继承类,其底层原理与es5一样,也是基于原型链,该实现方法只是一种语法糖。

class Person{
	constructor(name) {
	    this.name = name
		this.sayname=function(){
			console.log('my name is '+this.name)
		}
	}
	say(){
		console.log('my name is '+this.name)
	}
	static fnc(){
		console.log('我是静态方法,只在构造函数上调用')
	}
}
var aliang = new Person('aliang')
Person.fnc()        //我是静态方法,只在构造函数上调用
aliang.say()        //my name is aliang
aliang.sayname()    //my name is aliang
console.log(aliang instanceof Person)  //true

  此处创建了一个与es5中相同的一个Person类,可以看到es5中写在函数中的内容放到了constructor中,原型对象上的方法直接写在类里面,而类上的静态方法是在函数名之前写了一个static关键字。

继承Person类

class LIli extends Person{
	constructor(name,age) {
		super(name)
	    this.age = age
	}
	sayall(){
		console.log('这是 '+this.name+',他'+this.age+'岁')
	}
}
var lili = new LIli('lili',18)
LIli.fnc()       //我是静态方法,只在构造函数上调用
lili.sayall()    //这是 lili 他18岁
lili.say()      //my name is lili
lili.sayname()   //my name is lili

  可以看到结果与上面es5的继承结果相差不大,唯一的区别就是fnc方法被LIli类继承下来了,也就是es6中类的继承会将静态方法也继承下来。

  而且此处构造函数中多了super(),super函数的作用类似es5继承中调用Person函数一样,super()需要传入创建子类实例时传入的父类构造函数的参数,就算不需要给继承的实例属性传值,也需要在constructor首部调用一次super()否则后面的语句不能使用this,当然如果不需要定义实例属性,可以直接不写constructor函数。

Typescript中创建类与类的继承

创建Person类

class Person{
	private sex:string      //允许在当前类内部调用
	protected height:number  //允许在类内及继承的子类中使用
	public name:string
	public sayname:()=>void;
	constructor(name:string){
		this.name = name
		this.sayname=function(){
			console.log('my name is '+this.name)
		}
	}
	//constructor(public name:string){ } //可省略为此种写法
	public say(){
		console.log('我的名字是'+this.name+'我的技能')
		this.play1()
		this.play2()
	}
	private play1(){    //允许在当前类内部调用
		console.log('打篮球')
	}
	protected play2(){   //允许在类内及继承的子类中使用
		console.log('踢足球')
	}
	static fnc(){
		console.log('我是静态方法,只在构造函数上调用')
	}
}
var aliang = new Person('aliang')
Person.fnc()        //我是静态方法,只在构造函数上调用
aliang.play1() //编译报错 Property 'play1' is private and only accessible within class 'Person'
aliang.say()        //我的名字是aliang我的技能    打篮球 踢足球
aliang.sayname()    //my name is aliang
console.log(aliang instanceof Person)  //true

  这样我们就在TS中创建了一个Person类,可以看到这里多了很多es6中没有的修饰词,用来定义访问类型public,private,protected,写在类中的无论属性还是方法,没有加这三种修饰词的都是默认public,private指该属性或方法只能在当前类中使用,protected指该属性或方法只能在当前类中与其子类中使用;

  此处需要注意的是,与ES6不同,在TS构造函数constructor中使用this访问的属性必须要先在类顶部定义,或者在构造函数的参数中定义,否则也会导致报错。

继承Person类

class LIli extends Person{
	constructor(name:string,public age:number,private _sex:string){
		super(name)
	}
	public sayall(){
		console.log('这是 '+this.name+',他'+this.age+'岁,他的性别是'+this._sex)
		this.play2()
	}
}
let aming = new LIli('lili',100,'男')
aming.sayall()    //这是 aming,他100岁,他的性别是男; 踢足球
aming.play2()   //编译报错 Property 'play2' is protected and only accessible within class 'Person' and its subclasses.
console.log(aming._sex)     //编译报错  Property '_sex' is private and only accessible within class 'Man'

  这样就使LIli类继承了Person类,从运行结果可以看到,private定义的属性,只能在类里面使用,不可以在实例上使用,父类中protected定义的方法可以在子类中调用,不可以在子类实例上调用。

  而且此处constructor函数我们也是使用了相比于定义Person类时更简洁的写法,也是比较常用的写法。

这样关于es5与es6以及Typescript中类的创建与继承总结就结束了,其实在js中,不管用什么写法,他的底层都是不变的,都是基于原型链来进行的,所以弄懂Javascript原型链很重要。

第一次写,如果有不对的地方,请指出,多谢【抱拳】