一、简介
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.showName = function(){
return this.name;
}
var p1 = new Person('jack', 24);
console.log(p1.showName()); // jack
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class
关键字,可以定义类。
基本上,ES6 的 class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的 class
改写,就是下面这样。
//定义类
class Person{
constructor(name, age) {
this.name = name;
this.age = age;
}
showName() {
return this.name;
}
}
上面代码定义了一个“类”,可以看到里面有一个 constructor
方法,这就是构造方法,而 this
关键字则代表实例对象。也就是说,ES5 的构造函数 Person
,对应 ES6 的 Person
类的构造方法。
Person
类除了构造方法,还定义了一个 showName
方法。注意,定义“类”的方法的时候,前面不需要加上 function
这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法。
class Person{
}
typeof Person // "function"
Person === Person.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用 new
命令,跟构造函数的用法完全一致。
二、严格模式
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
三、constructor 方法
constructor
方法是类的默认方法,通过 new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的 constructor
方法会被默认添加。
class Person {
}
// 等同于
class Person {
constructor() {}
}
四、Class 的继承
Class 可以通过 extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Person {
}
class Man extends Person {
}
上面代码定义了一个 Man
类,该类通过 extends
关键字,继承了 Person
类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个 Person
类。下面,我们在 Man
内部加上代码。
class Man extends Person {
constructor(name, age, job) {
super(name, age); // 调用父类的constructor(name, age)
this.job = job;
}
showJob() {
return 'My name is ' + super.showName() + ',I am a ' + this.job; // 调用父类的 sayName()
}
}
var person1 = new Man('jack',26,'FED');
console.log(person1.sayJob()) // "My name is jack,I am a FED"
上面代码中,constructor
方法和 showJob
方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的 this
对象。
子类必须在 constructor
方法中调用 super
方法,否则新建实例时会报错。这是因为子类没有自己的 this
对象,而是继承父类的 this
对象,然后对其进行加工。如果不调用 super
方法,子类就得不到 this
对象。
class Person {
}
class Man extends Person {
constructor() {
}
}
var man = new Man(); // ReferenceError
上面代码中,Man
继承了父类 Person
,但是它的构造函数没有调用 super
方法,导致新建实例时报错。
ES5 的继承,实质是先创造子类的实例对象 this
,然后再将父类的方法添加到 this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super
方法),然后再用子类的构造函数修改 this
。
如果子类没有定义 constructor
方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有 constructor
方法。
class Man extends Person {
}
// 等同于
class Man extends Person {
constructor(...args) {
super(...args);
}
}
另一个需要注意的地方是,在子类的构造函数中,只有调用 super
之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有 super
方法才能返回父类实例。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Man extends Person {
constructor(name, age, job) {
this.job = job; // ReferenceError
super(name, age);
this.job = job; // 正确
}
}
上面代码中,子类的 constructor
方法没有调用 super
之前,就使用 this
关键字,结果报错,而放在
super
方法之后就是正确的。
下面是生成子类实例的代码。
let man = new Man('jack', 26, 'FED');
man instanceof Man // true
man instanceof Person // true
上面代码中,实例对象 man
同时是 Man
和 Person
两个类的实例,这与 ES5 的行为完全一致。
最后,父类的静态方法,也会被子类继承。
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {
}
B.hello() // hello world
上面代码中,hello()是A类的静态方法,B继承A,也继承了A的静态方法。
五、super关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super
作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
(略)