Es6的Class基本语法

130 阅读9分钟

1.类的由来

javascript 语言中,生成实例对象的传统方法是构造函数。


function P(x, y) {
    this.x = x;
    this.y = y;
    }
 P.prototype.toString = function() {
     return `($(this.x),$(this.y))`;
     };
     
 var p = new P(1,2)

上面这种写法跟传统的面向对象语言(比如C++和java)差异很大,很容易让我学习这门语言感到困惑

ES6的创建类的方式:

class P {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        }
        
    toString() {
        return `($(this.x)+$(this.y))`
        }
        
}
// 原型链添加方法
P.prototype.toValue = function() {console.log(this.x)}

// 使用的时候

const p = new P(1,3);
p.toString()
P.prototype.constructor === P  // true

2. constructor()方法

construction是类的默认方法,当类被实例化时自动执行 类的属性

class Inc {
    _conut = 0;
    get value() {
        console.log('Getting the current value');
        return this._count;
        
    }
    
    increment() {
        this._count++;
        // 还可以,不知道继承后能不能
        super._count++
    }
    
}

// 实例属性_count 与取值函数 value() 和increments()方法,
//处于同一个层级,这时,不需要在实例属性前面加上this

3.取与存值的函数

class MyClass {
    consotructor() {}
    get prop() { return 'getter'}
    set prop(value){ console.log('setter ' + value);}
}
let inst = new MyClass();
inst.prop = 123
//VM3253:4 setter 123
123
inst.prop
//'getter'
descrtiptor = Object.getOwnPropertyDescriptor(MyClass.prototype, 'prop')
//{enumerable: false, configurable: true, get: ƒ, set: ƒ}

"get" in descrtiptor  //true
"set" in descrtiptor  // true
// 存值函数和取值函数都是定义在prop属性和描述对象上面,这与ES5完全一致

这样实例化后的prop既能存又能付值 是不是后期可以用来判断这个值复值的时候进行判断,或者其他操作,取的时候也可以额外添加一些功能和判断

4.Class表达式

与函数一样,类也能使用表达式来定义。

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

let inst = new MyClass();
inst.getClassName();
Me.name

5. 静态方法

class Foo {
    static count = 1;
    static classMethod() {
        console.log('I im startic')
    }
    sum() {Foo.count++; console.log(Foo.count)};
    static sum() {console.log(this.count)};
    // Foo.sum() 输出的是1每次都是1,然的说this在静态方法中指向的是类吗?而不是实例。
    
    // 静态属性每次输出都是有记忆功能的。如何的实例
}


Foo.sum()

这个例子可以看出,静态方法的this是类的,而不是实例化的,而且静态方法和类方法是可以同名的。 静态方法可以调用静态方法。

class Foo {

    static classMethod() {
        console.log('hello')
        return 'hello'
    }
}



class Bar extends Foo{
    // 继承Foo父类

    static classMethod() {
        // 重写静态方法
        // 也可以先执行父类的静态方法
        super.classMethod()
        return 'world'
    }
}


Bar.classMethod()
// 这个例子告诉了我们,继承父类的子类,也继承了父类的静态方法
// 也可以重写之前,执行父类方法super

6.私有方法和属性

私有方法和属性,是只能在类的内部访问的方法和属性,外部不能访问。这时常见的需求,有利于代码的封装,但很早的ES6 不提供,只能通过变通方法模拟实现。

class IncreasingConter {
    
    #x;
    constructor(x=0) {

        this.#x = +x;
        // 这里+x可以把x自动转成数值
        
    }

    get x() {
        return this.#x;
    }

    set x(value) {
        this.#x = +value
    }
}

class IncreasingConter {
    
    #x;
    constructor(x=0) {
        this.#x = +x;
    }

    get x() {
        return this.#x;
    }

    set x(value) {
        this.#x = +value
    }
    #sum () {
        return this.#x + 3;
        
    }
    printSum() {
        console.log(this.#sum())
    }

    static getCountValue(instance){
        return instance.#x
    }

    // 静态方法取私有属性,必须传一个实例


    static #compute() {
        // 这是一个私有的静态方法
        return IncreasingConter.#x;
    }
}


