02-Class类
# 初识 Class
# Class 是什么
类可以看做是对象的模板,用一个类可以创建出许多不同的对象。
class Person {
// 实例化时执行构造方法,所以必须有构造方法,但可以不写出来
constructor(name, age) {
// console.log('实例化时执行构造方法');
// this 代表实例对象,上面定义的是实例属性/方法
this.name = name;
this.age = age;
}
// 各实例共享的方法
speak() {
console.log('speak');
}
}
const zs = new Person('ZS', 18);
const ls = new Person('LS', 28);
zs.speak();
console.log(zs.speak === ls.speak); // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
可以发现 Class 和我们之前学习的构造函数类似。下面看回顾下构造函数的写法,对比一下:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speak = function () {
console.log('speak');
};
2
3
4
5
6
7
8
之前学的构造函数用来模拟类,现在学习了 Class 就可以不用构造函数来做这个事情了。其实本质上还是构造函数那一套:
Class Person {}
cosole.log(typeof Person); // function
2
# Class 的两种定义形式
Class 的定义有两种形式:声明形式和表达式形式。
- 声明形式
class Person {
constructor() {}
speak() {}
}
2
3
4
5
- 表达式形式
先回顾下构造函数的声明形式和表达式形式:
// 构造函数声明形式
function Person(){}
// 构造函数表达式形式
const Person = function () {};
2
3
4
5
Class 的表达式形式定义:
const Person = class {
constructor() {
console.log('constructor');
}
speak() {}
};
new Person();
2
3
4
5
6
7
8
立即执行函数:
(function () {
console.log('func');
})();
2
3
立即执行匿名类:
new (class {
constructor() {
console.log('constructor');
}
})();
2
3
4
5
# Class 的属性和方法
# 实例属性和方法
实例属性和实例方法需要通过类的实例来访问。
class Person {
// 实例属性
age = 0;
sex = 'male';
// 实例方法:方法就是值为函数的特殊属性
getSex = function () {
return this.sex;
};
// 构造方法访问实例属性
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
// 实例方法
speak() {
console.log(`I'm ${this.name}.`)
}
}
const p = new Person('Alex');
// 通过实例 p 访问 实例属性
console.log(p.name);
console.log(p.age);
// 通过实例 p 访问实例方法 speak
p.speak();
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
# 静态方法
静态方法就是类的方法,不需要实例化类,就可以调用静态方法。
class Person {
// 实例方法
speak() {
console.log('speak');
console.log(this); // 实例方法中的 this 指向实例对象
}
// 静态方法
static speak() {
console.log('Person speak');
console.log(this); // 静态方法中的 this 指向类
}
}
// 通过实例调用实例方法
const p = new Person();
p.speak(); // speak
// 通过类名调用静态方法
Person.speak(); // Person speak
// 可以在 class 外为类添加静态方法
Person.sleep = function () {
console.log('Person sleep');
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 静态属性
静态属性就是类的属性,不推荐在属性前加 static 作为静态属性,有兼容性问题。
class Person {
// 不要这么写,目前只是提案,有兼容性问题
static version = 1.0;
}
2
3
4
那就只能在 class 外部添加属性了。
Person.version = '1.0';
推荐写成静态方法的形式,通过静态方法获取静态属性。
class Person {
static getVersion() {
return '1.0';
}
}
2
3
4
5
# 私有属性和方法
为什么需要私有属性和方法呢?一般情况下,类的属性和方法都是公开的,公有的属性和方法可以被外界修改,造成意想不到的错误。
有些方法和属性我们只希望在实例内部使用,不想暴露出去。这就需要我们实现私有属性和方法了。
ES6 并没有对私有属性和方法的支持,需要通过一些方式模拟私有属性和方法。下面介绍两种模拟方式。
方式一:_
开头表示私有(软约束)
class Person {
constructor(name) {
this._name = name;
}
// 通过公开方法访问私有属性
getName() {
return this._name;
}
}
const p = new Person('Alex');
console.log(p.getName());
2
3
4
5
6
7
8
9
10
11
12
13
方式二:将私有属性和方法移出类(强约束)
// 在函数作用域中定义属性和类,外界访问不到
(function () {
// 属性不定义在 Person 类里面,而是定义在同级作用域中
let name = '';
class Person {
constructor(username) {
name = username;
}
getName() {
return name;
}
}
// 通过 window 对象将 Person 类暴露出去
window.Person = Person;
})();
(function () {
const p = new Person('Alex');
console.log(p.name); // undefined
console.log(p.getName()); // Alex
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Class 的继承
# extends 关键字
我们来演示继承,先编写父类 Person。
class Person {
constructor(name, sex) {
// 实例属性和方法
this.name = name;
this.sex = sex;
this.say = function () {
console.log('say');
};
}
// 实例方法
speak() {
console.log('speak');
}
// 静态属性
static speak() {
console.log('static speak');
}
}
// 静态属性
Person.version = '1.0';
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
使用 extends
关键字继承父类。
// 使用 extends 关键字继承父类
class Programmer extends Person {
constructor(name, sex) {
// 子类构造方法中必须使用 super 关键字调用父类构造方法
super(name, sex);
}
}
const zs = new Programmer('zs', '男');
console.log(zs.name); // zs
console.log(zs.sex); // 男
zs.say(); // say
zs.speak(); // speak
Programmer.speak(); // static speak
console.log(Programmer.version); // 1.0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以看到子类继承了父类所有的属性和方法。
子类还可以改写父类的属性和方法,还可以添加属性和方法。
class Programmer extends Person {
constructor(name, sex, feature) {
// this 操作不能放在 super 前面
super(name, sex);
this.feature = feature; // 添加实例属性
}
// 添加实例方法
hi() {
console.log('hi');
}
// 改写实例方法
speak() {
console.log('Programmer speak');
}
// 改写静态方法
static speak() {
console.log('Programmer static speak');
}
}
// 覆盖静态变量
Programmer.version = '2.0';
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# super 关键字
super 关键字既可以作为函数使用,也可以作为对象使用。
# 作为函数使用
super 关键字作为函数使用只能调用父类的构造方法,只能在子类的构造方法中使用。
此时 super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例。
class Person {
constructor(name) {
this.name = name;
console.log(this);
}
}
class Programmer extends Person {
constructor(name, sex) {
// super 关键字调用父类构造方法
super(name, sex);
}
}
new Programmer();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 作为对象使用
(1) 在构造方法中使用或一般方法中使用
super 关键字作为对象使用用于通过 .
访问父类的属性和方法。
此时 super 代表父类的原型对象 Person.prototype
,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例。
class Person {
constructor(name) {
this.name = name;
console.log(this);
}
speak() {
console.log('speak');
// console.log(this);
}
static speak() {
console.log('Person speak');
console.log(this);
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
super.speak();
}
speak() {
super.speak(); // speak
console.log('Programmer speak');
}
}
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
(2) 在静态方法中使用
super 关键字用在静态方法中指向父类,而不是父类的原型对象。
通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例。
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
super.speak();
}
speak() {
super.speak(); // speak
console.log('Programmer speak');
}
static speak() {
super.speak(); // Person speak
console.log('Programmer speak');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16