五、ES7~ES13的特性
# 1、ES7新增特性解析
# 1.1.ES7 - Array.prototype.includes()
- 在ES7之前,如果我们想判断一个数组中是否包含某个元素,需要通过 indexOf 获取结果,并且判断是否为 -1。
- 返回的值为:-1则该数组不包含该元素;
- 返回的是大于等于0的索引值,则说明该数组包含该元素,拿到的返回值是该元素在数组中的索引值
- 在ES7中,我们可以通过includes来判断一个数组中是否包含一个指定的元素,根据情况,如果包含则返回 true,否则返回false。
- includes语法:
arr.prototype.includes(valueToFind[,formIndex])
- valueToFind参数一:要搜索的元素
- formIndex参数二:要从数组中哪个索引值开始往后面搜索
- includes语法:
const arr = ['nba','cba','ncaa']
const str = 'cba'
const str1 = 'bba'
console.log(arr.indexOf(str))//1 数组包含该元素,且该元素在数组中的索引为1的位置
console.log(arr.indexOf(str1))//-1 数组不包含该元素
console.log(arr.includes(str))//true 数组包含该元素
console.log(arr.includes(str1))//false 数组不包含该元素
console.log(arr.includes(str,2))//false 从数组中索引值为2的位置开始向后搜索,没有找到该元素,所以返回false
2
3
4
5
6
7
8
# 1.2.ES7 – 指数exponentiation运算符
- 在ES7之前,计算数字的乘方需要通过 Math.pow 方法来完成。
- 在ES7中,增加了
** 运算符
,可以对数字来计算乘方。
const result1 = Math.pow(2,10)
const result2 = 2 ** 10
console.log(result1,result2)//1024 1024
2
3
# 2、ES8新增特性解析
# 2.1.ES8 - Object.values()
- 之前我们可以通过 Object.keys(obj) 获取一个对象所有的key
- 在ES8中提供了 Object.values(obj) 来获取所有的value值:
const obj = {
name:'kobe',
age:39
}
const str = "lyk"
// ES8之前想一次获取对象所有属性对应的value值
console.log(Object.keys(obj))//[ 'name', 'age' ]
console.log(Object.keys(str))//[ '0', '1', '2' ]
const values = []
for(const key of Object.keys(obj)) {
values.push(obj[key])
}
console.log(values)//[ 'kobe', 39 ]
// ES8直接用Object.values(obj)来获取
console.log(Object.values(obj))//[ 'kobe', 39 ]
console.log(Object.values(str))//[ 'l', 'y', 'k' ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.2.ES8 - Object.entries()
- 通过 Object.entries(obj) 可以获取到一个数组,数组中会存放可枚举属性的键值对数组。
- 可以针对对象、数组、字符串进行操作;
- entries中文意思是:条目
const obj = {
name:'kobe',
age:39
}
const str = "lyk"
const arr = ['nba','cba']
// 1.如果是一个对象
console.log(Object.entries(obj))//[ [ 'name', 'kobe' ], [ 'age', 39 ] ]
for(const entry of Object.entries(obj)) {
const [key,value] = entry
console.log(key,value) //name kobe //age 39
}
// for(const [key,value] of Object.entries(obj)) {
// console.log(key,value)
// }
// 2.如果是一个数组
console.log(Object.entries(arr))//[ [ '0', 'nba' ], [ '1', 'cba' ] ]
// 3.如果是一个字符串
console.log(Object.entries(str))//[ [ '0', 'l' ], [ '1', 'y' ], [ '2', 'k' ] ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.3.ES8 - String Padding 字符串填充(String的实例方法:padStart 和 padEnd)
某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8中增加了 padStart 和 padEnd 方法,分别是对字符串的首尾进行填充的。(前面写秒杀活动倒计时功能 有用到过padStart方法)
String.prototype. padStart(targetLength [, padString])
从当前字符串的左侧开始填充。参数1:targetLength
:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。参数2:padString
(可选):填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的默认值为 " "
String.prototype. padEnd(targetLength [, padString])
从当前字符串的末尾(右侧)开始填充;参数可参照上面的padStart方法
const str = "Hello World"
const strStart = str.padStart(16,'a96')
console.log(strStart)// a96a9Hello World
console.log(str)//Hello World
const str1 = "Hello World"
const str1End = str.padEnd(15,'z52')
console.log(str1End)// Hello Worldz52z
console.log(str1)//Hello World
2
3
4
5
6
7
8
9
- 我们简单具一个应用场景:比如需要对身份证、银行卡的前面位数进行隐藏
const identityStr = '362426198908963664'
const identityStrLength = identityStr.length
const lastFourStr = identityStr.slice(-4)
const identityStrHidden = lastFourStr.padStart(identityStrLength,'*')
console.log(identityStrLength)//18
console.log(lastFourStr)//3664
console.log(identityStrHidden)//**************3664
2
3
4
5
6
7
# 2.4.ES8 - Trailing Commas(尾随逗号)
- 在ES8中,我们允许在函数定义和调用时多加一个逗号:()
function foo(a,b,) {
console.log(a,b)
}
foo(a,b,)
2
3
4
# 2.5.ES8 - Object.getOwnPropertyDescriptors () 获取对象的属性描述符信息
- Object.getOwnPropertyDescriptors :获取对象所有属性的 数据/存取 属性描述符
const obj = {
name:'kobe'
}
// 1.获取obj所有属性的(数据/存取)属性描述符
console.log(Object.getOwnPropertyDescriptors(obj))
/*{
name: {
value: 'kobe',
writable: true,
enumerable: true,
configurable: true
}
}
*/
// 2.获取obj的name属性的(数据/存取)属性描述符
console.log(Object.getOwnPropertyDescriptor(obj,'name'))
//{ value: 'kobe', writable: true, enumerable: true, configurable: true }
// 3.获取存取属性描述符及案例
let age = 18
Object.defineProperty(obj,'age',{
enumerable:true,
configurable:true,
set(value) {
console.log('我修改了属性值')
age = value
},
get(){
console.log('我正在获取属性值')
return age
}
})
console.log(Object.getOwnPropertyDescriptor(obj,'age'))//{get: [Function: get],set: [Function: set],enumerable: true,configurable: true}
obj.age = 39
console.log(obj.age)//39
console.log(obj)//{ name: 'kobe', age: [Getter/Setter] }
console.log(Object.keys(obj))//[ 'name', 'age' ]
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
30
31
32
33
34
35
36
37
38
# 2.6.ES8 - async、await(学完 Promise 再学习)
- Async Function:async、await
- 后续学完 Promise 再学习
# 3、ES9新增特性解析
# 3.1.ES9 - Async iterators(迭代器后续学习)
# 3.2.ES9 - Object spread operators:对象展开运算符
- 该特性前面有学习过:
展开运算符...
//在构建对象字面量时,可使用展开运算符;会将对象表达式按key-value的方式进行展开
const obj = {
name:'kobe',
age:39
}
const obj1 = {
address:'洛杉矶',
...obj
}
console.log(obj1)//{ address: '洛杉矶', name: 'kobe', age: 39 }
2
3
4
5
6
7
8
9
10
11
12
# 3.3.ES9 - Promise.prototype.finally()(学Promise时再学习)
- Promise的实例方法:finally
# 4、ES10新增特性解析
# 4.1.ES10 - Array.prototype.flat() 和 Array.prototype.flatMap()
- flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
Array.prototype.flat(depth)
参数depth
(可选的):指定嵌套数组结构应该展平的深度级别。默认为 1。- Array.prototype.flat (opens new window)
const arrs = [10, 20, 30, [40, 50, [60, 70, 80, [90, 100]]], [110, 120, [130, 140, 150]]]//这个数组的深度为:3
const arrs1 = arrs.flat(1)
console.log(arrs1) //[10, 20, 30, 40, 50, [ 60, 70, 80, [ 90, 100 ] ],110, 120, [ 130, 140, 150 ]]
const arrs2 = arrs.flat(2)
console.log(arrs2)//[10, 20, 30, 40, 50, 60, 70, 80, [ 90, 100 ], 110, 120, 130, 140, 150]
const arrs3 = arrs.flat(3)
console.log(arrs3)//[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150]
const arrs4 = arrs.flat(4)//设置值超过数组深度 则返回最大深度时得到的返回值结果
console.log(arrs4)//[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150]
2
3
4
5
6
7
8
9
10
- flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
Array.prototype.flatMap()
- Array.prototype.flatMap (opens new window)
- 注意一:flatMap是先进行map操作,再做flat的操作;
- 注意二:flatMap中的flat相当于深度为1;
const message = ["Hello World", "你好呀 李银河", "my name is kobe"]
// 1.flatMap方法的使用
const newMessage = message.flatMap(item => {
return item.split(" ")
})
console.log(newMessage)//['Hello', 'World', '你好呀', '李银河', 'my', 'name', 'is', 'kobe']
// 2.上面的flatMap方法 相当于 如下操作:先map 再flat
const messageArr1 = message.map(item => {
return item.split(" ")
})
console.log(messageArr1)//[[ 'Hello', 'World' ], [ '你好呀', '李银河' ], [ 'my', 'name', 'is', 'kobe' ]]
const newMessage1 = messageArr1.flat(1)
console.log(newMessage1)//['Hello', 'World', '你好呀', '李银河', 'my', 'name', 'is', 'kobe']
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.2.ES10 - Object.fromEntries()
- 在前面,我们可以通过 Object.entries() 将一个对象转换成 entries(条目)
- 那么如果我们有一个entries了,如何将其转换成对象呢?
- ES10提供了 Object.fromEntries()来完成转换:Object.fromEntries (opens new window)
const info = {
name:'kobe',
age:28
}
const entries = Object.entries(info)
console.log(entries)//[ [ 'name', 'kobe' ], [ 'age', 28 ] ]
const obj = Object.fromEntries(entries)
console.log(obj)//{ name: 'kobe', age: 28 }
2
3
4
5
6
7
8
9
10
- 那么这个方法有什么应用场景呢?
const paramsString = 'name=kobe&age=39&height=1.98'
const searchParams = new URLSearchParams(paramsString)
console.log(searchParams)//URLSearchParams { 'name' => 'kobe', 'age' => '39', 'height' => '1.98' }
for(const param of searchParams) {
console.log(param)//[ 'name', 'kobe' ] //[ 'age', '39' ] //[ 'height', '1.98' ]
}
const searchObj = Object.fromEntries(searchParams)
console.log(searchObj)//{ name: 'kobe', age: '39', height: '1.98' }
2
3
4
5
6
7
8
# 4.3.ES10 - 去除字符串前面或后面的空格:String的实例方法:trimStart和trimEnd
- 去除一个字符串首尾的空格,我们可以通过
String.prototype.trim()
方法; - 如果单独去除前面或者后面呢?
- ES10中给我们提供了
String.prototype.trimStart()
和String.prototype.trimEnd()
;
- ES10中给我们提供了
const message = " Hello "
console.log(message.trim())//"Hello"
console.log(message.trimStart())//"Hello "
console.log(message.trimEnd())//" Hello"
2
3
4
# 4.4.ES10 - Symbol description(Symbol类型的描述)
【该特性前面学Symbol类型时有学过】
我们可以在创建Symbol值的时候传入一个描述:description :
const s1 = Symbol(description)
- 当然也可以通过创建的Symbol值的description方法,来获取创建时传入的描述信息
const s1 = Symbol('bba')//传入一个描述:'bba'
console.log(s1.description)//bba
2
# 4.5.ES10 - Optional catch binding(可选的捕获绑定:try catch -> 后续学习)
# 5、ES11新增特性解析
# 5.1.ES11 - BigInt数据类型
- 在早期的JavaScript中,我们不能正确的表示过大的数字:
- 大于MAX_SAFE_INTEGER的数值,表示的可能是不正确的。
- ES11之前:
Number.MAX_SAFE_INTEGER
可获取能正确表示的最大数值;超过了这个数值,表示的数值可能就会出现错误
const maxInt = Number.MAX_SAFE_INTEGER
console.log(maxInt) //9007199254740991
//大于MAX_SAFE_INTEGER值的一些数值,无法正确表示
console.log(maxInt + 1) //9007199254740992
console.log(maxInt + 2) //9007199254740992 -> 超过了出现了错误
console.log(maxInt + 1010) //9007199254742000 -> 超过了出现了错误
2
3
4
5
6
7
- 那么ES11中,引入了新的数据类型BigInt,用于表示大的整数:
- BigInt的表示方法是在数值的后面加上n
const bigInt = 9007199254740991n
console.log(bigInt + 1n) //9007199254740992n
console.log(bigInt + 2n) //9007199254740993n
console.log(bigInt + 1010n) //9007199254742001n
console.log(typeof(bigInt + 1010n)) //bigint
2
3
4
5
6
# 5.2.ES11 - Nullish Coalescing Operator(空值合并操作符??)
- ES11,Nullish Coalescing Operator增加了空值合并操作符
??
:
const foo = "" //这里可以试试这些值转为Boolean值为false的值:“”,0,false,undefined,null,NaN
const result1 = foo || '默认值'
const result2 = foo ?? '默认值' //该语句 ---> 相当于下面这种写法:const result3 = foo === null || foo === undefined ? '默认值' : foo
const result3 = foo === null || foo === undefined ? '默认值' : foo
console.log(result1)
console.log(result2)
console.log(result3)
2
3
4
5
6
7
8
# 5.3.ES11 - Optional Chaining(可选链?.)
- 可选链也是ES11中新增一个特性,主要作用是让我们的代码在进行null和undefined判断时更加清晰和简洁:
const obj = {
name:'kobe',
friend:{
name:'james',
running() {
console.log(this.name,'running')
}
},
eating() {
console.log(this.name,'eating')
}
}
// obj.friend.eating()//之前这种写法会报错
//可选链写法:可选链写法不会报错
const result = obj?.friend?.eating?.()
console.log(result)//undefined
//第一个?.:判断obj中是否有friend属性;如果有则拿到 obj.friend 没有则返回undefined
// 第二个?.:判断obj.friend是否有eating方法:如果有则拿到obj.friend.eating 没有则返回undefined
// 第三个?.:这里第三个是为了防止出现(obj.friend没有eating方法的情况,这样obj?.friend?.eating会返回undefined;如果没有第三个?.;则会发生undefined()抛出错误问题)
obj?.friend?.running?.()//james running
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 可选链是为了避免我们在进行多人合作业务开发时,用到别人封装的函数或者对象,当获取不存在的属性或调用不存在的方法时 出现的抛出错误情况(报错情况)
# 5.4.ES11 - GlobalThis(统一规范的全局对象)
- 在之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的
- 比如在浏览器中可以通过this、window来获取;
- 比如在Node中我们需要通过global来获取;
//浏览器中拿到全局对象
console.log(this)
console.log(window)
//Node中拿到全局对象
console.log(global)
2
3
4
5
6
- 在ES11中对获取全局对象进行了统一的规范:globalThis
- 不管是在浏览器中,还是Node中,我们都可以通过globalThis拿到全局对象
console.log(globalThis)
# 5.5.ES11 - for..in标准化
- 在ES11之前,虽然很多浏览器支持for...in来遍历对象类型,但是并没有被ECMA标准化。
- 在ES11中,对其进行了标准化,for...in是用于遍历对象的key的:
const obj = {
name:'kobe',
age:39
}
for(const key in obj) {
console.log(key) //name //age
}
2
3
4
5
6
7
8
# 5.6.ES11 - Dynamic Import(动态导入):后面ES Module模块化中学习。
# 5.7.ES11 - Promise.allSettled:后面Promise中学习。
- Promise的类方法:
Promise.allSettled()
# 5.8.ES11 - import meta(导入元):后面ES Module模块化中学习。
# 6、ES12新增特性解析
# 6.1.ES12 - FinalizationRegistry类(监控对象垃圾回收 进行相应回调)
- FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调。
- FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调。(清理回调有时被称为 finalizer );
- 你可以通过调用register方法:
FinalizationRegistry.prototype.register()
(opens new window),注册任何你想要清理回调的对象,传入该对象和所含的值; - FinalizationRegistry类语法学习 (opens new window)
<script>
let obj = { name: 'kobe' } //强引用
const finalization = new FinalizationRegistry((value) => {
console.log(value,'被垃圾回收器回收了')
})
finalization.register(obj, 'obj')
obj = null //强引用消失 -> 则obj对象引用(引用的内存地址找到对应的存储空间)的 堆内存的存储空间 会被垃圾回收器回收(标记清除算法->可达性)
//过一会浏览器会打印:obj 被垃圾回收器回收了
</script>
2
3
4
5
6
7
8
9
10
11
# 6.2.ES12 - WeakRefs
- 如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用:
- 如果我们希望是一个弱引用的话,可以使用WeakRef;
<script>
let obj = { name: 'kobe' } //强引用
let info = new WeakRef(obj) //弱引用
const finalization = new FinalizationRegistry((value) => {
console.log(value,'被垃圾回收器回收了')
})
finalization.register(obj, 'obj')
obj = null //强引用消失 -> 因为info对obj对象对应的存储空间的引用是弱引用;所以该存储空间,在obj为null后,垃圾回收器会将该存储空间进行回收
//过一会浏览器会打印:obj 被垃圾回收器回收了
</script>
2
3
4
5
6
7
8
9
10
11
12
# 6.3.ES12 - logical assignment operators(逻辑赋值运算符:||=,&&=,??=)
- 之前我们学过赋值运算符:=,-=,+=,*=,/=,%=等;和逻辑运算符:!,||,&&,??等
- 那么接下来我们来学习赋值逻辑运算符:||=,&&=,??=
//1.逻辑或赋值运算符:||=
let message1 = ""
// message1 = message1 || "默认值" //之前写法
message1 ||= "默认值"
console.log(message1)//默认值
//2.逻辑与赋值运算符:&&=
let obj = {
name:'kobe'
}
//obj = null
//obj = obj && obj.name //之前写法
obj &&= obj.name
console.log(obj)//kobe
//3.逻辑空赋值运算符:??=
let foo = null
//foo = foo ?? "默认值" //之前写法
foo ??= "默认值"
console.log(foo)//默认值
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.4.ES12 - Numeric Separator(数字分隔符_)
//之前表示大数字
const num = 136565656656656
//用数字分隔符表示大数字 ---> 方便阅读
const num1 = 136_565_656_656_656
2
3
4
# 6.5.ES12 - String.prototype.replaceAll()(字符串替换-全部)
- 前面我们学习过一个字符串替换的实例方法:
String.prototype.replaceAll()
- ES12新增了字符串替换全部的实例方法:
String.prototype.replaceAll()
- 下面我们通过案例来体会他们的不同之处吧
const str = 'My good friend is kobe My good friend is kobe'
var str1 = str.replace('kobe','james')//替换字符串中 第一项符合规则的 字符串
console.log(str1)//My good friend is james My good friend is kobe
var str2 = str.replaceAll('kobe','james')//-> 替换字符串中 全部符合规则的 字符串
console.log(str2)//My good friend is james My good friend is james
2
3
4
5
6
7
# 7、ES13新增特性解析
# 7.1.ES13 - method.at()
- 前面我们有使用过字符串、数组的at方法,它们是作为ES13中的新特性加入的:
// 1.数组
const names = ['nba','cba','ncaa']
console.log(names.at(0))//nba
console.log(names.at(-1))//ncaa
// 2.字符串
const str = "Hello World"
console.log(str.at(4))//o
console.log(str.at(-3))//r
2
3
4
5
6
7
8
9
# 7.2.ES13 - Object.hasOwn(obj, propKey) - 判断对象上是否有某个属性
Object中新增了一个静态方法(类方法): hasOwn(obj, propKey)
- 该方法用于判断一个对象中是否有某个自己的属性;
Object.hasOwn(obj, propKey)
(opens new window)
那么和之前学习的Object.prototype.hasOwnProperty有什么区别呢?
注意:
Object.hasOwn()
旨在替代Object.prototype.hasOwnProperty()
(opens new window).区别一:防止 Object实例对象和所有Object子类的实例对象 中 内部有重写hasOwnProperty
区别二:对于 Object实例对象和所有Object子类的实例对象的 隐式原型指向null的情况, hasOwnProperty无法进行判断 (因为这样的话该实例对象的原型链中不存在Object.prototype,所以拿不到Object.prototype.hasOwnProperty()方法,即无法进行判断)
知识点提醒:Object是所有类的父类
// 1.区别一:防止对象内部有重写hasOwnProperty
const obj = {
name:'obj',
hasOwnProperty(value) {
console.log(this.name,'对象内部有重写hasOwnProperty,就调用不到Object.prototype.hasOwnProperty方法了')
}
}
obj.hasOwnProperty('name')//obj 对象内部有重写hasOwnProperty,就调用不到Object.prototype.hasOwnProperty方法了
const flag = Object.hasOwn(obj,'name')
console.log(flag)//true -> obj对象上有name属性
// 2.区别二:对于隐式原型指向null的对象, hasOwnProperty无法进行判断
const info = {
name:'info'
}
console.log(info.hasOwnProperty('name'))//true
Object.setPrototypeOf(info,null)//info.__proto__ = null
// console.log(info.hasOwnProperty('name'))//报错:info.hasOwnProperty is not a function
const flag1 = Object.hasOwn(info,'name')
console.log(flag1)//true -> info对象上有name属性
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 7.3.ES13 - New members of classes (class类中新增的字段)
- 在ES13中,新增了定义class类中成员字段(field)的其他方式:
- Instance public fields :实例公共字段
- Static public fields:静态公共字段
- Instance private fields:实例私有字段
- static private fields:静态私有字段
- staticblock:静态块
// 1.创建父类
class Person {
//类外部可以访问
address = '中国' //实例公共字段
static totalCount = '70亿' //静态公共字段
//只能类内部访问
#sex = 'male' //实例私有字段
static #maleCount = '10亿' //静态私有字段
constructor(name, age) {
this.name = name,
this.age = age
}
static {// 1.1.静态块 -> 类中的代码进行解析初始化时,解析到了静态块,静态块会就会立即执行,且只执行这一次 (只要类中有静态块,静态块就会立即执行:)
console.log('static block execution')
}
printInfo() {
console.log(this.address,Person.totalCount, this.#sex, Person.#maleCount)
}
}
const p = new Person('kobe',39)
console.log(p)//Person { address: '中国', name: 'kobe', age: 39 }
p.printInfo()//中国 70亿 male 10亿
// 2.创建子类
class Student extends Person {
constructor(name,age) {
super(name,age)
}
}
const stu = new Student('james',37)
console.log(stu)//Student { address: '中国', name: 'james', age: 37 }
stu.printInfo()//中国 70亿 male 10亿
console.log(Student.totalCount,Person.totalCount)//70亿 70亿
console.log(p.address,stu.address)//中国 中国
// 3.类中定义的私有字段,只能在类的内部进行访问,外部访问会抛出错误
// console.log(Student.#maleCount)//报错: Private field '#maleCount' must be declared in an enclosing class
// console.log(Person.#maleCount)//报错: Private field '#maleCount' must be declared in an enclosing class
// console.log(p.#sex)//报错:Private field '#sex' must be declared in an enclosing class
// console.log(stu.#sex)//报错:Private field '#sex' must be declared in an enclosing class
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47