ES6: class类讲解

执着明日

es6引入了类的概念,让对象原型的写法更加清晰、更像面向对象编程的语法,它可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到。

class类的声明方法

第一种声明式,例如:

class Res{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    a(){
        return "method:a==>"+this.x;
    }
    b(){
        return "method:b==>"+this.y;
    }
}

第二种命名式,例如:

let Res=class Res{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    a(){
        return "method:a==>"+this.x;
    }
    b(){
        return "method:b==>"+this.y;
    }
}

第三种匿名式,例如:

let Res=class {
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    a() {
        return "method:a==>" + this.x;
    }
    b() {
        return "method:b==>" + this.y;
    }
}

注意:采用命名式或者匿名式的写法是可以写出立即执行的类的,例如

let res=new class{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    a(){
        return "method:a==>"+this.x;
    }
    b(){
        return "method:b==>"+this.y;
    }
}("测试a方法","测试b方法")

console.log(res.a())//method:a==>测试a方法

生成类的实例:在类名前加上new关键字
类的函数必须是类的实例来调用,类直接调用会报错,但是如果函数前加上static关键字,则是类可以直接调用该方法,而实例不能调用

//类的实例调用内部的方法
let test=new Res(1,2);
console.log(test.a());//method:a==>1
console.log(test.b());//method:b==>2

//类直接调用会报错
Res.a()//TypeError: Class constructor Res cannot be invoked without 'new'

注意 :类里写多个方法时,方法之间不要用逗号分隔,会报错,另外类的内部所有定义的方法都是不可枚举的,通俗点说就是不能遍历,然而通过prototype添加到类里的方法则是可以枚举的

class Res{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    a(){
        return "method:a==>"+this.x;
    }
    b(){
        return "method:b==>"+this.y;
    }
}

//Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组
console.log(Object.keys(Res.prototype));//[]

Object.assign(Res.prototype,{
    c(){},
    d(){}
})
console.log(Object.keys(Res.prototype));//["c","d"]

//Object.getOwnPropertyNames获取对象的所有属性,包括不可枚举的
console.log(Object.getOwnPropertyNames(Res.prototype));//["constructor","a","b","c","d"]

类的方法名可以采用表达式,例如:

let methodA=a;
class Res{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    [methodA](){
        return "method:a==>"+this.x;
    }
    b(){
        return "method:b==>"+this.y;
    }
}

class类的constructor

构造函数constructor是类的默认方法,一个类如果没有定义constructor方法,一个空的constructor方法则会被默认添加,而通过new生成类的实例时,constructor会被自动调用,constructor默认返回实例对象this,但是也可以指定返回另外一个全新的对象,例如:

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

class类的prototype和__proto__

什么是原型和原型链?
原型概念:在javascript中,每一个对象(除null)外都有__proto__属性,指向对应的构造函数的prototype属性。
原型链概念:
在javascript中,每个对象都有一个指向它的原型(prototype)对象的内部链接。每个原型对象又有自己的原型,直到某个对象的原型为null为止,组成这条链的最后一环。
要理解这两个概念,请看下面的例子:
//用new创建个字符串类型的对象
let bol=new Boolean(true);
console.log(bol);

WechatIMG736.jpeg

每当我们使用new语法的时候,对象就会拥有__proto__ 这个属性,该属性会指向new之后对应函数(也就是Boolean)的prototype属性,这个属性是Number共有属性,该共有属性会拥有诸多共有方法,也就是说:

bol.__proto__  === Boolean.prototype// true

如果bol的共有方法在Boolean里找不到,就会向上一层去找,也就是向Boolean.prototype.__proto__中去找,该对象对应的就是Object.prototype,也就是说:

bol.__proto__.__proto__===Object.prototype//true

如果依旧找不到呢?那就再去上一层找,也就是Object.prototype.__proto__,找出来的值为null,在javascript的数据结构中,这就是顶层了,也就是说:

bol.__proto__.__proto__.__proto__====Object.prototype.__proto__//true

Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

class类的继承(extends)以及super关键字

class之间可以通过extends关键字实现继承,例如:

class ParentClass {
    constructor(x) {
        this.x = x;
    }
    a() {
        console.log("method-a");
    }
}
class ChildClass extends ParentClass {
    constructor(x, y) {
        super(x);
        this.y = y
    }
    childA() {
        console.log("method-childA");
    }
}

上面的代码中类ChildClassl的constructor里调用了super方法,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象,所以要注意子类要先在constructor函数里调用super方法,才能在super方法下面使用this关键字,代码顺序不能写错
判断一个类是否继承了另一个类,可以用Object.getPrototypeOf(),例如:

Object.getPrototypeOf(ChildClass)===ParentClass//true

注意,如果一个类混入了多个类就不能用这个方法判断了。

