# Javascript对象
# 函数
# 定义函数
函数可作为封装模块被重复使用,在javascript中定义函数有三种方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
//创建函数方式1:
var fun1 = new Function("a", "b", "a++;return a+b;");
console.log(fun1(1, 2));
//创建函数方式2:
function fun2(a, b) {
return a - b;
}
console.log(fun2(1, 2))
//创建函数方式3:
var fun3 = function (a, b) {
return a * b;
}
console.log(fun3(2, 3));
console.log(fun3.length);
//箭头函数(es6语法):
console.log("箭头函数方式");
var arr2 = [1, 2, 3, 4, 5];
arr2.forEach((e) => {
console.log(e);
});
</script>
</head>
<body>
</body>
</html>
1
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
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
注意Javascript和类c语言不太一样的地方有:
- 方法定义是,形参的类型不用写,返回值类型也不写。
- 方法是一个对象,如果定义名称相同的方法,会覆盖。
- 在JS中,方法的调用只与方法的名称有关,和参数列表无关。
# 获取调参信息
Javascript获取函数参数,被调用者对象:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
//获取所有参数
function add() {
var sum = 0;
console.log(arguments.callee);
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
console.log(add(1, 2, 3, 4, 5));
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
下列情况会跟我们设想的不一样,javascript中的函数名是对象,常常会变化,如果函数内部依赖于函数名就会出现错误:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
function fac(num) {
if (num <= 1) { //0的阶乘也是1
return 1;
}
else {
return num * fac(num - 1);
}
}
var trueFac = fac;
fac = function (num) {
console.log("call fac!");
return 0;
};
console.log(trueFac(10));
</script>
</head>
<body>
</body>
</html>
1
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
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
此时就需要使用callee:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
function fac(num) {
if (num <= 1) { //0的阶乘也是1
return 1;
}
else {
return num*arguments.callee(num-1);
}
}
var trueFac = fac;
fac = function (num) {
console.log("call fac!");
return 0;
};
console.log(trueFac(10));
</script>
</head>
<body>
</body>
</html>
1
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
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
此外还可以使用caller来查看函数的调用者:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer();
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 匿名函数
匿名函数就是没有函数名的函数,它可以:
- 赋值到一个变量里,变量相当于函数名。
- 利用事件去调用。
- 作为对象的方法调用。
- 作为其他函数的参数调用。
这里介绍一种好玩的匿名函数,自执行匿名函数,下面是几个常见的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
(function () {
console.log("1");
})();
(function (a) {
console.log(a);
})("hi");
(function (a) {
console.log(a);
return arguments.callee;
})("hello")("world");
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
下面是几个不常见的自执行匿名函数:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
+function () { console.log("1"); }();
-function () { console.log("2"); }();
~function () { console.log("3"); }();
!function () { console.log("4"); }();
new function () { console.log("5"); }();
new function () { console.log("6"); };
void function () { console.log("7"); }();
(function () { console.log("8");}());
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Promise
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
它是来解决两个问题的:
- 有很多回调函数,前面函数的结果是后面函数的输入。
- 异步执行代码,支持多个并发请求。
下面来看示例:
<html>
<head>
<title>promise</title>
</head>
<body>
<script>
function taskA() {
console.log("Task A");
// throw new Error("taskA掉坑里了");
}
function taskB() {
console.log("Task B");
throw new Error("taskB掉坑里了");
}
function onRejected(error) {
console.log("onRejected catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA) // 和task A/B
.then(taskB)
.catch(onRejected)
.then(finalTask);
console.log('promise finish'); // promise finish 打印顺序
</script>
</body>
</html>
1
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
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
# Array
数组是使用单独的变量名来存储一系列值。
# 创建数组
基本有三种方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
var arr1 = new Array(1, 2, 3, 4);
console.log(arr1)
var arr2 = new Array(5);
console.log(arr2)
var arr3 = ["a", "b", "c", "d"];
console.log(arr3)
var arr4 = [1,"a",2,"hello",{name:"xie"}];
console.log(arr4)
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
注意:
- javascript中,数组元素的类型是可变的。
- javascript中,数组长度是可变的。
# 常用方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
var arr = [1, "a", 2, "hello", { name: "xie" }];
console.log(arr.length)
console.log(arr.join("-"))
arr.push(456)
console.log(arr)
console.log(arr.pop())
console.log(arr)
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Map
Map在es6是新引进的,如果是在es5中需要用数组实现:
# ES5
var ZeroRTCMap = function () {
this._entrys = new Array();
// 插入
this.put = function (key, value) {
if (key == null || key == undefined) {
return;
}
var index = this._getIndex(key);
if (index == -1) {
var entry = new Object();
entry.key = key;
entry.value = value;
this._entrys[this._entrys.length] = entry;
} else {
this._entrys[index].value = value;
}
};
// 根据key获取value
this.get = function (key) {
var index = this._getIndex(key);
return (index != -1) ? this._entrys[index].value : null;
};
// 移除key-value
this.remove = function (key) {
var index = this._getIndex(key);
if (index != -1) {
this._entrys.splice(index, 1);
}
};
// 清空map
this.clear = function () {
this._entrys.length = 0;
};
// 判断是否包含key
this.contains = function (key) {
var index = this._getIndex(key);
return (index != -1) ? true : false;
};
// map内key-value的数量
this.size = function () {
return this._entrys.length;
};
// 获取所有的key
this.getEntrys = function () {
return this._entrys;
};
// 内部函数
this._getIndex = function (key) {
if (key == null || key == undefined) {
return -1;
}
var _length = this._entrys.length;
for (var i = 0; i < _length; i++) {
var entry = this._entrys[i];
if (entry == null || entry == undefined) {
continue;
}
if (entry.key === key) {// equal
return i;
}
}
return -1;
};
}
1
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# ES6
参考资料:
使用 Map 对象:
let myMap1 = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
[1, 'four']
])
let myMap = new Map();
let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");
myMap.size; // 3
// 读取值
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get(keyObj); // "和键keyObj关联的值"
myMap.get(keyFunc); // "和键keyFunc关联的值"
myMap.get('a string'); // "和键'a string'关联的值"
// 因为keyString === 'a string'
myMap.get({}); // undefined, 因为keyObj !== {}
myMap.get(function() {}); // undefined, 因为keyFunc !== function () {}
1
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
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
遍历:
let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (let [key, value] of myMap) {
console.log(key + " = " + value);
}
// 将会显示两个log。一个是"0 = zero"另一个是"1 = one"
for (let key of myMap.keys()) {
console.log(key);
}
// 将会显示两个log。 一个是 "0" 另一个是 "1"
for (let value of myMap.values()) {
console.log(value);
}
// 将会显示两个log。 一个是 "zero" 另一个是 "one"
for (let [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
myMap.forEach(function(value, key) {
console.log(key + " = " + value);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
合并:
let kvArray = [["key1", "value1"], ["key2", "value2"]];
// 使用常规的Map构造函数可以将一个二维键值对数组转换成一个Map对象
let myMap = new Map(kvArray);
myMap.get("key1"); // 返回值为 "value1"
// 使用Array.from函数可以将一个Map对象转换成一个二维键值对数组
console.log(Array.from(myMap)); // 输出和kvArray相同的数组
// 更简洁的方法来做如上同样的事情,使用展开运算符
console.log([...myMap]);
// 或者在键或者值的迭代器上使用Array.from,进而得到只含有键或者值的数组
console.log(Array.from(myMap.keys())); // 输出 ["key1", "key2"]
//复制Map
let original = new Map([
[1, 'one']
]);
let clone = new Map(original);
console.log(clone.get(1)); // one
console.log(original === clone); //
//Map之间的合并
let first = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let second = new Map([
[1, 'uno'],
[2, 'dos']
]);
// 合并两个Map对象时,如果有重复的键值,则后面的会覆盖前面的。
// 展开运算符本质上是将Map对象转换成数组。
let merged = new Map([...first, ...second]);
console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three
1
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
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
注意要使用Set方法增加元素,不要直接赋值
let wrongMap = new Map()
wrongMap['bla'] = 'blaa'
wrongMap['bla2'] = 'blaaa2'
console.log(wrongMap) // Map { bla: 'blaa', bla2: 'blaaa2' }
wrongMap.has('bla') // false
wrongMap.delete('bla') // false
console.log(wrongMap) // Map { bla: 'blaa', bla2: 'blaaa2' }
let myMap = new Map()
myMap.set('bla','blaa')
myMap.set('bla2','blaa2')
console.log(myMap) // Map { 'bla' => 'blaa', 'bla2' => 'blaa2' }
myMap.has('bla') // true
myMap.delete('bla') // true
console.log(myMap) // Map { 'bla2' => 'blaa2' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Set
Set为集合数据结构,也是es6中加入的。
# 基本使用
let s = new Set([1,2,3])
s.add("school")
console.log(s) // Set(4){1,2,3,"school"}
// 能够通过链式增加,将元素增加到map中
let s = new Set([1,2,3])
s.add("school").add("bus")
console.log(s) // Set(5){1,2,3,"school","bus"}
s.delete(2)
console.log(s) // Set(2){1,3}
s.clear()
s.has(2)
s.size
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Set遍历
let s = new Set([1,2,3])
s.forEach(item => console.log(item)) // 1 2 3
for(let item of s){
console.log(item) // 1 2 3
}
for(let item of s.keys()){
console.log(item) // 1 2 3
}
for(let item of s.values()){
console.log(item) // 1 2 3
}
// 对于map,key和value是一样的
for(let item of s.entries()){
console.log(item) // [1,1] [2,2] [3,3]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 去重
//数组去重
let arr = [1,2,3,3,3,2,1]
let s = new Set(arr)
console.log(s) // Set(3){1,2,3}
console.log([...s]) // [1,2,3]
console.log(Array.from(s)) // [1,2,3]
//数组合并后去重
let arr1 = [1,2,3,4]
let arr2 = [2,3,4,5,6]
let s = new Set([...arr1,...arr2])
console.log(s) // Set(6){1,2,3,4,5,6}
console.log([...s]) // [1,2,3,4,5,6]
console.log(Array.from(s)) // [1,2,3,4,5,6]
//两个数组的交加
let arr1 = [1,2,3,4]
let arr2 = [2,3,4,5,6]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter((item) => s2.has(item)))
console.log(result) // Set(3){2,3,4}
//两个数组差集
let arr1 = [1,2,3,4]
let arr2 = [2,3,4,5,6]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let s3 = new Set(arr1.filter((item) => !s2.has(item)))
let s4 = new Set(arr2.filter((item) => !s1.has(item)))
console.log(s3) // Map(1){1}
console.log(s4) // Map(1){5,6}
console.log([...s3,...s4]) // [1,5,6]
1
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
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
# WeakMap
let ws = new WeakSet()
// ws.add(1) // 报错:因为1不是Object类型,不能增加到WeakMap中。外面只能寄存Object,不能寄存其余数据类型
ws.add({
name:"lilei"
})
ws.add({
age:12
})
console.log(ws) // WeakSet{{...},{...}}
//删除对象
let ws = new WeakSet()
// ws.add(1) // 报错:Invalid value used in weak set
ws.add({
name:"lilei"
})
ws.add({
age:12
})
ws.delete({
name:"lilei"
})
console.log(ws) // WeakSet{{...},{...}} 删除之后没有失效,因为对象是援用数据类型,增加对象的地址和删除元素的地址不统一,导致不能删除
let ws = new WeakSet()
const obj1 = {name:"lilei"}
const obj2 = {age:12}
ws.add(obj1)
ws.add(obj2)
console.log(ws) // WeakSet{{...},{...}}
ws.delete(obj1)
console.log(ws) // WeakSet{{...}} // 曾经删除了{name:"lilei"}内容,次要就是因为对象属于援用类型,间接在delete写对象与前一个对象在堆内存中指向的不是同一个地址
console.log(ws.has(obj2)) // true 判断外部是否含有obj2对象,与map雷同,应用has函数
1
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
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
# Boolean
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
var b1 = new Boolean(1);
console.log(b1);
console.log(typeof(b1));//类型为对象
console.log(Boolean(0));
console.log(typeof(Boolean(0)));//为boolean基本类型
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Date
Date表示日期对象,它常用的操作如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="http://cdn.staticfile.org/moment.js/2.24.0/moment.js"> </script>
<script>
var date = new Date();
document.write(date + "<br>");
//返回当前date对象对应的时间本地字符串格式
document.write(date.toLocaleString() + "<br>");
//返回当前时间到1970年1月1日零点的毫秒值差
document.write(date.getTime() + "<br>");
//返回ISO8601格式时间
document.write(moment().format("YYYY-MM-DDTHH:mm:ss.SSSZ"));
</script>
</head>
<body>
</body>
</html>
1
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
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
# Math
Math表示数学对象,它不用创建直接使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
document.write(Math.PI + "<br>");
//random():返回 0 ~ 1 之间的随机数。 含0不含1
document.write(Math.random() + "<br>");
//round(x):把数四舍五入为最接近的整数。
document.write(Math.round(3.14) + "<br>");
//ceil(x):对数进行向上舍入。
document.write(Math.ceil(3.14) + "<br>");
//floor(x):对数进行向下舍入。
document.write(Math.floor(3.14) + "<br>");
document.write(Math.PI + "<br>");
/**
* 取 1~100之间的随机整数
* 1. Math.random()产生随机数:范围 [0,1)小数
* 2. 乘以 100 --> [0,99.9999] 小数
* 3. 舍弃小数部分 :floor --> [0,99] 整数
* 4. +1 -->[0,99] 整数 [1,100]
*/
var number = Math.floor((Math.random() * 100)) + 1;
document.write(number + "<br>");
</script>
</head>
<body>
</body>
</html>
1
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
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
# RegExp
RegExp表示正则表达式对象,用来定义字符串的组成规则。
- 单个字符:[],如: [a] [ab] [a-zA-Z0-9_]
- 特殊符号代表特殊含义的单个字符:
- \d:单个数字字符 [0-9]
- \w:单个单词字符[a-zA-Z0-9_]
- 特殊符号代表特殊含义的单个字符:
- 量词符号:
- ?:表示出现0次或1次
- *:表示出现0次或多次
- +:出现1次或多次
- {m,n}:表示 m<= 数量 <= n
- m如果缺省: {,n}:最多n次
- n如果缺省:{m,} 最少m次
- 开始结束符号
- ^:开始
- $:结束
实例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
var reg = new RegExp("^\\w{6,12}$");
var reg2 = /^\w{6,12}$/;
console.log(reg)
console.log(reg2)
var username = "zhangsan";
var flag = reg.test(username);
console.log(flag);
</script>
</head>
<body>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Global
Global表示全局对象,这个Global中封装的方法不需要对象就可以直接调用。常用的操作有:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script>
var str = "http://www.baidu.com?wd=木叶清风";
var encode = encodeURI(str);
document.write(encode + "<br>");
var s = decodeURI(encode);
document.write(s + "<br>");
var str1 = "http://www.baidu.com?wd=木叶清风";
var encode1 = encodeURIComponent(str1);
document.write(encode1 + "<br>");
var s1 = decodeURIComponent(encode1);
document.write(s1 + "<br>");
var str = "a234abc";
var number = parseInt(str);
var a = NaN;
//NaN参与的所有==判断都为false。
document.write((a == NaN) + "<br>");
document.write(isNaN(a));
var jscode = "alert('hello')";
eval(jscode);
</script>
</head>
<body>
</body>
</html>
1
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
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