ECMAScript 2015 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。
类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。
类声明
定义一个类的一种方法是使用一个类声明。要声明一个类,你可以使用带有class关键字的类名(这里是“Rectangle”)。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
函数声明和类声明之间的一个重要区别是函数声明会提升,类声明不会。
类表达式
一个类表达式是定义一个类的另一种方式。类表达式可以是被命名的或匿名的。赋予一个命名类表达式的名称是类的主体的本地名称。
/* 匿名类 */
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
/* 命名的类 */
let Rectangle = class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
类表达式也同样受到类声明中提到的提升问题的限制。
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。它可以被看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
在ES5中定义一个类:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function(){
return 'Hi, I am ' + this.name;
}
而用ES6的写法重写一下,检测类型发现Person本质上仍然是函数:
class Person {
constructor(name){
this.name = name;
}
sayHello(){
return 'Hi, I am ' + this.name;
}
}
typeof Person; //'function'
调用的方式都是一致的:
var p1 = new Person('zhangsan');
用ES6定义class中的方法,定义在原型对象上的。与ES5不同的是,这些定义在原型对象的方法是不可枚举的。
ES6类和模块是严格模式下的;不存在变量提升,保证子类父类的顺序;类的继承也有新的写法:
class Female extends Person {
constructor(name){
super(name); //调用父类的,调用之后,子类才有this
this.sex = 'boy';
}
sayHello(){
return super.sayHello() + ', I am ' + this.sex;
}
}
注意,子类必须在父类的构造函数中调用super(),这样才有this对象,因为this对象是从父类继承下来的。而要在子类中调用父类的方法,用super关键词可指代父类。
ES5中类继承的关系是相反的,先有子类的this,然后用父类的方法应用在this上。
ES6类继承子类的this是从父类继承下来的这个特性,使得在ES6中可以构造原生数据结构的子类,这是ES5无法做到的。
ES6也可以定义类的静态方法和静态属性,静态的意思是这些不会被实例继承,不需要实例化类,就可以直接拿来用。ES6中class内部只能定义方法,不能定义属性。在方法名前加上static就表示这个方式是静态方法,而属性还是按照ES5的方式来实现。
function Person(name){
if(new.target === Person){
this.name = name;
}
else{
throw new Error('必须用new生成实例');
}
}
ES6中当函数用new关键词的时候,增加了new.target属性来判断当前调用的构造函数。这个有什么用处呢?可以限制函数的调用,比如一定要用new命令来调用,或者不能直接被实例化需要调用它的子类。
function Person(name){
if(new.target === Person){
this.name = name;
}
else{
throw new Error('必须用new生成实例');
}
}
ES5类中的 实例方法&原型方法&静态方法
1.实例方法只有实例可以调用
2.实例方法可以调用静态方法,不能调用原型方法
3.原型方法可以被实例和构造函数访问到
4.原型方法只可以调用静态方法
5.静态方法只有构造函数可以调用
ES5中定义三种方法及调用关系:
let Animal = function (type) {
this.type = type
// 实例方法
this.drink = function () {
// 实例方法可调用类的静态方法
Animal.walk()
console.log('drink')
}
}
// 原型方法
Animal.prototype.eat = function () {
// 原型方法可调用类的静态方法
Animal.walk()
console.log('eat food')
}
// 静态方法
Animal.walk = function () {
console.log('walking')
}
let dog = new Animal('dog')
dog.drink() // walking drink
dog.eat() // walking eat food
Animal.walk() // walking静态方法只能由<类自身&实例方法&原型方法>调用,实例对象无法调用
// dog.walk() // dog.walk is not a function 实例对象无法调用静态方法
ES6类中的 实例方法&原型方法&静态方法
1.实例方法=原型方法 类中直接定义的实例方法默认就是ES5中的原型方法(语法糖)
2.静态方法用static前缀定义
3.静态方法只有构造函数可以调用,原型方法是实例和构造函数都可以调用,是共享的方法。
ES6中定义三种方法及调用关系:
class Animal {
constructor (type) {
this.type = type
}
// 定义实例对象的方法
eat () {
// 使用静态方法,以类形式访问,而非this访问
Animal.walk()
console.log('eat food')
console.log(this.type) // 类的例实例对象的方法可以获取到实例对象的信息,输出dog
}
// static 定义类的静态方法
static walk () {
console.log('walking')
console.log(this.type) // 类的静态方法无法获取到实例对象的信息,输出undefined
}
}
let dog = new Animal('dog')
dog.eat() // walking ; undefined ; eat food ; dog
Animal.walk() // walking ; undefined ;
dog.walk() // dog.walk is not a function
// 得出结论:需要访问实例对象的信息时用实例对象的方法,否则使用static静态方法
// 实例方法就是只有实例可以调用,静态方法只有构造函数可以调用,原型方法是实例和构造函数都可以调用,是共享的方法。