在ES5及以前版本,是没有真正意义上类的概念,只是通过构造函数来模拟类的运用。尽管JS脚本语言是一门解释型弱语言,对类的需求意义不大,但ES6还是引入类的实现,让JS真正够上面向对象编程门槛,尽管对类的功能实现还不全面、彻底,但基本面向对象编程功能具有了。
一、ES5中的近类结构
由构造函数实现的近类能够对功能代码进行抽象定义与封装,实现代码重复运用与安全性,具有部分类的作用。但还不能算真正意义的类,因为它只有实例化运用功能,不具有类的直接继承与派生(只能通过间接方法),不能够对方法进行重写与多态、重载等表现。
function PersonType(name){//定义构造函数
this.name=name;
}
PersonType.prototype.sayName=function(){//通过原型方法,给构造“类”添加方法
console.log(this.name);
}
var person=new PersonType("张小玉");//实例化一个构造“类”
person.sayName(); //输出结果:张小玉
console.log(person instanceof PersonType);//输出结果:true
console.log(person instanceof Object);//输出结果:true
二、ES6中实现真正的类结构
(一)类的声明
class PersonClass{//或者写成:let PersonClass=class{
//等价于构造函数
constructor(name){
this.name=name;
}
//等价于PersonType.prototype.sayName
sayName(){
console.log(this.name);
}
}
let person=new PersonClass("张小玉");
person.sayName();//输出结果:张小玉
console.log(person instanceof PersonClass);//输出结果:true
console.log(person instanceof Object);//输出结果:true
console.log(typeof PersonClass);//输出结果:function
console.log(typeof PersonClass.prototype.sayName);//输出结果:function
从上可以看出,JS类的结构从本质上看还是函数,说明ES6的JS类还是对以前构造函数的包装,只是被打上了真正类的一些行为标签。
(二)为何要在ES6中引入类语法
1.以前的函数(包括构造函数)是要在作用域中被提升,而ES6的类不会被提升,它会处于临时死区中,也let定义一样,可以克服各种冲突与安全性问题。
2.类声明中所有代码将自动运行在严格模式下,而且无法强化让代码脱离严格模式。
3.构造函数的方法不可枚举,而类方法可以枚举。
4.在类中,如果调用没有通过[[construct]]定义过的方法会报错,而构造函数会默认采用新增方法,不会产生调用错误,这可能会造成较多误解。
三、ES6中类的功能实现
(一)静态成员实现
1.构造函数的静态成员实现
function PersonType(name){//定义构造函数
this.name=name;
}
//静态方法
PersonType.crate=function(name){
return name;
}
//实例方法
PersonType.prototype.sayName=function(){
console.log(this.name);
}
var person=PersonType.crate("张小玉");//静态方法必须直接访问,不能在实例对象中访问
console.log(person);//输出结果:张小玉
2.ES6中类的静态成员实现
class PersonClass{
//等价于构造函数
constructor(name){
this.name=name;
}
//等价于PersonType.prototype.sayName
sayName(){
console.log(this.name);
}
//等价于PersonType.create
static creat(name){
return name;
}
}
let person=PersonClass.creat("张小玉");//静态方法必须直接访问,不能在实例对象中访问
console.log(person); //输出结果:张小玉
(二)继承与派生
1.定义
class Rectangle{//定义父类
constructor(length,width){
this.length=length;
this.width=width;
}
getArea(){
return this.length*this.width;
}
}
class Square extends Rectangle{//定义子类
constructor(length){//继承派生方法
super(length,length);//通过super方法访问父类构造方法
}
}
var square=new Square(3);
console.log(square.getArea());//输出结果:9
console.log(square instanceof Square);//输出结果:true
2.使用super()方法的注意事项
(1)只可在派生类的构造函数中使用super(),如果尝试在非extends声明的类使用会抛出错误,反之如果在继承派生类中不使用super()方法继承父类,则系统会自动派生super(…args),将父类构造方法继承过来。
(2)在构造函数中访问this之前一定要调用super(),它负责初始化this,否则将出错。
(3)如果不想调super(),唯一的方法是让类的构造函数返回一个对象。
(三)类方法的遮蔽与重写
继承子类会自动遮蔽父类方法,需要进行方法的重写,因此在实例中就不会直接调用父类方法,如果需要调用,则需要在重写方法中用super()进行调用。
class Rectangle{//定义父类
constructor(length,width){
this.length=length;
this.width=width;
}
getArea(){
return "父类中抽象方法,未实现";
}
}
class Square extends Rectangle{//定义子类
constructor(length){//派生方法
super(length,length);//通过super方法访问父类构造方法
}
//覆盖并遮蔽Rectangle的geArea()方法
getArea(){
return this.length*this.width;//在使用this之前,必须调用super
}
}
var square=new Square(3);
console.log(square.getArea());//输出结果:是9,而不是父类中的getArea()方法的输出"父类中抽象方法,未实现"
如果要想在子类中使用父类的方法,则需要在重写方法中调用super()方法,如下
class Rectangle{//定义父类
constructor(length,width){
this.length=length;
this.width=width;
}
getArea(){
return "父类中抽象方法,未实现";
}
}
class Square extends Rectangle{//定义子类
constructor(length){//派生方法
super(length,length);//通过super方法访问父类构造方法
}
//覆盖并遮蔽Rectangle的geArea()方法
getArea(){
return super.getArea();
}
}
var square=new Square(3);
console.log(square.getArea());//输出结果:父类中抽象方法,未实现
(四)静态成员继承
class Rectangle{//定义父类
constructor(length,width){
this.length=length;
this.width=width;
}
getArea(){
return this.length*this.width;
}
static create(length,width){
return new Rectangle(length,width);
}
}
class Square extends Rectangle{//定义子类
constructor(length){//派生方法
super(length,length);//通过super方法访问父类构造方法
}
//覆盖并遮蔽Rectangle的geArea()方法
getArea(){
return super.getArea();
}
}
var rect=Square.create(3,4);//因为是静态方法,直接调用,不能实例化中调用
console.log(rect.getArea());//输出结果:12