Class


# Class

对于面向对象编程而言,主要关注类的声明、属性、方法、静态方法、继承、多态、私有属性。

# 声明类

ES6 之前类是通过构造函数来实现的:

let Animal = function(type) {
  this.type = type
  this.walk = function() {
    console.log('I am walking')
  }
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')
1
2
3
4
5
6
7
8
9

或者可以这样写:

let Animal = function(type) {
  this.type = type
}

Animal.prototype.walk = function() {
  console.log('I am walking')
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')
1
2
3
4
5
6
7
8
9
10

在 ES6 中把类的声明专业化了,通过 class 语法来代替 function 的方式:

class Animal {
  constructor(type) {
    this.type = type
  }
  walk() {
    console.log( `I am walking` )
  }
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')
1
2
3
4
5
6
7
8
9
10
11

这样就和其他编程语言(Java、Python)保持一致了:声明一个类时有构造函数、方法。

class 不是新的数据类型,它本质上还是一个 function,通过观察原型可以发现,Animal.prototype 对象上有两个方法,一个是构造函数(constructor)、一个是自定义的方法(walk),这和上面 ES5 的第二种写法是一样的。而且和 ES5 一样,都有个 API 用来判断对象的自有属性(hasOwnProperty)。

class Animal {
  constructor(type) {
    this.type = type
  }
  walk() {
    console.log( `I am walking` )
  }
}

console.log(Animal.prototype)
1
2
3
4
5
6
7
8
9
10

所以可以得出结论:class 的方式是 function 方式的语法糖。

# Setters & Getters

对于类中的属性,除了可以直接在 constructor 中通过 this 直接定义,还可以直接在类的顶层来定义:

class Animal {
  constructor(type, age) {
    this.type = type
    this._age = age
  }
  get age() {
    return this._age
  }
  set age(val) {
    this._age = val
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

乍一看没有什么实质性用途,不过 get/set 的真正用途要体现在只读属性/私有属性上:

  • addr 是个只读属性
class Animal {
  constructor(type) {
    this.type = type
  }
  get addr() {
    return '北京动物园'
  }
}
1
2
3
4
5
6
7
8
  • 对某个属性设置 getset,拦截该属性的赋值和读取行为
class Animal {
  constructor(type) {
    this.type = type
  }
  get addr() {
    return '北京动物园'
  }
  set addr(value) {
    console.log(`can not set addr: ${value}`);
  }
}

let dog = new Animal('dog')
console.log(dog.addr)  // 北京动物园
dog.addr = '上海动物园' // can not set addr: 上海动物园
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 静态方法

静态方法是面向对象最常用的功能,在 ES5 中利用 function 实现的类是这样实现一个静态方法的。

let Animal = function(type) {
  this.type = type
  this.walk = function() {
    console.log('I am walking')
  }
}

Animal.eat = function(food) {
  console.log('I am eating')
}
1
2
3
4
5
6
7
8
9
10

在 ES6 中使用 static 的标记是不是静态方法,这也和其他变成语言保持了一致。

class Animal {
  constructor(type) {
    this.type = type
  }
  walk() {
    console.log('I am walking')
  }
  static eat() {
    console.log('I am eating')
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 继承

面向对象之所以可以应对复杂的项目实现,很大程度上要归功于继承。在 ES5 中是这样实现继承的:

// 定义父类
let Animal = function(type) {
  this.type = type
}
// 定义方法
Animal.prototype.walk = function() {
  console.log('I am walking')
}
// 定义静态方法
Animal.eat = function(food) {
  console.log('I am eating')
}
// 定义子类
let Dog = function() {
  // 初始化父类
  Animal.call(this, 'dog')
  this.woof = function() {
    console.log('Wang Wang')
  }
}
// 继承
Dog.prototype = Animal.prototype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

从代码上看,很繁琐,而且阅读性也较差。ES6 是这样解决这些问题的:

class Animal {
  constructor(type) {
    this.type = type
  }
  walk() {
    console.log('I am walking')
  }
  static eat() {
    console.log('I am eating')
  }
}

class Dog extends Animal {
  constructor () {
    super('dog')
  }
  woof () {
    console.log('Wang Wang')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面代码中,需要注意的是子类的构造函数必须执行一次 super 函数,否则 JavaScript 引擎会报错

虽然 ES6 在类的定义上仅是 ES5 定义类的语法糖,但是从开发者的角度而言,开发更有效率了,代码可阅读性大大提升。

# 参考资料

(完)