super

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的constructor函数必须执行一次super函数,并且也只能在constructor函数里调用,在类的地方调用会报错;
第二种情况,super作为对象调用时,指向的是父类的原型对象,但是需要注意的是由于指向是父类的原型对象,所以实例是无法调用super的。

class类的实现多个类的继承(mixin)

mixin指的是,将多个类的接口混入另一个类,实现mixin代码如下:

function mix(...mixins) {
    class Mix {}

    for (let mixin of mixins) {
        copyProperties(Mix, mixin);
        copyProperties(Mix.prototype, mixin.prototype);
    }

    return Mix;
}

function copyProperties(target, source) {
    for (let key of Reflect.ownKeys(source)) {
        if (key !== "constructor" &&
            key !== "prototype" &&
            key !== "name"
        ) {
            let desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
        }
    }
}

//想要一次性继承多个类,可以将上面的mixin封装代码复制到自己的项目里
class A{
    a(){
        console.log("class A");
    }
}
class B{
    b(){
        console.log("class B");
    }
}
class C extends mix(A,B){
    c(){
        console.log("class C");
    }
}
let testMix=new C();
console.log(testMix.a());//class A
console.log(testMix.b());//class B
console.log(Object.getOwnPropertyNames(C.prototype.__proto__))//["constructor","a","b"],从打印结果就可以看到A类和B类的方法都被C类继承

class类的静态方法(static)

在类中定义的所有方法,都会被实例继承,但是如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。例如:

class Res {
    constructor() {}
    static testStatic(a) {
        return "method:testStatic==>" + a;
    }
}
let R = Res.testStatic("类调用静态方法测试");
console.log(R) //method:a==>类调用静态方法测试
let res = new Res().testStatic("实例调用静态方法测试");
console.log(res) // TypeError: (intermediate value).testStatic is not a function

class类的私有方法

私有方法就是仅供class类的内部使用;
第一种是方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,在类的外部,是可以调用到这个方法,所以不建议使用,例如:

class Res {
    constructor() {}
    _test(a) {
        return "method:test==>" + a;
    }
}
let res=new Res();
console.log(res.a("私有方法测试"))//method:test==>私有方法测试

第二种是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。

class Res {
    constructor() {}
    //公有方法
    testPublic(b) {
        wayPrivate.call(this,b)//使用call方法将wayPrivate变成了Res类的私有方法
        return "method:test==>" + b;
    }
}
function wayPrivate(b){
    console.log(b);
}
let res=new Res();
console.log(res.wayPrivate("测试私有方法"))//TypeError: res.wayPrivate is not a function

第三种是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。

let way1=Symbol("wayPrivate");
let str=Symbol("str");
class Res{
    wayPublic(){
        console.log("公有方法");
    }
    [way1](param){
        console.log("私有方法");
        this[str]=param;//用Symbol定义的str是私有属性
    }
}

class类的getter和setter

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

let _test="测试
class Res{
    constructor(x){
       
    }
    get val(){
        return _test;
    }
    set val(upd){
        _test=upd;
    }
}
let res=new Res();
console.log(res.val);//测试
res.val="重新setter val的值"
console.log(res.val)//重新setter val的值

class类的生成器(generator)

generator是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
函数在执行过程中,如果遇到return语句,return后面的代码就不会执行,函数末尾如果没有return,就是隐含的return undefined;
generator跟函数很像,generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。在class类里将*放在函数名前面,例如:

let res=new class{
    //虽然这个类里没有定义constructor,但是类会默认添加一个空的constructor
    *a(){
        yield "yield返回值1";
        yield "yield返回值2";
        return "return返回值3";
    }
}()

可以使用for.....of方法循环遍历generator对象,打印出来的结果就是yield返回值,例如:

for(let x of res.a()){
    console.log(x)
    //yield返回值1;
    //yield返回值2;
}

也可以使用next()的方法,每次遇到yield xxx;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果donefalse,则valueyield的返回值,如果donetrue,则valuereturn的返回值。当执行到donetrue时,这个generator对象就已经全部执行完毕,不要再继续调用next()了,不建议使用这种方法,因为这方法需要我们自己去通过done来判断循环是否结束,例如:

let r=res.a();
console.log(r.next());//{ value: 'yield返回值1', done: false }
console.log(r.next());//{ value: 'yield返回值2', done: false }
console.log(r.next());//{ value: 'return返回值3', done: true };

注意:使用next()方法执行generator时,要先将函数赋值给一个变量,然后通过这个变量来调用next()方法,如果直接通过函数来调用next()方法时,得到值永远都是第一个yield返回值,例如:

console.log(res.a().next());//{ value: 'yield返回值1', done: false }
console.log(res.a().next());//{ value: 'yield返回值1', done: false }
console.log(res.a().next());//{ value: 'yield返回值1', done: false }
阅读 300

重启大法真牛逼

111 声望
2 粉丝
0 条评论
你知道吗?

重启大法真牛逼

111 声望
2 粉丝
宣传栏