ES6 Class类深入学习

概述

在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。

class 的本质是 function。

它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。

基础用法

类定义

1
2
3
4
5
6
7
8
9
// 匿名式
let Person = class {}
// 命名式
let Student = class Student {}
// 立即执行
let Person = new class {}()

Student.prototype.constructor === Student // true
typeof Student // function

注意要点

  1. 类不可重复声明。
  2. 类定义不会被提升,访问前需对类定义。
  3. 类中方法不需要function 关键词。
  4. 方法间不能加分号。
  5. 类内部所有定义的方法都是不可枚举的。

引入你可能需要的知识

  • for…in可遍历自身对象上和原型上每一个可枚举属性(defineProperty上enumerable为false的除外)
  • Object.keys只能遍历自身的可枚举属性,不能遍历原型上的可枚举属性。
  • Object.getOwnPropertyNames可遍历自身对象上的所有属性,但是不能遍历原型上的属性。
  • xxx.propertyIsEnumerable检测一个属性是否可枚举

可枚举属性包含两个条件:

  1. 该属性是对象的自有属性。也就是对象本身就包含该属性,而不是从原型链继承而来。使用hasOwnProperty() 方法可以检测自有属性。
  2. 该属性是自定义的,而不是内置的,可以通过 for in 循环列举出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
constructor () {
this.sex = 'male'
}
// 这种写法通过Object.keys(Person.prototype)访问不到
age () {
return 18
}
}
// Person.prototype.age = () => 18; 这种写法Object.keys(Person.prototype)可以访问得到
let xiaoming = new Person();
Object.defineProperty(xiaoming, "name", {
value: "小明",
enumerable: false
});

for(var k in xiaoming){
  console.log(k); // sex,age (自身对象上和原型上)
}

Object.keys(xiaoming) // sex (自身的可枚举属性)
Object.getOwnPropertyNames(xiaoming) // sex, name(自身对象上的所有属性)

可证ES5的的原型写法和ES6的原型写法存在差异。

类的主体

属性
prototype

ES6中,prototype仍然存在,虽然可以直接定义类中方法,但实际方法还是定义在prototype上的。

覆盖方法/初始化方法

1
Person.prototype = {}

添加方法

1
Object.assign(Person.prototype, {});
静态属性

静态属性: class本身的属性,即定义在类内部的属性,不需要实例化。ES6中规定,Class内部只有静态方法,没有静态属性。

1
2
3
4
5
6
class Person {
// 新提案
static age = 18;
}
// 目前可行方法
Person.age = 18
公共属性

属性名可采用表达式

1
2
3
4
5
6
7
let getName = 'GETNAME'
class Person {
[getName] () {
// ...
}
}
Person.prototype.age = 18
实例属性

实例属性:定义在实例对象(this)上的属性。

1
2
3
4
5
6
class Person {
age = 18;
constructor () {
console.log(this.age);
}
}
name 属性

返回跟在class后的类名(如果存在)。

1
2
3
4
5
let Person = class {}
Person.name // 'Person'

let Student = class Child {}
Student.name // 'Child'
方法
constructor方法

constructor方法是类的默认方法,创建类的实例化对象时被调用。

1
2
3
4
5
6
class Person{
constructor(){
console.log('我是constructor');
}
}
new Person(); // 我是constructor
返回对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person{
constructor(){
// return this 默认返回实例对象
}
}
new Person() instanceof Person; // true

class Student{
constructor(){
// 指定返回对象
return new Person()
}
}
new Student() instanceof Student; // false
new Student() instanceof Person; // true
静态方法
1
2
3
4
5
6
class Person {
static sum(a, b) {
return a + b
}
}
Person.sum(1, 2); // 3
原型方法
1
2
3
4
5
6
7
class Person {
sum(a, b) {
return a + b
}
}
let person = new Person();
person.sum(1,2) // 3
实例方法
1
2
3
4
5
6
7
8
9
class Person {
constructor () {
this.sum = (a, b) => {
return a + b
}
}
}
let person = new Person();
person.sum(1,2) // 3

类的实例化

new

class的实例化必须通过new 关键字(这边不再举例)。

实例化对象

共享原型对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
constructor (a, b) {
this.a = a;
this.b = b;
}
sum () {
return this.a + this.b
}
}
let p1 = new Person(2, 1);
let p2 = new Person(3, 1);
p1.__proto__ === p2.__proto__; // true

Person.prototype.sub = function () {
return this.a - this.b
}
p1.sub(); // 1
p2.sub(); // 2

this的指向

类的方法内部如果含有this,它默认指向类的实例.

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
sayHello (name = '小明') {
// 此处的this指向Person的实例,提取出去会指向当前的环境
return `hello ${this.getFriends(name)}`
}
getFriends (name) {
return name
}
}
let p1 = new Person();
let { sayHello } = p1;
sayHello(); // Cannot read property 'getFriends' of undefined

可以通过在constructor中绑定this,即this.sayHello = this.sayHello.bind(this)

也可以通过在constructor中写箭头函数继承上一层的this指向,即

1
this.sayHello = (name = '小明')  => `hello ${this.getFriends(name)}`}

封装与继承

getter/setter

