都es2022了,你知道class现在长啥样么

877 阅读4分钟

es现在稳步发展,每年都会发布一些新的语法或者api,今年es2022也发布了,包含Top-level awaitError Case等完结提案,其中关于class的提案有点多,包含Class Static BlockPrivate instance methods and acdessorsClass Public instance Fields & Private Instance FieldsStatic class fields and private static methods,先不展开细说了,看看把这些语法都用上之后class是什么样子的吧:

class H {
    #a='a';
    b='b';
    #_x = 'x';
    #getA() {
            return this.#a;
    }
    getB() {
            return this.b;
    }
    static c = 'c';
    static getC() {
            return H.c;
    }
    static #d = 'd';
    static #getD() {
            return H.#d;
    }
    static #f;
    static #getF;
    static {
            this.e = 'e';
            this.#f = 'f';
            this.getE = () => {
                    return this.e;
            };
            this.#getF = () => {
                    return this.#f;
            }
    }
    constructor() {
            this.b = 'B';
            this.#a = 'A';
            this.#_x = 'X';
    }
    get #x() {
            return this.#_x;
    }
    set #x(x) {
            this.#_x = `${x}-${x}`;
    }
    get X() {
            return this.#x;
    }
    set X(x) {
            this.#x = x;
    }
}

上面的代码没啥实际意义,但是看下来主要有两个东西比较特殊,#static,其中#代表的是私有,static就是静态喏,再组合class现有的一些语法就形成了上面形式的代码了,下面就展开聊聊这些正式的提案。

#

#就代表private,即私有,通过#可以定义私有变量和方法了,在以前要实现这些效果,还是挺费劲的,现在就很简单了,看个简单的例子:

class A {
    #name = 'a';
    getName() {
        return this.#name;
    }
}
const a = new A();
console.log(a.getName()); // 'a'
console.log(a.#name); // Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class

a实例中的私有变量不能直接访问,只能通过暴露出来的公共方法来间接访问,当然#还可以修饰方法,这样方法就变得私有不会被暴露出去了;

class A {
    #privateMethod() {
        return 'a';
    }
    callPrivateMethod() {
        return this.#privateMethod();
    }
}
const a = new A();
console.log(a.callPrivateMethod()); // a

如果私有变量如果没有提前声明,但是在构造函数中进行了初始化,那么这是不容许的;

class A {
    #addr;
    constructor() {
        this.age = 10; // ok
        this.#addr = 'ww'; // ok
        this.#name = 'a'; // Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
    }
}

关于#还是挺简单的,还需要说明的一点是,getter/setter同样可以结合#使用,就像文章开头的示例代码;

static

静态肯定就是跟class关联,而不是类实例的属性或者方法了,这个时候我们定义的类就是一个存放fields/methods的容器。static可以细分为三种情况:

1.static + public fields/methods

class A {
    static count = 0;
    static addCount() {
        E.count++;
    }
}
E.count; // 0
E.addCount(); // 1

2.static + private fields/methods

class A {
    static #name = 'a';
    static getName() {
        return A.#name;
    }
    static #getAge() {
        return 10;
    }
    static getAge() {
        return A.#getAge();
    }
}
A.getName(); // a
A.getAge(); // 10

只要是通过#修饰的静态属性/方法,就不能直接访问,这是私有特性所决定;

3.static blocks

static blocks就是static {...}形式,他的出现是为了解决什么问题呢,提案的作者给出了一个例子:

class C {
  static x = ...;
  static y;
  static z;
}

try {
  const obj = doSomethingWith(C.x);
  C.y = obj.y
  C.z = obj.z;
}
catch {
  C.y = ...;
  C.z = ...;
}

// with static blocks:
class C {
  static x = ...;
  static y;
  static z;
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}

如果要对多个static属性、方法进行初始化,那么提供一个block进行统一处理会带来一些便利,从上面的对比中看出,第一段代码将内部逻辑都暴露在calss外面了,这样显然是应该避免的。 还有其他注意的一些要点:

  • 1.static {} 创建了一个新的词法作用域,被外部的class作用域嵌套;
  • 2.一个class可以定义多个static {};
  • 3.static {}不能和装饰器混用;
  • 4.static {}内部的this指向的是class构造器对象;
  • 5.不能访问agruments,否则会报语法错误;
  • 6.不能执行super(),否则会报语法错误; 还有其他一些说明,有兴趣可以查看提案文档

其他

初始化的顺序

class Base {
    x = 1;
    constructor() {
        console.log(this.x); // 1
    }
}
class Derived extends Base {
    x = 2;
}
const d = new Derived();

父类实例变量先初始化,然后是父类构造函数,然后才是进入子类初始化,因此Base里面会打印1,而最终d实例中的x是2,因为对于变量x,父类和子类的初始化进程进行了两次赋值:

  1. this.x = 1;
  2. Base constructor;
  3. this.x = 2;
  4. Derived constructor; // 缺省
  5. return this; // { x: 2 }

继承与遮蔽

class Base {
    x = 1;
    #y = 2;
    z = 3;
}
class Derived extends Base {
    #y;
    z;
    getY() {
        return this.#y;
    }
}
const d = new Derived();
d.x; // 1
d.getY(); // undefined
d.z; // undedefined

由于父子类中对变量进行多次赋值,因此对于#y和z来说,由于Derived类中的值默认是undefined,因此就如结果所示;

箭头函数不能继承

class Base {
    foo() {}
    bar = () => {};
}

class Derived extends Base {
    myFoo() {
        super.foo(); // ok
        super.bar(); // Uncaught TypeError: (intermediate value).bar is not a function
    }
}
const d = new Derived();
d.myFoo();

Base类中的bar方法只存在于Base类的实例上,而不能被子类访问,因为bar方法不会被放在Base.prototype上,而foo则会,因此它可以被子类访问。那为什么会有这种限制呢,应该是因为箭头函数的this问题吧。还有一点就是每个Base实例都会持有独立的bar方法,在内存上会有损耗。