class 类即构造函数的语法糖,它很甜,但规则也很多
这也是一直想输出 class
相关文章,一直延期的原因
但请遵循规则,玩得才开心
# 它只是语法糖
相对 ES5 定义类的实例,原型,静态属性、方法更加便捷
同时还可以很方便继承扩展 JS 内置类,比如 Array、Map 等
但无论怎样,本质依然是函数,所以 typeof 结果为 function
函数是一等公民,所以类也是一等公民
# 特殊的调用方式
不同于 ES5 构造函数,new 不 new 都可以正常调用
但 class 类只能通过 new
调用,还可以直接使用类的 constructor 构造函数调用,构造函数依然要通过 new 调用
class A {}
// 1.
new A()
// 2.
new A.constructor()
A() // 直接调用报错
2
3
4
5
6
7
8
9
# 在 class 块中定义方法
class 块指的是在 {}
中定义的
类块中定义的方法作为类的原型方法被共享,而且不可枚举
即方法的描述符对象的 [[enumerable]] 值为 false
而在块外定义的都是可枚举的
另外值得注意的是类定义块内的代码都在严格模式下执行,而且类定义不可提升调用
相似的,通过 static 定义的静态方法也不可枚举
console.log(A) // 报错
// age 不可枚举,但 showAge 可枚举
class A {
age() {}
}
A.prototype.showAge = function() {}
2
3
4
5
6
7
# 定义类实例、原型、静态的数据成员
两种方式,一种在类块外定义
一种在类块内定义,但不能定义原型上的数据成员
无论哪种方式定义的数据成员都是不可枚举的
// 类块外定义
class A {}
const insA = new A()
insA.a = 1
A.protottype.b = 1
A.a = 1
// 在块内直接定义,不能定义原型上的数据成员,只能通过外部定义
// 但原型上通过为共享的方法定义,不建议定义数据成员
class A {
a = 1 // 定义在实例上
static c = 1 // 定义在类上
}
2
3
4
5
6
7
8
9
10
11
12
13
# new.target 妙用
类若只能被继承,可以通过 new.target
实现
此时的类也是抽象基类
另外说一下,new.target
指向调用者本身(类)
class A {
constructor() {
console.log(new.target === A) // 当 new A() 直接调用结果是 A,当通过 B 继承调用时,结果是 B
if(new.target === A) {
throw new Error('A 只能被继承')
}
}
}
class B extends A {} // ok
new A() // 报错
2
3
4
5
6
7
8
9
10
11
12
# 子类的构造过程
在类模式中,子类的 this 是不完整的
如果显示声明了 constructor 必须调用 super 才能完善 this 构造
且 super 必须第一个调用,调用之前不能使用 this
如果没用调用 super 必须在构造函数中返回一个自定义对象,否则报错
class A {}
class B extends A {
constructor() {
console.log(this) // this 是不完整的,报错
super()
console.log(this) // this 整理完成,ok
}
}
new B()
2
3
4
5
6
7
8
9
10
11
其实说子类没有自己的 this 也是不过分的
因为它是将父类的实例属性、方法赋值给当前的上下文环境的,上下文环境通常叫 this
然后子类在这个 this 上进行修改,所以说子类的 this 是不完整的,它依赖于父类实例
所以在子类中使用 this 时一定要先使用 super
,完善子类 this
# 定义一个无名称的 class
并非所有的类都有名称,当然也并非所有的函数都有名称,比如匿名函数自执行时的名称为空字符串
// 无名的类
const arr = [class {}]
console.log(arr[0].name) // "" 空字符串
2
3
4
// 无名的函数
~function() {
console.log(arguments.callee.name) // ""
}()
2
3
4
5
# 特别的 super 关键字
super 本身是超级的意思,表示父类、超类
至于具体指父类的 constructor 构造函数、prototype 原型对象或父类本身
取决于子类使用 super 时所处的位置
它只能在子类中使用
另外 super 是关键字,独立访问报错
比如 console.log(super),就像 in
必须结合上下文才可以正常使用
- 在子类的 constructor 中当成函数调用,即
super()
,此时它表示父类的构造函数 - 在子类的原型方法当成对象使用,super.x(),此时表示父类的原型对象
- 在子类的静态方法当成对象使用,super.x(),此时表示父类本身
- 以上都是以
属性查询
的方式使用 spuer,但如果将 super 当成对象设置属性时,属性被设置到 this 或子类本身上,取决于 super 所处位置, 这点与心智相反 - delete super 上的属性报错
class A {
getName() {}
static getAge() {}
}
class B extends A {
constructor() {
super() // super 此时表示 A.constructor
super.x = 1 // super 当成对象赋值时 super 表示子类实例对象,即 this
delete super.x // 报错
}
showName() {
super.getName() // super 此时表示 A.constructor.prototype
super.y = 2
}
static showAge() {
super.getAge() // super 此时表示 A 本身
super.z = 3 // super 表示子类本身
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 关于父类方法中 this 指向问题
如果是父类直接 new 调用,指向父类实例或父类,
如果被继承,指向子类的实例或子类
决于 this 是在原型方法中还是在静态方法中
class A {
getName() {
console.log(this)
}
static getAge() {
console.log(this)
}
}
class B extends A {
showName() {
super.getName()
}
static showAge() {
super.getAge()
}
}
const a = new A()
a.getName() // 指向实例 a
A.getAge() // // 指向父类 A
const b = new B()
b.getName() // 继承调用,指向实例 b
B.getAge() // 继承调用,指向子类 B
b.showName() // super 调用,指向实例 b
B.showAge() // super 调用,指向子类 B
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 小结
以上的规则中 super
规则理解有点心智负担的
以属性查询
的方式使用,指向父类构造器、父类原型、父类本身,取决于使用位置
如果以赋值
的方式使用,指向子类 this 实例本身、子类
类块中方法的 this 指向,非继承调用或 super 调用指向父类实例或父类本身,相反指向子类实例或子类本身
new.target
可以实现抽象继承类
并非所有类都有名称,有的可能返回是空字符串
扫一扫,微信中打开