在Class内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
constructor() {
this.prop = []
}
get info() {
return this.prop;
}
set info(value) {
this.prop.push(value);
}
}
let person = new Person();
person.info = 'age:18' // prop: ["age:18"] info: ["age:18"];
person.info = 'name:xiaoming' // prop: ["age:18", "name:xiaoming"] info一样

class Student extends Person {
constructor () {
super()
}
}
let stu = new Student();
stu.info = 2;
stu.info // [2]

getter不能单独出现,且getter必须与setter同级

extends

通过extends实现类的继承。

extends 不可继承常规对象。

子类通过super关键字继承父类的this对象。

Object.getPrototypeOf()方法可以用来从子类上获取父类。

1
2
3
4
5
6
class Child extends Person {}

// 子类的__proto__总是指向父类,表示构造函数的继承。
Child.__proto__ === Person // true
// 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
Child.prototype.__proto__ === Person.prototype // true

super

子类constructor方法中必须有super,且必须在this之前。

调用父类方法,super可作为对象,

在普通方法(原型方法)中,指向父类的原型对象。

在静态方法中,指向父类。

无法调用实例方法。

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
class Father {
constructor () {
this.test2 = () => {
return 2
}
}
test(){
return 0;
}
static test1(){
return 1;
}
}

class Child extends Father {
constructor(){
super();
// 调用父类普通方法(原型方法)
console.log(super.test()); // 0
}
static test3(){
// 调用父类静态方法(静态方法)
return super.test1()+2;
}
}
Child.test3(); // 3

作为函数,代表父类构造函数。

supuer虽然代表的是父类的构造函数,却是返回子类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person{
constructor(){
this.age = 18
}
sayAge (){
console.log(this.age)
}
}

class Student extends Person{
constructor(){
super(); // 指向子类的实例
this.age = 20;
}
getInfo () {
super.sayAge() // super.sayAge.call(this)
}
}
let xiaoming = new Student()
xiaoming.getInfo() // 子类没有定义age = 20的话输出18,定义了输出20

Mixin类

Mixin类是指多个类以混入另一个类。

  • 同级继承(A,B,C继承于D)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function mix(...mixins) {
class Mix {}

for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}

return Mix;
}

function copyProperties(target, source) {
console.log(Reflect.ownKeys(source))
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}

案例

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class A {
constructor () {
this.type = 'A'
this.A = 'A'
}
getType () {
return this.type
}
getBase () {
return 'getBase'
}
}
A.prototype.hobs = '333'
class B {
constructor () {
this.type = 'B';
this.B = 'B'
}
getType () {
return 'hi getType ' + this.type;
}
getMid1 () {
return 'hi getMid1 ' + this.C
}
}
B.prototype.hobs = '444'

class C {
constructor () {
this.type = 'C';
this.C = 'C'
}
getType () {
return 'hello getType ' + this.type;
}
getMid2() {
return 'hello getMid2 ' + this.middle2
}
}
C.prototype.hob = '555'

class D extends mix(A, B, C) {
constructor () {
super();
this.type = 'D'
this.D = 'D'
}
}
let user = new D()

console.log(user.A, user.B, user.C, user.D) // undefined undefined undefined D
console.log(user.getType()) // hello getType D
console.log(user.getMid2()) // hello getMid2 undefined
console.log(user.hobs, user.hob) // 444 555
<!--
由此可见:
1.只继承子类的实例属性,不继承父类。
2.可见后者的同名原型方法覆盖前者。
3.这种方式依旧不能访问到实例属性。
4.可以访问到父类的原型属性,同名属性后者覆盖前者
-->
  • 嵌套继承(A继承于B,B继承于C, C继承于D)
1
2
3
4
const D = superClass => class extends superClass {};
const C = superClass => class extends superClass {};
const B = superClass => class extends superClass {};
class A extends B(C(D)) { }

实例

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const B = superClass => class extends superClass {
constructor () {
super()
this.type = 'B';
this.B = 'B'
}
getType () {
return 'B: ' + this.type;
}
getB (b) {
return b + b
}
}

const C = superClass => class extends superClass {
constructor () {
super()
this.type = 'C'
this.C = 'C'
}
getType () {
return 'C: ' + this.type;
}
getC (c) {
return c + c
}
}

class D {
constructor () {
this.type = 'D'
this.D = 'D'
}
getType () {
return 'D: ' + this.type;
}
getD (d) {
return d + d
}
}

class A extends B(C(D)) {
constructor () {
super();
this.type = 'A'
this.A = 'A'
}
// getType () { return 'A: ' + this.type; }
getA (a) {
return a + a
}
getAA () {
return super.getType()
}
}
let user = new A();

console.log(user.B, user.C, user.D) // B C D
console.log(user.type) // A
console.log(user.getType()) // A: A 当Class A 有getType方法
console.log(user.getType()) // B: A 当Class A 没有getType方法
console.log(user.getAA()) // B: A 当Class A 没有getType方法 super.getType()调用B的方法却指向A的实例属性
console.log(user.getAA()) // A: A 当Class A 有getType方法
<!--
由此可见:
1.可以继承所有父类的实例属性。
2.可见子类的同名实例属性覆盖父类。
3.可见子类的同名实例方法覆盖父类,并且指向子类。
4.可见super.getType.call(this)
-->