ECMAScript 6中的面向对象

Heero.Luo发表于9年前,已被查看1778次

ECMAScript 6(下面简称ES6)增强了对面向对象的支持,引入了class关键字,并且为类的创建、继承提供了简洁清晰的语法。

类声明与类表达式

毋庸置疑,class关键字就是用来定义类的。

定义类的常用方式是类声明,语法如下:

class ClassName {
	// 构造函数
	constructor() {
		// ...
	}

	// 方法
	method() {
		// ...
	}
}

而另一种方式则是类表达式,语法如下:

var ClassName = class {
	// 构造函数
	constructor() {
		// ...
	}

	// 方法
	method() {
		// ...
	}
};

下面通过class关键字重新实现Shape类(注意,class关键字目前只在Chrome42的严格模式下可用):

'use strict';

class Shape {
	constructor(name) {
		this.setName(name || '形状');
	}

	getName() { return this._name; }
	setName(name) { this._name = name; }
	perimeter() { }
}

var shape = new Shape();
shape.setName('我的形状');
shape.getName(); // '我的形状'

Getter和Setter

getName、setName这两个方法本来是为了隐藏_name属性而存在的,但调用方法明显比访问属性更累赘一些。Getter和Setter就是为了解决这个问题而出现的。它们分别用于定义对属性读和写时进行的操作:

class Shape {
	constructor(name) {
		this.name = name || '形状';
	}

	get name() {
		// 获取name属性的值时,实际返回的是_name
		return this._name;
	}
	set name(name) {
		// 设置name属性的值时,会写入到_name,而不是覆盖name
		this._name = name;
	}
	
	// 只有getter,所以无法写入
	get perimeter() { }
}

var shape = new Shape();
shape.name = '我的形状';
console.log(shape.name); // '我的形状'

shape.perimeter = 10; // 抛出异常

Getter和Setter分别通过get、set关键字来声明,二者可以同时存在或只存在其一。如果一个属性只有getter没有setter,那它就是个只读属性(如perimeter)。

继承

与Java一样,JavaScript也使用extends关键字实现继承,子类中可以通过super关键字调用父类:

class Square extends Shape {
	constructor() {
		this.length = 0;
		super('正方形'); // 调用父类构造函数
	}

	get length() { return this._length; }
	set length(length) {
		if (length < 0) {
			throw new Error('...');
		}
		this._length = length;
	}

	get perimeter() { return this.length * 4; }
}

var square = new Square();

上面的代码看起来没问题,但实例化Square的时候会出现异常:

Uncaught ReferenceError: this is not defined

这可奇怪了,this明明指的是当前对象,为何提示未定义呢?进一步查资料得知,子类必须在调用父类构造函数后才能使用this。于是Square类代码调整为:

class Square extends Shape {
	constructor() {
		super('正方形');
		this.length = 0;
	}

	get length() { return this._length; }
	set length(length) {
		if (length < 0) {
			throw new Error('...');
		}
		this._length = length;
	}

	get perimeter() { return this.length * 4; }
}

接着声明长方形类和圆形类:

class Rectangle extends Shape {
	constructor() {
		super('矩形');
		this.length = 0;
		this.width = 0;
	}

	get length() { return this._length; }
	set length(length) {
		if (length < 0) {
			throw new Error('...');
		}
		this._length = length;
	}

	get width() { return this._width; }
	set width(width) {
		if (width < 0) {
			throw new Error('...');
		}
		this._width = width;
	}

	get perimeter() { return (this.length + this.width) * 2; }
}
class Circle extends Shape {
	constructor() {
		super('圆形');
		this.radius = 0;
	}

	get radius() { return this._radius; }
	set radius(radius) {
		if (radius < 0) {
			throw new Error('...');
		}
		this._radius = radius;
	}

	get perimeter() { return 2 * Math.PI * this.radius; }
}

最后是计算周长:

function computePerimeter(shape) {
    console.log(shape.name + '的周长是' + shape.perimeter);
}

var rectangle = new Rectangle();
rectangle.width = 10;
rectangle.length = 20;
computePerimeter(rectangle);

var square = new Square();
square.length = 10;
computePerimeter(square);

var circle = new Circle();
circle.radius = 10;
computePerimeter(circle);

其他细节

语法糖

虽然引入了class关键字,但ECMAScript 6并没有真的引入类这个概念,通过class定义的仍然是函数:

console.log(typeof Shape); // 'function'

所以说,class仅仅是通过更简单直观的语法去实现原型链继承。这种对语言功能没有影响、但是给程序员带来方便的新语法,被称为语法糖。

变量提升

JavaScript中的函数声明存在变量提升这一特性,也就是说,调用函数的代码可以在函数声明之前。例如:

fn();
function fn() { }

虽然通过class声明的“类”实际上也是函数,但是它没有变量提升这一特性。例如下面这段代码会抛出异常:

var a = new A(); // 抛出异常
class A { }

最后

可见,与原型链写法比起来,使用class定义类简单得多。但是要想在ES6普及之前使用这个语法,只能通过一些工具把ES6转成ES5或ES3了。

评论 (3条)

发表评论

(必填)

(选填,不公开)

(选填,不公开)

(必填)