ES6 Class

简介

ES6class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

简单的对比

// ES5
function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype.toString = function () {
    return '(' + this.x + ', ' + this.y + ')';
}

var p = new Point(1, 2);

// ES6
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}

let p = new Point(1, 2)

上述的代码分别定义了在 ES5ES6 中类声明的方式,可以看出有几点区别:

  • ES5function 声明类,ES6 使用 class 声明。
  • ES5 中函数中的内容移到了 ES6/constructor 中。
  • ES5 中在 prototype 上配置原型链上的方法,在 ES6 中直接定义。

由上可以看出,ES6 中的 class 一定程度上可以认为是 ES5 类写法的语法糖。

接着思考下 ES5 中类是如何实例化的:

  1. 直接调用类的函数,然后把 this 对象给返回出来。
  2. prototype 上的内容定义在实例的原型链上。

首先我们来看第一点,在 ES5 的类编写过程中,总会有这么一句

Point.prototype.constructor === Point // true

这说明类 prototype 上的 constructor 属性是自己。
那现在在来看实例化过程中第一步,可以认为调用类下 prototype 上的 constructor 方法。

那么是否可以这么认为:一个类的实例化过程其实和类本身没多大关系了,仅仅和 prototype 有关。

接着在来看 ES6 中的构造方式,我们定义了一个名字和一系列的方法,这其实就是在定义一个类的 prototype

但有一点需要尤其注意:

  • ES5 中在 prototype 上定义的方法是可枚举的,ES6 中定义的所有方法都是不可枚举的。

类的属性名,可以用 [] 表示:

let methodName = 'getArea';

class Square {
    constructor(length) {
        // ...
    }

    [methodName]() {
        // ...
    }
}

constructor 方法

constructor 方法是类的默认方法,通过 new 命令生成对象实例时,会调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。

class Point {
}

// 等同于
class Point {
    constructor() {}
}

constructor 方法默认返回实例对象(this),当然也可以指定对象返回,但是这就会照成实例对象并不是类的实例。

class Foo {
    constructor() {
        return Object.create(null)
    }
}

new Foo() instanceof Foo
// false

Class 表达式

Class 表达式的效果与函数一致

const MyClass = class Me {
    getClassName() {
        return Me.name
    }
};

let inst = new MyClass();
inst.getClassName();                // Me
Me.name;                            // ReferenceError: Me is not defined

如果一个类被定义成表达式的形式,那么跟在 class 关键字后的类名在类外部不可见,仅仅只能在类内部使用,如果内部不使用的话,可以去掉。

采用 Class 表达式,可以写出立即执行的 Class

let person = new class {
    constructor(name) {
        this.name = name
    }
    
    sayName() {
        console.log(this.name)
    }
}('张三')

person.sayName();               // "张三"

但感觉这很没必要,这还不如直接写一个对象。

不存在变量提升

类在 ES6 中的定义方式与 let 变量一致,不存在变量提升。

new Foo();                      // ReferenceError
class Foo {};

this 指向

ES6 实例对象下的方法表现与 ES5 一致,在方法中使用 this 对象需要小心,this 代指拥有这个方法的对象。

class Logger {
    printName(name = 'there') {
        this.print(`Hello ${name}`);
    }
    
    print(text) {
        console.log(text);
    }
}

const logger = new Logger();
const { printName } = logger;
printName();
// TypeError: Cannot read property 'print' of undefined

上述写法会报错,原因在于声明的 printName 变量的拥有者是 window 。这与函数下的 this 对象的绑定一致,并不存在说 ES6 下有特殊情况,解决方式和函数确定 this 指向方式一致。

  1. 使用 bind
  2. 使用箭头函数。

不过使用这两种方法,都需要在 this 对象上绑定同名的原型链上的方法:

class Logger {
    constructor() {
        this.printName = this.printName.bind(this);
    }

    // ...
}

class Logger {
    constructor() {
        this.printName = (name = 'there') => {
            this.print(`Hello ${name}`);
        }
    }

    // ...
}

Class get/set

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

class MyClass {
    constructor() {
        // ...
    }
    get prop() {
        return 'getter';
    }
    set prop(value) {
        console.log('setter: ' + value);
    }
}

let inst = new MyClass();

inst.prop = 123;                    // setter: 123

inst.prop;                          // 'getter'

这其实也是在语法糖内的简单实现,ES5 中通过 defineProperty 定义 prototype 可以实现一样的效果。

Class 的静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例实现。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例实现,而是直接通过类来调用,这就称为 静态方法

class Foo {
    static classMethod() {
        return 'hello';
    }
}

Foo.classMethod();                  // 'hello'

var foo = new Foo();
foo.classMethod();
// TypeError: foo.classMethod is not a function

当然如果在静态方法包含 this 关键字,这个 this 指的是类,而不是实例,这其实也是遵循函数执行时的 this 指向问题,总是执行调用这个函数的对象,而类仅仅是一个特殊的对象。

父类的静态方法,可以被子类继承。

class Foo {
    static classMethod() {
        return 'hello';
    }
}

class Bar extends Foo {
}

Bar.classMethod();                  // 'hello'

但目前 ES6 规定,类下仅仅只能有静态方法,不能有静态属性。