私有属性不存在时,在哪里访问都会报错,带有#的私有属性或方法,在外部访问不了只能在类里访问,私有属性也可以用getter和seter方法,私有属性不限于this引用,只要是在类的内部,或者实例。

7.in运算

判断私有属性时,in 只能用在类的内部

8. 静态块

静态块的一个问题是,如果它有初始化逻辑,这个逻辑要么写在内的外部,要么写在constructor()方法里面。 使用静态块,在类生成时候且只运行一次,主要作用是对静态属性进行初始值化,以后,新建类的实例时,这个块就不在运行了。


class C {
    static x =223;
    static y;
    static z;
    constructor() {
        // 这样初始化值会导致每次创建实例的时候都会调用一次
        const obj = doSomethingWith(C.x)
        C.y = obj.y;
        C.z = obj.z;
    }


    static sum() {
        // 这样你要手动来调用一次。
        const obj = doSomethingWith(this.x)
        this.y = obj.y;
        this.x = obj.x;
        
    }

    // 使用静态块的方法
    static {
        try{
            const obj = doSomethingWith(this.x)
            this.y = obj.y;
            this.x = obj.x;
        }
        catch{
            this.y = ...;
            this.x = ...;
        }
    }
}


try {
    // 这样到外部创建也不方便。
    const obj = doSomethingWith(C.x)
    C.y = obj.y;
    C.z = obj.z;
}catch {
    C.y = ...;
    C.z = ...;
}
//除了静态块属性的初始化,还可以将私有属性与类的外部代码分享。
class A {
    #x = 1;
    static {
        getC = obj => obj.#x;
        setC = obj => obj.#x = 23;
    }
}
console.log(getC(new A())) // 1

9 类的注意点

1.严格模式
类和模块的内部,默认就是严格模式,所以不需要使用 use strict
2.不存在提升
new Foo() //ReferenceError
class Foo {}
使用在前,定义在后是会报错的
3.name属性
由于本质上s5和s6的构造函数的一层保障,所以函数的许多特性都被class继承,包括name
4.如果某个方法之前加上了星号(*)就表示该方法是一个Generator函数
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明

10 类的继承

Class类是可以通过 extends 关键字实现继承,让子类继承父类的属性,方法。

class A {
}

class B extends A {}
// A是父类,B是子类,B继承了A父类的方法和属性,等同于复制了父类

子类继承父类必须要在构造方法constructor()方法内调用super()方法,如果不调用子类将无法找到this 为啥要调用super,因为es6不同es5的继承机制,es5是先创建独立的子类实例对象,再将父类的方法和属性添加到这个对象上,既“实例在前,继承在后”。而es6的继承机制是,先将父类的属性和方法,加到一个空对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后” 如果不调用super方法,这一步就会生成一个继承父类的this对象。另外要注意的是,只有在先调用了super()方法之后才能使用this关键字,否则会报错,这时因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。

class P { }

class C extends P{
    constructor(x, y, color) {
        this.color = color; /// 错误的方式❌这里会报错。ReferenceError
        super(x, y)
        this.color = color;  // 正确✅
    }



    toString() {
        return this.color + ' ' + super.toString();
            
    }
    
}

如果子类没有定义constructor()方法,这个方法会默认添加,并且还会调用super()方法,不管有没有显式定义,任何一个子类都有constructor()方法。

11. 私有属性和私有方法的继承。

子类无法访问父类的私有属性和方法,私有的属性和方法都只能再定义他的class类中使用。

 class A{
     #a = 1; // 私有属性
     #m() {
         console.log('hello')
         };
         
      getA() {
          return this.#a;
          
      }
     }
     
 class B extends A {
     consrtuctor() {
         super()
         // 访问父类私有属性
         console.log(this.#a) // 报错
         this.#m() // 报错
         //正确的访问方式
         console.log(this.getm())  // 1
       }
     }

 }

12. 静态属性和静态方法的继承

