ES6特性 Class

Class关键字介绍

ES6 class关键字是一个基于原型链面向对象编程模式的语法糖. 有单一快捷的声明形式使"类"模式的使用变得容易, 并且也鼓励互用性. "类"支持基于原型的继承, 超级回调, 实例和静态方法以及构造函数.

class SkinnedMesh extends THREE.Mesh {
    constructor(geometry, materials) {
        super(geometry, materials);
        this.idMatrix = SkinnedMesh.defaultMatrix();
        this.bones = [];
        this.boneMatrices = [];
        //...
    }
    updata(camera) {
        //...
        super.updata();
    }
    get boneCount() {
        return this.bones.length;
    }
    set matrixType(matrixType) {
        this.idMatrix = SkinnedMesh[matrixType]();
    }
    static defaultMatrix() {
        return new THREE.Matrix4();
    }
}

方法之间不需要用逗号分隔, 加了会报错. 类的数据类型就是函数, 类本身就指向构造函数. 类的所有方法都是定义在类的prototype属性上面的, 在类的实例上调用方法其实就是在调用原型上的方法. 而Object.assign方法可以方便的一次向类添加多个方法. 并且, 类内部所有定义的方法, 都是不可枚举的. 类的属性名可以采用表达式. 类的内部默认是严格模式, 所以不必显示定义. 类也继承了函数的name属性.

let methodName = 'methodName';
class Point {
    constructor () {}
    [methodName] () {}
}
Object.assign(Point.prototype, {
    toString () {},
    toValue() {}
})
typeof Point
//function
Point == Point.prototype.constructor
//true

constructor方法和Generator方法

constructor方法是类默认的方法, 即使没有显示定义, 一个空的constructor方法也会被JavaScript引擎自动添加. 类必须使用new操作符调用, 否则会报错.

某个方法前加了*号则表明该方法为Generator函数.

类的实例对象

实例的属性除非显示定义在其本身, 即定义在this对象上, 否则都是定义在原型上的. 类的所有实例共享一个原型对象, 所以可以通过实例的__proto__属性修改增加类上的方法.

class People {
    constructor (name, husband) {
        this.name = name;
        this.husband = husband;
    }
    hasHusband () {return this.name + "has husband" + this.husband}
}

let emi = new People("emi", "vilic");
emi.hasOwnProperty('name')
emi.hasOwnProperty('hasHusband')
emi.__proto__.hasOwnProperty('hasHusband')

Class表达式和变量提升问题

类可以写为表达式形式, let myClass = class my {}, 注意这里的my只能类内部使用, 当内部不需要使用时可以省略不写. 使用类表达式可以写出立即执行的Class生成类的实例. let person = new class {}().

类不存在变量提升, 所以在使用类前需要定义好.

私有属性和私有方法

ES6并不支持私有属性和私有方法.
私有方法常见的做法是私有方法用_下划线为命名的开始或者把私有方法写到模块中类的外部或者利用Symbol值的唯一性.
可能以后可以用#前缀来表示私有属性和方法.

this的指向

类的方法内部如果有this, 默认指向类的实例. 但是如果把该方法单独提出来使用时this将指向该方法运行时所在的环境, 此时可能报错. 解决办法是在定义式就用bind绑定或者使用箭头函数. 也可以使用proxy在获取方法的时候自动绑定this.

Class的取值和存值函数

class Demo {
constructor() {
}
set prop(value) {
    this.name = value;
    console.log('setter: ' + this.name);
}
get prop() {
    console.log('getter: ' + this.name);
}
} 

let demo = new Demo();
demo.prop = 'emi';
//demo.prop 'emi'
//demo.name 'emi'

getset关键字其实最大的用处是在存取值时拦截该属性的存取行为并做一些操作. Object.getOwnPropertyDescriptor(Demo.prototype, 'prop'), 通过这个表达式可以看见getset是定义在该属性的Descriptor对象上的.

Class的静态方法

使用static关键字声明类的静态方法, 静态方法直接用类调用, 不会被实例继承, 不能在类实例上调用. 如果静态方法上包含this, 则这个this指的是类, 而不是实例. 静态方法可以和非静态方法重名. 父类的静态方法可以被子类继承, 也可以从super对象上调用.

class Demo {
 constructor(name, age) {
     this.name = name;
     this.age = age;
 }
 static lover() {
     console.log("emi loves vilic");
 }
 lover() {
     console.log("vilic loves emi");
 }
 static belongClass() {
     this.lover();
 }
}

class LittleDemo extends Demo {
   static try() {
        this.lover();
    }
}

目前, 类的静态属性只能像这样写在类外部. class Foo {}; Foo.prop = 1;

new.target属性

在构造函数constructor中使用, 如果构造函数不是通过new命令调用的, new.target属性将返回undefined, 反之将返回当前类. 子类继承父类时, new.target将返回子类.

class People {
    constructor() {
        if(new.target === People) {
            throw new Error("can't instantiation");
        }
    }
}
class Emi extends People {
    constructor(name, age) {
        super();
    }
}

super关键字

super对象用于在子类上调用父类上的方法,在constructor中先调用super方法以方便有this可用. Class通过extends实现继承, 子类在没有自己的this, 需要在constructor构造函数中调用super()方法继承父类的this对象后才能在子类中使用this. 如果没有显示的在子类中写构造函数, 其实隐式调用构造函数的同时也是会调用super()方法的. super()用作方法时, 只能在子类的构造函数中使用. 当super在子类的普通函数中可以用作对象, 指向父类的原型对象, 在静态方法中指向父类. 由于super对象指向的是父类的原型对象, 所以定义在父类实例上的方法和属性无法通过super调用.

class People {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    static printIt() {
        console.log('emi');
    }
    printIt() {
        console.log('vilic');
    }
}
class Emi extends People {
    constructor(name, age, husband) {
        //需要父类中的哪个就传入哪个
        super(name, age);
        this.husband = husband;
    }
    child() {
        super.printIt();
    }
    static child() {
        super.printIt();
    }
}

在子类中通过Object.getPrototypeOf()可以获取父类.

prototype和__proto__

prototype

原生构造函数的继承

在ES6中, 原生的构造函数Boolean() Number() String() Array() Date() Function() RegExp() Error() Object()也能继承

class myArray extends Array {
    constructor(...arg) {
        super(...arg);
        //...
    }
}