类是一种“特殊的函数”。
class C {
}
typeof C; // "function"
就像函数声明定义方式和函数表达式定义方式一样,类的定义方式也有两种:类声明定义方式和类表达式定义方式。
1.类声明
class 类名 {
//类体
}
2.类表达式
注意:命名类表达式的右侧依然是类表达式,不是类声明。
//命名类表达式
{ let | const } C = class C2 {
//类体
};
//匿名类表达式
{ let | const } C = class {
//类体
};
3.类声明提升(Hoisting)
类声明、类表达式都不支持提升。
4.类名
类名是第一次跟类定义绑定的类名或变量或常量。
4.1类声明的类名
class C {}
console.log(C.name); // C
console.log( (class C {}).name ); // C
类声明是 Function
类型的实例,类名就是存储这个实例的变量,因此在赋值后可以通过重新赋值更改其值。
class C {
f() {
return 1;
}
}
console.log(new C().f()); // 1
C = 2;
console.log(new C().f()); // 报错
console.log(C); // 2
4.2类表达式的类名
//命名类表达式
let C = class C2 {};
console.log(C.name); // C2
console.log( (class C2 {}).name ); // C2
//匿名类表达式
let C = class {};
console.log(C.name); // C
console.log( (class {}).name ); // ""(空字符串)
注意:命名类表达式的类名 C2
的作用域仅仅为类体,不可以在类体外使用。
let C = class C2 {
f() {
return 1;
}
};
console.log(new C().f()); // 1
console.log(new C2().f()); // 报错
5.类引用表达式
类名;
6.类定义作为值
6.1作为值赋给变量
class C {
f() {
return 1;
}
}
console.log(new C().f()); // 1
let B = C;
console.log(new B().f()); // 1
6.2作为函数的实参或返回值
//作为实参
function callSomeClass(someClass) {
return new someClass().f();
}
class C {
f() {
return 1;
}
}
console.log(callSomeClass(C)); // 1
//作为返回值
function returnSomeClass() {
class C {
f() {
return 1;
}
}
return C;
}
let C2 = returnSomeClass();
console.log(new C2().f()); // 1
7.成员名
支持类的成员:静态字段、静态访问器属性、静态方法、实例字段、实例访问器属性、实例方法。
支持对象字面量的成员:数据属性、访问器属性、方法。
成员名的类型只可以为 字符串类型(包括空字符串) 或者 符号类型。
注意:此处成员名用于“成员声明”的时候,而不是“访问成员”的时候。
//类
class C {
"a" = 1; // 语法糖:a = 1;
"1" = 2; // 语法糖:1 = 2;
"first name" = 3; // 无语法糖
"first-name" = 4; // 无语法糖
}
//对象字面量
let o = {
"a": 1, // 语法糖:a: 1,
"1": 2, // 语法糖:1: 2,
"first name": 3, // 无语法糖
"first-name": 4 // 无语法糖
};
实际上,比较两个成员名是否相等采用的是相等 ==
运算符。先进行强制类型转换为字符串类型,再比较两个成员名是否相等,如果相等,则返回 true
。比如上面的示例:
//类
"a" = 1; // 语法糖:a = 1;
"1" = 2; // 语法糖:1 = 2;
//对象字面量
"a": 1, // 语法糖:a: 1,
"1": 2, // 语法糖:1: 2,
8.可计算名
可计算名语法用于使用表达式的返回值作为成员名。
支持类的成员:静态字段、静态访问器属性、静态方法、实例字段、实例访问器属性、实例方法。
支持对象字面量的成员:数据属性、访问器属性、方法。
[表达式]
方括号 []
内可以为任何 JavaScript 表达式,表达式的返回值的类型只可以为 字符串类型(包括空字符串) 或者 符号类型。
注意:可计算名语法用于“成员声明”的时候,而不是“访问成员”的时候。
//类
let name = "a";
class C {
[name] = 1;
}
let o = new C();
console.log(o); // { a: 1 }
//对象字面量
let name = "a";
let o = {
[name]: 1
};
console.log(o); // { a: 1 }
//对象字面量解构赋值
let name = "a";
let { [name]: b } = { a: 1 };
console.log(b); // 1
let { [name]: a } = { a: 1 };
console.log(a); // 1
//不支持此语法(注意:当属性名与变量名相同时,不支持属性简写语法)
let { [name] } = { a: 1 };
9.访问成员
[]
方式用于使用表达式的返回值作为成员名。
//类
类名[静态字段名]
类名[静态访问器属性名]
类名[静态方法名]()
对象名[实例字段名]
对象名[实例访问器属性名]
对象名[实例方法]()
//对象字面量
对象名[数据属性名]
对象名[访问器属性名]
对象名[方法名]()
方括号 []
内可以为任何 JavaScript 表达式,表达式的返回值的类型只可以为 字符串类型(包括空字符串) 或者 符号类型。
//类创建对象方式
let name = "b";
class C {
"a" = 1;
"1" = 2;
"first name" = 3;
"first-name" = 4;
[name] = 5;
}
let o = new C();
o["a"]; // 语法糖:o.a;
o["1"]; // 语法糖:o[1];
o["first name"]; // 无语法糖
o["first-name"]; // 无语法糖
o["b"]; // 语法糖:o.b;
o[name];
//对象字面量创建对象方式
let name = "b";
let o = {
"a": 1,
"1": 2,
"first name": 3,
"first-name": 4,
[name]: 5
};
o["a"]; // 语法糖:o.a;
o["1"]; // 语法糖:o[1];
o["first name"]; // 无语法糖
o["first-name"]; // 无语法糖
o["b"]; // 语法糖:o.b;
o[name];
//Object()构造函数创建对象方式
let name = "b";
let o = new Object();
o["a"] = 1; // 语法糖:o.a = 1;
o["1"] = 2; // 语法糖:o[1] = 2;
o["first name"] = 3; // 无语法糖
o["first-name"] = 4; // 无语法糖
o["b"] = 5; // 语法糖:o.b = 5;
o[name] = 6;
.
方式只支持 JavaScript 标识符。
//类
类名.静态字段名
类名.静态访问器属性名
类名.静态方法名()
对象名.实例字段名
对象名.实例访问器属性名
对象名.实例方法名()
//对象字面量
对象名.数据属性名
对象名.访问器属性名
对象名.方法名()
10.属性简写
注意:类没有属性简写语法。
在对象字面量创建对象方式中,当属性名和代表属性值的变量名相同时,可以省略属性名以及紧跟在属性名后面的冒号,只使用变量名。
let personName = "张三";
//属性名与变量名不相同(前面name是属性名,后面personName是变量名)
let person = {
name: personName
};
console.log(person); // { name: "张三" }
let name = "张三";
//属性名与变量名相同(前面name是属性名,后面name是变量名)
let person = {
name: name
};
//省略属性名以及紧跟在属性名后面的冒号,只使用变量名。
let person = {
name
};
console.log(person); // { name: "张三" }
当对象字面量作为函数的返回值时,也支持属性简写。
function makePerson(name) {
return {
name
};
}
let person = makePerson("张三");
console.log(person); // { name: "张三" }
11.访问器属性(Accessor Property)
11.1类
class ClassWithGetSet {
//静态getter
static get staticGetter() {
//访问器属性体
}
//静态setter
static set staticSetter(形参) {
//访问器属性体
}
//实例getter
get instanceGetter() {
//访问器属性体
}
//实例setter
set instanceSetter(形参) {
//访问器属性体
}
}
11.2对象字面量
let 对象名 = {
//实例getter
get instanceGetter() {
//访问器属性体
},
//实例setter
set instanceSetter(形参) {
//访问器属性体
}
};
12.静态初始化块(Static Initialization Block)
静态初始化块是用于类的初始化。
一个类非必须有静态初始化块。如果一个类有多个静态初始化块,后声明的静态初始化块不会覆盖之前声明的静态初始化块,会按声明时的顺序依次执行。
class 类名 {
static {
//静态初始化块体
}
}
class ClassWithStaticInitializationBlock {
static staticProperty1 = 'Property 1';
static staticProperty2;
static {
this.staticProperty2 = 'Property 2';
}
}
console.log(ClassWithStaticInitializationBlock.staticProperty1); // Property 1
console.log(ClassWithStaticInitializationBlock.staticProperty2); // Property 2
13.构造函数
构造函数是用于对象的初始化。
一个类非必须有构造函数。如果一个类没有构造函数,相当于定义了一个函数体为空的构造函数。如果一个类有构造函数,则只能有一个构造函数,否则会报错。
class 类名 {
constructor() {
//构造函数体
}
}
14.继承
一个子类只可以直接继承自一个父类,不可以直接继承自多个父类。
子类会直接继承父类中可继承的成员,间接继承祖父类中可继承的成员,以此类推,直至没有可继承的成员。
继承支持静态字段、静态访问器属性、静态方法、静态初始化块、实例字段、实例访问器属性、实例方法、构造函数。
注意:从父类中继承过来的成员无需在子类中重新声明,否则会发生覆盖。
//类声明
class 子类名 extends 父类名 {
//子类体
}
//类表达式
//命名类表达式
{ let | const } C = class C2 extends 父类名 {
//子类体
};
//匿名类表达式
{ let | const } C = class extends 父类名 {
//子类体
};
15.覆盖(Override)
如果从父类中继承过来的成员在子类中重新声明,此时相当于在子类中同时声明了两个相同名称的成员,则在子类中重新声明的成员会覆盖从父类中继承过来的成员。
注意:只需要成员名称相同,就会发生覆盖。
注意:关于支持覆盖的成员,参考继承章节。
注意:覆盖并不会影响父类中原来的成员。
class A {
f() {
return 1;
}
}
class B extends A {
f() {
return 2;
}
}
console.log(new B().f()); // 2
16.抽象类
JavaScript 语言不支持抽象类。
17.创建对象
17.1类创建方式
//无实参(因为没有实参传递,则可以省略圆括号,一般不推荐省略)
{ let | const } 对象名 = new 类名();
//有实参
{ let | const } 对象名 = new 类名(实参);
17.2对象字面量创建方式
注意:赋值运算符 =
右侧期望的是表达式,所以右侧的花括号 {}
处于表达式上下文,表示的是对象字面量表达式的开始和结束。如果花括号 {}
处于语句上下文,则表示的是块语句的开始和结束。
注意:最后一个属性(或方法)后面允许有逗号 ,
,这样要添加一个新属性(或新方法)时很方便。
{ let | const } 对象名 = {
数据属性名: 值,
方法名: function() {
//方法体
},
//方法定义简写
方法名() {
//方法体
}
};
17.3Object()构造函数创建方式
Object()
构造函数创建方式目前已很少使用,而类创建方式和对象字面量创建方式非常流行。
//等同的对象字面量创建方式{ let | const } 对象名 = {};,但对象字面量创建方式实际上并不会调用Object()构造函数。
{ let | const } 对象名 = new Object();
//访问成员
对象名.属性名 = 值;
对象名.方法名 = function() {
//方法体
};
18.super关键字
super
关键字用于在子类中调用父类的成员。
支持类的成员:静态字段、静态访问器属性、静态方法、实例访问器属性、实例方法、构造函数,不支持静态初始化块、实例字段。
支持对象字面量的成员:数据属性、访问器属性、方法。
注意:当 super
关键字用于构造函数时,父类的构造函数必须被子类的构造函数覆盖,super
关键字必须在子类的构造函数体内使用,且 super
关键字必须在 this
关键字之前使用。
//调用父类的构造函数
//无实参(注意:圆括号不可以省略)
super()
//有实参
super(实参)
//调用父类的成员
//[]方式
super[静态字段名]
super[静态访问器属性名]
super[静态方法名]()
super[实例访问器属性名]
super[实例方法名]()
//.方式
super.静态字段名
super.静态访问器属性名
super.静态方法名()
super.实例访问器属性名
super.实例方法名()
//调用父对象字面量的属性和方法
//[]方式
super[数据属性名]
super[访问器属性名]
super[方法名]()
//.方式
super.数据属性名
super.访问器属性名
super.方法名()
19.this关键字
this
关键字的指向问题非常复杂,建议遇到具体问题时具体分析。
原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/javascript/javascriptlang/4729.html