注:这是一篇学习笔记,记录自己在ES6学习过程中按照自己的思路觉得应该记录的一些要点,方便以后查看和复习。参考:
阮一峰ES6的书籍中ES6 Class 的基本语法
我的ES6入门学习规划
面向对象编程不是新鲜事,这篇笔记要记录的内容更不是新鲜事。其它语言比如java,早就有这些东西了。
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
constructor
构造函数是类基本的东西,ES6的class也跟其它语言一样,提供了constructor函数,利用它可以完成类的实例化逻辑。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。1
2
3
4
5class Popup {
constructor({title, content, zIndex}) {
// ...
}
}
构造函数一般不需要显示地return this,但是可以通过return返回其它对象。 虽然如此,我认为这是错误的用法。 如果要这么搞,就不要在类这个层次这样做,这样破坏了面向对象的语义。 js如果是强类型的语言,这样子肯定是通不过编译的。
类必须使用new调用,否则会报错。
实例方法
在class的内部,直接可以编写类的实例方法,这些方法只有类的实例才能调用。1
2
3
4
5
6
7
8
9
10
11
12
13class Popup {
constructor({title, content, zIndex}) {
// ...
}
show() {
}
hide() {
}
}
实例方法会被添加到类的原型上。1
Popup.prototype.hasOwnProperty('show')// true
类的多个实例,共用同一个原型,与ES5是一致的。
实例属性
在构造函数或实例方法内,通过this可为类的实例添加实例属性,同时实例属性也可以统一在实例方法并列的位置进行初始化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Popup {
_title = '';
constructor({title, content, zIndex}) {
// ...
this._title = title;
this._content = content;
}
show() {
}
hide() {
}
}
这点也是为了跟其它语言保持一致,让类的结构看起来更像是一个面向对象的风格。
setters和getters
在java语言里面,这两个东西是访问实例属性的最常用的方式,因为java语言里面有访问修饰符,不是随便就能访问到实例成员的。 在ES6的class里面,setter与getters可以与实例方法并列地放置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Popup {
_title = '';
constructor({title, content, zIndex}) {
// ...
this._title = title;
this._content = content;
}
show() {
}
hide() {
}
set title(title) {
this._title = title;
}
get title() {
return this._title;
}
}
setters和getters部署在属性描述对象(PropertyDescriptor)上面。
属性名表达式
ES6的class内部,除了构造函数,其它实例成员,可以使用属性名表达式,就跟在字面量对象中使用一样。
class表达式
ES6的class可以写出表达式,变成类似java的内部类:1
2
3
4
5const MyClass = class Me {
getClassName() {
return Me.name;
}
};
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。1
2
3
4
5
6
7
8
9
10
11let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
上面代码中,person是一个立即执行的类的实例。
注意点
- 类的内部所有定义的方法,都是不可枚举的(non-enumerable)
- ES6的class默认开启严格模式,不能修改
- ES6的class声明不存在提升
- ES6的class可以部署同步或异步的Generator函数
静态成员
静态属性和静态方法,与实例属性、实例方法的区别,仅仅是前面有static的关键字。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39class Popup {
_title = '';
_content ='';
_zIndex = 0;
static baseZIndex = 10;// 静态成员前面有static关键字
constructor({title = '', content = '', zIndex = undefined} = {}) {
// ...
this._title = title;
this._content = content;
if(!zIndex) {
this._zIndex = Popup.getZIndex(); // 通过类名访问静态方法
}
}
show() {
}
hide() {
}
set title(title) {
this._title = title;
}
get title() {
return this._title;
}
static getZIndex() {
return this.baseZIndex++;
}
static resetZIndex() {
this.baseZIndex-=1;
}
}
在静态方法内部,this指向的是类本身,而不是类实例。 静态属性,只需要在属性前面加上static关键字,就能部署到跟静态方法并列的位置。 静态方法可以通过类名来调用。
因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性法的前面,加上static关键字。
所以staic声明的静态属性目前来说还是不规范的,chrome浏览器倒是已经支持了。
私有属性的提案
由于ES6并没有引入访问修饰符,所以暂时没有标准方式来实现私有属性。目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示。注意是属性名,包括静态属性、静态方法、实例属性、实例方法,都可以通过#号来实现私有。1
2
3
4
5
6
7
8
9
10class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。
私有属性不限于从this引用,只要是在类的内部,实例也可以引用私有属性。1
2
3
4
5
6
7
8class Foo {
#privateValue = 42;
static getPrivateValue(foo) {
return foo.#privateValue;
}
}
Foo.getPrivateValue(new Foo()); // 42
上面代码允许从实例foo上面引用私有属性。
私有属性和私有方法前面,也可以加上static关键字,表示这是一个静态的私有属性或私有方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class FakeMath {
static PI = 22 / 7;
static #totallyRandomNumber = 4;
static #computeRandomNumber() {
return FakeMath.#totallyRandomNumber;
}
static random() {
console.log('I heard you like random numbers…')
return FakeMath.#computeRandomNumber();
}
}
FakeMath.PI // 3.142857142857143
FakeMath.random()
// I heard you like random numbers…
// 4
FakeMath.#totallyRandomNumber // 报错
FakeMath.#computeRandomNumber() // 报错
new.target 属性
该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
需要注意的是,子类继承父类时,new.target会返回子类。1
2
3
4
5
6
7
8
9
10
11
12
13
14class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
// ...
}
}
class Square extends Rectangle {
constructor(length) {
super(length, width);
}
}
var obj = new Square(3); // 输出 false
上面代码中,new.target会返回子类。
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类,就像java中的抽象类一样。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确