基本

传统的JavaScript中只有对象,没有类概念,跟面向对象语言差异很大。为了让JavaScript具有更接近面向对象语言的写法,ES6引入了Class(类)的概念,通过class关键字定义类。面向对象是指类(Class),不是真正的对象(Object)

  1. // ES5的写法
  2. function Person1(name, age) {
  3. // 实例属性
  4. this.name = name
  5. this.age = age
  6. }
  7. // 原型属性
  8. Person1.prototype.count = 1
  9. // 原型函数
  10. Person1.prototype.getName = function () {
  11. return this.name
  12. }
  13. const p1 = new Person1('kingx', 12)
  14. console.log(p1.getName()) // kingx
  15. // ES6的写法
  16. class Person2 {
  17. constructor(name, age) {
  18. // 实例属性
  19. this.name = name
  20. this.age = age
  21. }
  22. // 实例属性(es7语法,其实并不是真正原型上的属性,这样写只是会给每个实例加上该属性,相当于this.count = 1)
  23. count = 1;
  24. // 通过属性访问器添加的才是真正添加到原型上的属性Person2.prototype
  25. get count2() {
  26. return 2
  27. }
  28. set count2(newVal) {}
  29. // 原型函数(this指向实例)
  30. getName() {
  31. return this.name
  32. }
  33. }
  34. const p2 = new Person2('kingx', 12)
  35. console.log(p2.getName()) // kingx
  36. console.log(typeof Person2); // function
  37. console.log(p2.constructor === Person2.prototype.constructor); // true
  38. console.log(p2.getName === Person2.prototype.getName); // true

Class本质上也是一个函数,class中的所有属性和函数都是定义在prototype属性中的,但ES6将prototype相关的操作封装在了class中,避免我们直接去使用prototype属性。

重点

1. constructor()函数

constructor()函数是一个类必须具有的函数,可以手动添加,如果没有手动添加,则会自动隐式添加一个空的constructor()函数。
constructor()函数默认会返回当前对象的实例,即默认的this指向,可以手动修改返回值。

  1. class Person {
  2. constructor(name) {
  3. this.name = name
  4. return {} // 修改了this指向为一个空对象{}
  5. }
  6. getName() {
  7. return this.name
  8. }
  9. }
  10. const p = new Person('kingx')
  11. console.log(p.getName()) // TypeError: p.getName is not a function

2. 静态属性和函数

静态属性和函数使用static关键字修饰时,静态属性和函数无法被实例访问,只能通过类自身使用。静态函数中的this指向的是类本身,而不是类的实例,也正因为静态函数和实例函数中的this是隔离的,所以同一个类中可以存在函数名相同的静态函数和实例函数。

  1. class Foo {
  2. constructor(number) {
  3. // 实例属性
  4. this.number = number
  5. }
  6. // 静态属性
  7. static classProp = 'staticProp'
  8. // 静态函数
  9. static testHello() {
  10. return 'hello'
  11. }
  12. // 静态函数中,this指向类本身而不是实例
  13. static getNumber() {
  14. return this.number
  15. }
  16. // 原型函数,this指向实例
  17. getNumber() {
  18. return this.number
  19. }
  20. }
  21. // 类自身可以正常访问静态属性和函数
  22. Foo.classProp // 'staticProp'
  23. Foo.testHello() // 'hello'
  24. Foo.getNumber() // undefined
  25. Foo.number = 60
  26. Foo.getNumber() // 60
  27. const foo = new Foo(20)
  28. // 通过实例访问静态属性,返回undefined
  29. foo.classProp // undefined
  30. // 通过实例访问静态函数,抛出异常
  31. foo.testHello() // TypeError: foo.testHello is not a function
  32. foo.getNumber() // 20

3. ES7 中类的用法

ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义实例属性,并且可以定义静态属性

  1. class Animal {
  2. name = "Jack";
  3. static num = 42;
  4. constructor() {
  5. // ...
  6. }
  7. }
  8. let a = new Animal();
  9. console.log(a.name); // Jack
  10. console.log(Animal.num); // 42

4. 不存在变量提升

let关键字和const关键字声明的变量不存在变量提升,class定义的类同样不存在变量提升,因此如果在定义类之前去使用它,会抛出引用异常。

  1. const p = new Person(); // ReferenceError: Person is not defined
  2. class Person {}

5. 不加function关键字

在类中声明函数时,不要加function关键字,否则会抛出语法异常。

  1. class Person {
  2. test1 function() { // SyntaxError: Unexpected token function
  3. return '111'
  4. }
  5. function test2() { // SyntaxError: Unexpected identifier
  6. return '222'
  7. }
  8. }

6. this指向问题

类内部的this默认指向的是类的实例,在调用实例函数时,一定要注意this的指向性问题。如果单独使用实例函数时,this的指向会发生变化:

  1. class Person {
  2. constructor(name) {
  3. this.name = name
  4. }
  5. getName() {
  6. return this.name
  7. }
  8. }
  9. const p = new Person('kingx')
  10. let { getName } = p
  11. getName() // TypeError: Cannot read property 'name' of undefined

使用解构获取到getName()函数,在全局环境中执行,this指向的是全局环境,而在ES6的class关键字中使用了严格模式。在严格模式下this不能指向全局环境,而是指向undefined,所以getName()函数在执行时,this实际为undefined。解决方法:在构造函数中使用bind关键字重新绑定this。

  1. class Person {
  2. constructor(name) {
  3. this.name = name
  4. // 重新绑定getName()函数中this的指向为当前实例
  5. this.getName = this.getName.bind(this)
  6. }
  7. getName() {
  8. return this.name
  9. }
  10. }
  11. const p = new Person4('kingx')
  12. let { getName } = p
  13. getName() // kingx

7. 只能与new关键字配合使用

class定义的类只能配合new关键字生成实例,不能像普通函数一样直接调用。

  1. class Person {}
  2. const p1 = new Person(); // 正常
  3. const p2 = Person(); // TypeError: Class constructor Person cannot be invoked
  4. without 'new'

继承

父类的静态函数无法被实例继承,但可以被子类继承。
子类在访问时同样是通过本身去访问,而不是通过子类实例去访问。

  1. class Parent {
  2. static staticMethod() {
  3. return 'hello';
  4. }
  5. }
  6. class Child extends Parent {}
  7. // 通过子类本身可以访问到父类的静态函数,输出“hello”
  8. console.log(Child.staticMethod());