es现在稳步发展,每年都会发布一些新的语法或者api,今年es2022也发布了,包含Top-level await
,Error Case
等完结提案,其中关于class的提案有点多,包含Class Static Block
,Private instance methods and acdessors
,Class Public instance Fields & Private Instance Fields
,Static 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,父类和子类的初始化进程进行了两次赋值:
- this.x = 1;
- Base constructor;
- this.x = 2;
- Derived constructor; // 缺省
- 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方法,在内存上会有损耗。