1. 简介

ES6引入了class(类)的概念,作为对象的模板。通过class可以定义类。比如定义一个MyPoint类:

class MyPoint {
    //构造方法,this关键字代表实例对象
    constructor(x,y) {
        this.x = x;
        this.y = y;
    }

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

注意:定义类的方法时,前面不用加function关键字,另外方法之间不需要逗号分隔,加了会报错

使用的时候直接对类使用new命令

const point = new MyPoint(1,3);
point.toString(); //"(1, 3)"

事实上类的所有方法都定义在类的prototype属性上面

class Point {
    constructor() {}
    toString() {}
    toValue() {}
}

等同于

Point.prototype = {
    constructor() {},
    toString() {},
    toValue() {},
}

所以在类的实例上调用方法,其实就是调用原型上的方法。

class B {}
let b = new B();

b.constructor === B.prototype.construcotor // true

因为类的方法都定义在prototype对象上,所以类的新方法可以添加在prototype对象上,因此可以利用Object.assign方法一次向类添加多个方法

class Point {
    constructor(){}
}

Object.assign(Point.prototype, {
    toString(){},
    toValue(){}
})

注意:类的内部定义的多个方法,都是不可枚举的(non-enumerable)

class Point {
    constructor(){}
    toString(){}
}

Object.keys(Point.prototype)
//[]
Object.getOwnPropertyNames(Point.prototype)
//["constructor","toString"]

采用ES5的写法,toString方法是可以枚举的

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

Point.prototype.toString = function() {
// ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

2. 严格模式

类和模块的内部,默认是严格模式的,所以不需要使用use strict指定运行模式

3. constructor方法

constructor方法当通过new关键字生成实例对象的时候会被自动调用。一个类必须有constructor方法,如果没有显示的定义,一个空的constructor方法会被默认添加。 constructor方法默认返回实例对象。

4. 类的实例对象

生成类的实例对象可以借助new关键字。

class Point {}

const p = new Point();

与ES5一样,实例的属性除非显示定义在其本身(即定义在this对象上),否则都定义在class上

class Point {
    constructor (x, y) {
        this.x = x; //显示定义实例对象本身上
        this.y = y; //显示定义实例对象本身上
    }
    //定义在class上
    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}

const point = new Point(2,3);
point.toString(); // (2,3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') ;// true

与 ES5 一样,类的所有实例共享一个原型对象。

var p1 = new Point(1,2);
var p2 = new Point(1,2);
p1.__proto__ === p2.__proto__
//true

__proto__ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前 很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

5. class 表达式

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

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

以上代码使用表达式定义了一个类。这个类的名字是MyClass而不是Me,Me只在class的内部代码可用,指代当前类。如果内部没有用到的话也可以省略Me

const MyClass = class{};

利用class表达式,可以写出立即执行的class

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

person.sayName();//'tim'

6. 不存在变量提升(hoist)

类不存在变量提升,也就是说类定义必须在前,使用在后。

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

7. this的指向

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

class Logger {
    printName(name='there') {
        //this指向Logger类的实例
        this.print(`Hello ${name}`);
    }
    print(text) {
        console.log(text);
    }
}

const logger = new Logger();
logger.printName();// 'Hello there'

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

将printName方法单独提出,this指向运行时所在环境,这里为全局对象。而全局对象中没有定义print方法,所以引用错误。

解决方案1 – 在构造方法中绑定this

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

解决方案2 – 使用箭头函数

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

8. name属性

name属性总是返回紧跟在class关键字后面的类名

9. 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指的是类而不是类的实例

class Foo {
    static classMethod() {
        return 'hello';
    }
    static bar () {
        //this指代类Foo本身
        this.baz();
    }
    static baz () {
        console.log('hello');
    }
    //静态方法可以与非静态方法重名。
    baz () {
        console.log('world');
    }
}
Foo.bar()//'hello'

class Bar extends Foo {
    static classMethod(){
        //静态方法也是可以从super对象上调用的
        return super.classMethod() + ',too';
    }
}
//父类的静态方法,可以被子类继承
Bar.bar();//'hello'
Bar.classMethod();//'hello,too'

9. class的静态属性

静态属性是指class本身的属性,即class.propname,而不是定义在实例对象this上的属性

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

目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。

10. new.target属性

new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

function Person(name) {
    if(new.target !== undefined){
        this.name = name;
    } else {
        throw new Error('必须使用new命令生成实例');
    }
}

// 另一种写法

function Person(name) {
    if (new.target === Person) {
        this.name = name;
    } else {
        throw new Error('必须使用new命令生成实例');
    }
}

var person = new Person('tim');//Person {name: "tim"}
var notAPersion = Person.call(person,'tim')//'必须使用new命令生成实例'

Class 内部调用new.target,返回当前 Class

class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}

var obj = new Rectangle(3, 4); // true

注意,子类继承父类时,new.target会返回子类。

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
        this.who = new.target;
        console.log(this.who.name);
    }
}
var obj = new Rectangle(10,5);//Rectangle
class Square extends Rectangle {}
var obj2 = new Square();//Square

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类

class Shape {
    constructor() {
        if (new.target === Shape) {
            throw new Error('本类不能实例化');
        }
    }
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        // ...
    }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);

注意,在函数外部,使用new.target会报错。

  1. 寻寻觅觅 -- Christine Welch
  2. Just the Way You Are -- Bruno Mars
  3. Despacito(Remix) -- Luis Fonsi;Daddy Yankee;Justin Bieber
  4. 没有什么不同 -- 曲婉婷
  5. 故乡--许巍
  6. Jar Of Love -- 曲婉婷
  7. I Really Like You -- Carly Rae Jepsen