愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

《ECMAScript6入门》学习笔记之Class

ES6中开始引入了class这一关键字,使得能够以更加清晰的方法来定义类和使用类。实际上,class本质上是一个语法糖

一、示例

定义一个类形如:

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

    toString() {
        return `(${this.x}, ${this.y})`
    }
}

它实际上相当于:

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)

我们可以发现有以下的情况:

typeof Point // "function"
Point === Point.prototype.constructor // true

因此,本质上而言,ES6的类完全可以看做是构造函数的另一种写法。而且,在ES6的类中,仍然保留了prototype,类的所有方法,本质上是定义在prototype对象上的。可以用Object.assign为类添加新的方法,如:

Object.assign(Point.prototype, {
    add(x, y) {
        this.x += x
        this.y += y
    }
})

但是,ES6的类,和prototype还是有点不同的,类中定义的方法,是不可枚举的,所以有:

Object.keys(Point.prototype) // []

再者,类中的属性名,可以采用表达式的形式,如:

let methodName = 'add'
class Point {
    // ...
    [methodName]() {
        // ...
    }
}
// 那么,可以这么使用:
p.add(3, 4)


二、constructor

constructor方法是类的默认方法,在通过new命令生成对象实例时,就会调用该方法。一个类必定有一个constructor方法,如果没有显示指定,则constructor默认是一个空方法。
注意: constructor方法默认返回的是实例对象,当然也可以指定返回其他对象,但这可能会导致使用instanceof时出现意外的情况,如:

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

new Foo() instanceof Foo // 返回 false

此外,和构造函数不同的是,ES6中的类,如果没有实例化的话,是没有办法调用的,会报错。即:

Foo() // 报错
new Foo() // 正确

所有的实例,共享同一个原型对象(与ES5一样),如:

var p1 = new Point(1, 2)
var p2 = new Point(3, 4)

p1.__proto__ === p2.__proto__ // true

注意:类和模块的内部,默认是严格模式。只要代码写在类或者模块中,就只有严格模式可用

三、class表达式

1)和function类似,class也可以用表达式的形式定义,如:

const MyClass = class Me {
    getClassName() {
        // 类的名字是MyClass,Me只能在内部使用
        return Me.name
    }
}

2)此外,也可以省略类名,如:

const MyClass = class {
    // ...
}

3)和函数类似,类中存在有立即执行类,如:

let p = new class {
    constructor(x, y) {
        this.x = x
        this.y = y
    }

    describe() {
        console.log(`(${this.x}, ${this.y})`)
    }
}(1, 2)

p.describe() // (1, 2)

注意点

1)class不存在变量提升,如下会报错:

new Foo()
class Foo {}

为什么class不存在变量提升?原因如下:
考虑有情况如:

{
    let Foo = class {}
    class Bar extends Foo {
    }
}

假如class存在变量提升,那么class Bar extends Foo会被提升到代码头部,而由于let不存在变量提升,所有就会导致Bar类继承Foo类时,Foo类还未定义。


四、私有方法

ES6中的类,默认是不同私有方法功能的,如果我们想要实现私有方法功能,只能使用变通的方法实现,如:

1、命名上加以区分

使用_开头标识私有方法,但是这种方式,是不保险的,因为类外仍然可以访问得到,即:

class Circle {
    area () {
        // ...
    }
    _check () {
        // ...
    }
}

2、将私有方法移出类

由ES6的模块实现机制我们可以知道,有:

class Cirlce {
    // ...
}

export default Circle;

如果我们没有将一个方法、常量、变量export出,那么在其他模块中是访问不了的。借助这种特性,我们可以这么实现私有方法,如:

function _getRadius() {
    return this.radius;
}

class Circle {
    constructor(radius) {
        this.radius = radius;
    }

    getRadius() {
        return _getRadius.call(this);
    }
}

export default Circle;


五、name属性

name属性可以获取类的名称,如:

Point.name; // 'name'


六、getter和setter

可以使用getset关键字,来设置一个属性的getter和setter,如:

class Person {
    get name() {
        return 'getter';
    }
    set name(value) {
        console.log('setter');
        return value;
    }
}

const p = new Person();
p.name; // 'getter'
p.name = 'ABC';
/*
setter
'ABC'
*/

注意: getter和setter,都是定义在属性的Descriptor对象上的


七、generator

如果要使某个方法称为generator,那么可以在方法之前加上型号*,如:

class Foo {
    * gen() {
        yield 1;
        yield 2;
        yield 3;
    }
}

const p = new Foo;
const [a, b, c] = p.gen();
a; // 1
b; // 2
c; // 3


八、静态方法

可以使用static关键字来定义静态方法:静态方法只能使用类名来调用,不能使用实例调用,如:

class Foo {
    static someMethod() {
        console.log('called!!');
    }
}
const p = new Foo;
p.someMethod(); // Uncaught TypeError: p.someMethod is not a function
Foo.someMethond(); // called

父类的静态方法,是可以被子类继承的,如:

class Bar extends Foo {}
Bar.someMethod(); // called

静态方法,可以通过super对象调用,如:

class Bar extends Foo {
    static someMethod() {
        super.someMethod();
        console.log('called again!!');
    }
}
Bar.someMethod();
/*
called!!
called again!!
*/


九、静态属性

ES6中,class内部只有静态方法,没有静态属性,即:

class Foo {
    prop: 1
    static prop: 2
}

都是无效的,要添加静态属性,可以有:

class Foo {}
Foo.prop = 1;
Foo.prop; // 1

提案:

1)类的实例属性
类的实例属性可以写在类定义里面,如:

class Foo {
    prop = 123;
    constructor() {
        console.log(this.prop); // 123
    }
}

2)类的静态属性
加上static关键字即可,如:

class Foo {
    static prop = 123;
    constructor() {
        console.log(Foo.prop); // 123
    }
}


十、new.target属性

ES6中,为new命令引入了new.target属性,用于确定构造函数是怎么调用的。如:

const p = new Person;
// p是通过new命令建立的,所以在构造函数内部有 new.target === Person

再看以下的情况:

const p = Person.call(person);
// p不是通过new命令建立的,所以 new.target === undefined

此外,还需注意:当子类继承父类时,new.target返回的是子类