ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。 部分来自JavaScript ES6 class指南、mozilla https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes
1.定义类
让我们回想一下在ES5中定义一个类的方式。通过不是很常用的Object.defineProperty方法,我可以定义一些只读的属性。
function Vehicle1(make, year) {
Object.defineProperty(this, 'make', {
get: function() { return make; }
}); Object.defineProperty(this, 'year', {
get: function() { return year; }
});
}
Vehicle1.prototype.toString = function() {
return this.make + ' ' + this.year;
}
var vehicle1 = new Vehicle1('Toyota Corolla', 2009);
console.log(vehicle1.make); // Toyota Corolla
vehicle1.make = 'Ford Mustang';
console.log(vehicle1.toString()) // Toyota Corolla 2009
很简单,我们定义了一个有两个只读属性和一个自定义toString方法的Vehicle类。让我们在ES6中来做一样的事情:
class Vehicle {
constructor(make, year) {
this._make = make;
this._year = year;
}
get make() {
return this._make;
}
get year() {
return this._year;
}
toString() {
return `${this.make} ${this.year}`;
}
}
var vehicle = new Vehicle('Toyota Corolla', 2009);
console.log(vehicle.make); // Toyota Corolla
vehicle.make = 'Ford Mustang';
console.log(vehicle.toString()) // Toyota Corolla 2009
上面两个例子中定义的类有一个不同的地方。我们为了享受新的get语法带来的好处,所以只是将make和year定义成了普通的属性。这使它们可以被外部所改变。如果你确实需要一个严格的私有属性,还是请继续使用defineProperty。
2.类声明
类声明是定义类的一种方式,就像下面这样,使用 class 关键字后跟一个类名(这里是 Polygon),就可以定义一个类。
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
3.变量提升
类声明和函数声明不同的一点是,函数声明存在变量提升现象,而类声明不会。
也就是说,你必须先声明类,然后才能使用它,否则代码会抛出 ReferenceError 异常,像下面这样:
var p = new Polygon(); // ReferenceError
class Polygon {}
4.类表达式
类表达式是定义类的另外一种方式。在类表达式中,类名是可有可无的。如果定义了类名,则该类名只有在类体内部才能访问到。
匿名的
var Polygon1 = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
命名的
var Polygon2 = class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
注意: 类表达式和类声明一样也不会有提升的现象。
5.原型方法
class Polygon3 {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea()
}
calcArea() {
return this.height * this.width;
}
}
const square = new Polygon3(10, 10);
console.log(square.area);//
6.静态方法
static 关键字用来定义类的静态方法。静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法,需要注意的是静态方法不能被实例化的对象调用。静态方法经常用来作为工具函数。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y; return Math.sqrt(dx*dx + dy*dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2));
7.类继承
使用 extends 创建子类
extends 关键字可以用在类声明或者类表达式中来创建一个继承了某个类的子类。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
speak() {
console.log(this.name + ' barks.');
}
}
var d = new Dog('Mitzie');
d.speak();// 'Mitzie barks.' //同样也可以用于原有的原型继承的“类”:
function Animal2 (name) {
this.name = name;
}
Animal2.prototype.speak = function () {
console.log(this.name + ' makes a noise.');
}
class Dog2 extends Animal2 {
speak() {
super.speak();
console.log(this.name + ' barks.');
}
}
var d2 = new Dog2('Mitzie');
d2.speak(); //需要注意的是类不能继承一般(非构造的)对象。如果你想要创建的类继承某个一般对象的话,你要使用 Object.setPrototypeOf():
var Animal3 = {
speak() {
console.log(this.name + ' makes a noise.');
}
};
class Dog3 {
constructor(name) {
this.name = name;
}
speak() {
super.speak();
console.log(this.name + ' barks.');
}
}
Object.setPrototypeOf(Dog3.prototype, Animal3);
var d3 = new Dog3('Mitzie');
d3.speak();
8.Species
你可能想要数组类 MyArray 返回的是 Array 对象。这个 species 模式能让你重写默认的构造器。
例如,当使用像 map() 这样的方法来返回默认的构造器时,你想要这个方法返回父级的 Array 对象,而不是 MyArray 对象。Symbol.species 能实现:
class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
9.super 关键字可以用来调用其父类的构造器或者类方法
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
new Lion('nick').speak();
//再看个重要的例子
class Animal4 {
constructor(){
this.type = 'animal';
}
says(say){
console.log(this.type + ' says ' + say);
}
}
let animal4 = new Animal4();
animal4.says('hello') //animal says hello
class Cat4 extends Animal4 {
constructor(){
super();
this.type = 'cat';
// 1.子类定义constructor;
// 2.调用super();
// 3.this就会指向子类实例cat4,否则constructor内this报错或子类方法内this指向父类实例
}
}
let cat4 = new Cat4();
cat4.says('hello'); //cat says hello
上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。
简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的。
Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。
这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
es6暂终