写在前面
- 本篇文章旨在让读者快速入门ES6中关于类的相关运用,关于在ES5中如何实现近类结构等问题不在此过多讲解。
- 默认读者掌握关于面向对象的基本知识,并了解其他面向对象语言中的类的概念。
类的声明
- 基本语法
要声明一个类,首先打出class关键字,然后是类的名字,后面的部分跟声明一个对象类似,但是并不需要用 逗号 隔开各个部分。
举个栗子:
class FoodClass{
//构造函数,constructor是保留字,constructor构造函数是可以缺省的
constructor(name){
this.name = name;
}
showName(){
console.log(this.name);
FoodClass = 1;//报错,不能在类中修改类名所指向的内容
}
}
let food = new FoodClass("炒米粉");
food.showName();//输出"炒米粉"
console.log(food instanceof FoodClass);//true
console.log(typeof FoodClass);//"function"
在类的声明语法里面,有且仅有一个保留方法名 constructor ,也就是我们熟悉的构造函数,这个构造函数是可以缺省的。
值得一提的是,构造函数中创建的属性是私有属性,因此我们可以十分方便地在构造函数中创建所有需要的私有属性,这样就能在一个地方控制所有的私有属性。私有属性是实例中的属性,不会出现在 原型 上,且只能在类的构造函数中被创建。
上面这段话能帮你更好地理解构造函数,如果看不懂也没有关系,并不影响你使用它。
类实际上是一个具有构造方法的函数,这点在上面代码片的最后一行得到了体现。
- 类的一些特性
① 我们都知道函数声明是可以被提升的,但是类的声明与let和const类似,是不能被提升的,在执行声明语句之前,它们会存在于临时死区(TDZ)中。关于这点可以参考一下 这篇文章。
②类声明中的所有代码都会运行在严格模式下,而且不能让它脱离严格模式运行。
③使用new以外的方式调用类的构造函数会报错。
④类的名字在类中是个常量,不能修改,否则会报错。这点在上面的代码片中得到了体现。
⑤类可以作为一个参数传入函数,也可以作为函数的返回值。
类表达式
如同普通函数一样,类也有两种存在形式:声明形式和表达式形式。
声明形式:
class Aclass{
……
}
let a = new Aclass("构造函数的参数");
表达式形式:
let Bclass = class{
……
}
let b = new Blcass("构造函数的参数");
- 基本语法
用表达式形式来定义一个类,不需要将标识符写在class后面。在功能上,类表达式和类声明是等价的。
除了编写语法稍有不同以外,他们的区别就在于类声明的name属性值为类名,类表达式的name属性值是一个空字符串。比方说像下面的这个栗子中,FoodClass.name的值为空字符串,但是在上文中类声明的栗子里,FoodClass.name的值为"FoodClass"。
let FoodClass = class{
//构造函数,constructor是保留字,constructor构造函数是可以缺省的
constructor(name){
this.name = name;
}
showName(){
console.log(this.name);
FoodClass = 1;//报错,不能在类中修改类名所指向的内容
}
};
let food = new FoodClass("炒米粉");
food.showName();//输出"炒米粉"
console.log(food instanceof FoodClass);//true
console.log(typeof FoodClass);//"function"
- 命名类表达式(此知识点仅了解即可)
在上文的栗子中,我们定义的类表达式都是匿名的,我们可以给类的表达式进行命名。
举个栗子:
在这个栗子中,我们给这个类命名为FoodClass2,FoodClass2这个标识符只存在于类定义中,因此typeof FoodClass2的值是undefined。这样做就使得我们可以在类的定义中使用FoodClass标识符,因为类名变成了FoodClass2。let FoodClass = FoodClass2{ …… };
类的静态方法
如同大多数其他语言的静态方法一样,ES6中定义静态方法的标识符也是 static ,static可以修饰类中 除了构造方法 的所有其他方法。
举个栗子:
class FoodClass{
constructor(name){
this.name = name;
}
showName(){
console.log(this.name);
}
static create(name){
return new FoodClass(name);
}
static showName2(){
console.log(this.name);
}
}
let food = new FoodClass("炒米粉");
let food2 = FoodClass.create("炒鸡蛋");
food.showName();//炒米粉
food2.showName();//炒鸡蛋
food.showName2();//报错:food.showName2 is not a function.
在这个栗子中,我们用静态方法create()创建了food2,在这里,静态方法create起到了跟构造函数一样的作用。
静态方法是不可以在实例中访问的,必须要在类中访问静态方法,上面代码片的最后一行代码中我们尝试在实例中访问静态方法,结果报错。
类的继承和方法重写
与大多数其他面向对象语言一样,ES6中类的继承也是通过关键字extends来实现。
举个栗子:
class chaomifen{
constructor(weight){
this.weight = weight;
}
chao(){
console.log(`你的${this.weight}斤米粉炒好了,趁热吃!`)
}
}
class chaojidan extends chaomifen{
constructor(weight){
super(weight);//super()方法用于访问基类的构造函数
}
chao(){
console.log(`你的${this.weight}斤鸡蛋炒好了,趁热吃!`)
}
}
let 炒鸡蛋 = new chaojidan(2);
console.log(炒鸡蛋.chao());//输出:"你的2斤鸡蛋炒好了,趁热吃!"
在这个栗子中,chaojidan这个类继承了chaomifen这个类,也就是说chaomifen是chaojidan的基类,chaojidan是chaomifen的子类,也叫派生类。
super()方法用于调用基类的构造函数,chaojidan的构造函数中就用super()调用了chaomifen的构造函数。
在这里要强调几个点:1.如果子类中定义了构造函数,那么构造函数中必须要调用super(),否则会报错,如果不使用构造函数,那么创建新的实例时,系统会自动调用super()并传入所有参数。2.如果一个类不是子类,那么尝试在构造函数中调用super()会报错,这很好理解,因为它没有基类。
在上面的代码片中我们可以看到,chaomifen已经有一个chao()方法了,正常来说chaojidan会继承基类的chao()方法,而chaojidan又在类定义中声明了一个新的chao()方法,这时候在chaojidan()中的chao()方法会覆盖掉继承自基类的chao()方法,这就是类的方法重写。这点与其他面向对象语言中的方法重写表现一致,在这就不花太多篇幅讲解了。
值得一提的是,静态方法也能被正常继承。