class A {
    static foo = 100;
    static obj = {n: 200} // 静态对象
    
}

class B extends A {
    constructor() {
        super()
        B.foo--; // A.foo=100,B.foo=99
        B.obj.n++;  // a.obj.n =201,b.obj.n=201
        
    }
}

// 静态属性和方法都是会继承给子类的,但是静态属性由于是浅拷贝,拷贝父类静态属性的值,当属性不是一个对象时候,那么继承的这个静态属性是两个彼此独立的。
// 如果是一个对象,由于浅拷贝是复制对象的内存地址,所以一个静态对象改变则都会变

13. Object.getPrototypeOf()方法可以用来从子类上获取父类,可以用来判断一个类是否继承了另一个类

14. super 关键字

super 这个关键字,即可当作函数使用,又可以当对象使用。这两种情况下,它的用法完全不同。 第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super()函数。

class A {
    constructor(){
        console.log(new.target.name);
    }
}
class B extends A {
    constructor(){super()}
}

new A(); // A
new B(); // B
// 虽然super是代表构造父类的方法,但是它内部的this内部指向的是B
class A {
    name = 'a';
    constructor(){
        console.log('My name is ' + this.name);
    }
}

class B extends A {
    name = 'b'
    constructor(){
        super()
    }

}

const b = new B(); // 输出 My name is a 

// 为啥是a呢?不是说super执行的this指向的是实例类吗?
// 这是因为在子类构造方法执行时,子类的属性和方法还没绑定到this,所以如果存在同名属性,此时拿到的事父类的属性

如果super作为函数使用,它只能在子类构造函数中使用,在其他地方使用就会报错 第二种情况:super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中指向父类

class A {
    constructor() {
    }
    s() {
        console.log('a 的实例方法')
    }
    static s() {
        console.log('a 的静态方法')
    }
}

class B extends A {
    constructor() {
        super();
    }
    p() {
        super.s()
    }
    static p() {
        super.s();
    }
}
new B().p() // a 的实例方法
B.p() //a的静态方法

注意:由于super 指向父类的原型对象,所以定义在父类的实例方法或属性,时无法通过super 调用的。

class A {
    constructor(){
        this.a = 2;
    }
}

class B extends A {
    get m() {
        return super.a;  // a is not defind
    }
    

}

let b = new B();
b.m() // 报错
// 我们可以使用原型连来定义属性,这样super就能使用了
class A {
}
A.protostyle.x = 2;
class B extends A {
    get m() {super.x} // 正常输出2
}

es6规定,在子类普通方法中通过 super 调用父类的方法时候,方法内部的this指向的是当前子类的实例

class A {

    constructor() {
        this.p = 2
    }
    c() {
        console.log('super c this'+ this.p)
    }
    
}

class B extends A {
    constructor() {
        super()
        this.p = 3
    }
    a = 'b'
    get m() {
        return super.c();
    }
}
const b = new B()
b.m // 输出 super c this 3 这里是实例子类的p
class A {

    constructor() {
        this.p = 2
    }
    c() {
        console.log('super c this'+ this.p)
    }
    
}

class B extends A {
    constructor() {
        super()
        // 由于super指向的是子类实例所以通过super赋值的时候是修改子类的属性,
        // super,同等于this 赋值时同等于this
        this.p = 1;
        super.p = 4;
        console.log(super.x) // undefined 因为相当于 A.prototype.p 父类中没有原型属性p所以返回undefind
    }
    a = 'b'
    get m() {
        return super.c();
    }
}

super对象 在子类静态方法中指向的是父类 在子类的静态方法中指向的是父类,在子类实例方法中指向的是父类的原型,prototype 另外在子类静态方法中调用父类的方法时,this指向的是子类,而不是子类的实例。 注意,使用super时,必须显示指定作为函数还是对象,如果不指定则报错 console.log(super) // 报错

15 Mixin模式

Minxin 指的是多个对象合成一个新的对象,新的对象具有各个组成成员的接口,它的最简单实现如下

const a = {
    a: 'a'
};

const b = {b:'b'}
const c = {...a, ...b}