# Vue基础1
# 前端历史
web前端的发展和网站的起源密不可分分,网站的起源分几个阶段:
- 在上个世纪1989年,欧洲核子研究中心物理学家Tim Berners-Lee发明了超文本标记语言(HyperText Markup Language),简称HTML,并在1993年成为互联网草案。从此互联网开始迅速商业化,诞生了一大批商业网站。最早的HTML页面是完全静态的网页,浏览器请求某个URL时,Web服务器把对应的html文件扔给浏览器。
- 针对不同的用户显示不同的页面,服务器需要针对不同的用户,使用C++等语言,动态生成不同的html文件,直接向浏览器输出拼接后的字符串,这种技术成为CGI(Common Gateway Interface)。
- 后来发现,拼字符串的时候大多数字符串都是HTML片段,是不变化,变化的只有少数和用户相关的数据,于是又出现新的动态HTML的方式:ASP,JSP和PHP。这些文件中,通过语言固定形式,配合循环和条件判断,创建动态HTML比CGI容易的多。
- 1995年,在上面服务器端语言诞生的同时,Navigator浏览器也急需要一种语言来控制浏览器的行为,这种语言最初叫Mocha,9月份改名为LiveScript,12月与Sun公司达成协议,改名为Javascript。为了保持简单,这种脚本语言缺少一些关键的功能,比如块级作用域、模块、子类型(subtyping)等等,但是可以利用现有功能找出解决办法。这种功能的不足,直接导致了后来JavaScript的一个显著特点:对于其他语言,你需要学习语言的各种功能,而对于JavaScript,你常常需要学习各种解决问题的模式。而且由于来源多样,从一开始就注定,JavaScript的编程风格是函数式编程和面向对象编程的一种混合体。
从此web前端迅速发展,它大致分为三个阶段:
JS原生通过浏览器解析机制,它的原理是使用浏览器提供的原生API 结合JS语法,可以直接操作DOM。
由于原生API晦涩难懂,语法很长不好用,最重要的是要考虑各种浏览器兼容性,因为他们的解析标准都不一样,造成了写一段效果代码要写很多的兼容语法,后面jQuery等框架出现了,它简化了原生JS语法,对各种浏览器进行兼容,极大提高了效率,但是此时,还是直接操作DOM阶段,只不过是更方便操作了而已。这个时候前端网页大部分是作为网站MVC框架中的View层,View层向Controller通信,Controller向Model通信,它们是单向的,提交一次反馈一次。
这个阶段就是MVVM框架模式,这个概念最初由微软提出来,它分为Model,View和ViewModel。开发人员从后端获取需要的数据模型,通过DOM把Model渲染到View中,后面从DOM中获取View中数据,并同步到Model中。MVVM中的VM做的事情就是把DOM操作完全封装起来,开发人员不再关心Model和View之间如何相互影响。它完成下面两件事:
- 只要Model发生改变,View上自然会表现出来。
- 当用户修改了View,Model中的数据会跟着改变。
# Vue快速入门
现在MVVM框架主要有Vue,React,AngularJS。Vue是一套用于构建用户界面的渐进式框架,Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或当前项目整合。
渐进式指的是可以选择性地使用该框架的一个或一些组件,这些组件的使用也不需要引用全部组件,也就是使用这些组件并不要求你的系统全部都使用该框架。
下面是vue的一个入门案例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<h1>vue数据示例</h1>
<div id="example">
a:{{a}}
<button v-on:click="a=4">Change it</button>
</div>
<script>
var data = {
a: 1
}
//初始化变量
var data2 = {
newTodoText: '',
visitCount: 0,
hideCompletedTodos: false,
todos: [],
error: null
}
//不顶用
//Object.freeze(data.a);
//Object.freeze(data);//这句话得在实例Vue之前。
var vm = new Vue({
el: '#example',
data: data
})
console.log(vm.$data === data); // => true
console.log(vm.a == data.a);
vm.a = 3;
console.log(vm.$el === document.getElementById('example')); // => true
// $watch 是一个实例方法
vm.$watch('a', function(newValue, oldValue) {
console.log(newValue + ":" + oldValue);
})
</script>
</body>
</html>
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
注意:
- 每个Vue实例都需要关联一段Html模板,可通过el属性来指定。
- 使用vue的范围仅在id为bindTest的div内,超出这个div无法使用vue。
- 只有Vue实例被创建时已经存在于data的属性才是响应式的,在后面添加的属性不会触发任何视图的更新,如果一个属性开始没有但到后面需要,需要设置初始值。
- 除了自定义的属性,Vue暴露了一些其他属性和方法,它们都有前缀$,为了能和用户定义的属性区分开。
# 生命周期
每个Vue实例在被创建时会经历过一系列阶段,创建实例,装载模板,渲染模板。Vue为生命周期中的每个状态都设置了监听函数(钩子函数),当实例处于相应的阶段时就会调用该监听函数。
注意,生命周期监听函数自动绑定this上下文到当前Vue实例,可通过this访问数据,对属性和方法进行运算。但是不能使用箭头函数来定义一个生命周期方法,因为箭头函数没有自己的this,arguments,super或new.target。该箭头函数绑定了父上下文,因此this与我们期待的Vue实例不同。
下面是实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{msg}}
</div>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
msg: ""
},
//钩子函数
created() {
//this表示vue实例
this.msg = "vue created.";
console.log(this);
}
});
</script>
</body>
</html>
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
# 模板语法
# 插值
插值是在需要的地方显示数据,数据绑定最常见的是"Mustache语法",无论何时绑定的数据对象上的属性发生了变化,插值处内容都会更新。还有v-once指令,能进行一次性的插值,当数据改变时,插值处内容不会更新。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{msg}}
<br>
{{ number + 1 }}
<br>
{{ ok ? 'YES' : 'NO' }}
<br>
{{ msg.split('').reverse().join('') }}
<br>
<div v-once>{{info}}</div>
</div>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
msg: "",
number: 0,
ok: true,
info: "测试信息"
},
//钩子函数
created() {
//this表示vue实例
this.msg = "vue created.";
//this.info="无法测试!";
console.log(this);
}
});
//如果这句话是放在created钩子函数内,其会在渲染前改变为"无法测试!"
app.msg="进行修改!";
this.info="无法测试!";
</script>
</body>
</html>
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
还有两个指令可以用来代替{{}}:
- v-text:将数据输出到元素内部,所有数据都作为普通文本输出。
- v-html:将数据输出到元素内部,如果数据中有HTML代码,则会被渲染成页面一部分。
注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
v-text:<span v-text="msg"></span><br>
v-html:<span v-html="msg"></span><br>
</div>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
msg: "<h2>hello, vue</h2>"
}
});
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 指令
指令(Directives)是带有v-前缀的特殊属性,指令属性的值应该是单个JavaScript表达式,它的作用是当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。一些指令需要接收一些参数,在指令名称之后以冒号表示。有两个常用的指令v-bind和v-on,它们的缩写分别为:和@。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<h1>vue声明式渲染</h1>
<div id="bindTest">{{message + ' world'}}
num:{{num}}
<!-- 绑定属性 -->
<div v-bind:id="message"></div>
<!-- 绑定属性缩写模式 -->
<div :id="message"></div>
<div :[hrefname]="MyAttribute"></div>
<!-- 绑定方法 -->
<button v-on:click="num++">测试1</button>
<!-- 绑定方法缩写形式 -->
<button @click="showInfo">测试2</button>
<div @[methodname]="doSomething">测试文本</div>
</div>
<script>
var vm = new Vue({
el: '#bindTest',
data: {
num:0,
message: 'hi',
hrefname:"MouseClick",
methodname:"click",
MyAttribute:"ClickClick",
},
methods:{
doSomething:function(){
alert("ok!")
},
showInfo:function(){
alert("alert some info!");
}
}
});
</script>
</body>
</html>
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
注意:
- 从2.6.0开始后,可用方括号括起来的Javascript表达式作为一个指令参数来实现动态参数,动态参数会求这个Javascript表达式,如果求出一个字符串则进行,否则异常情况下值为null,这个特殊的null值可被显性地用于移除绑定。
- 动态参数表达式有一些语法约束,一些字符比如空格和引号,放在HTML属性名中是无效的。
- 避免使用大写字符来命名键名,浏览器会强制把所有属性名转化为小写。
# 计算属性
模板内的表达式很方便,但是在这里面放太多会让模板过重难以维护,对于复杂的逻辑,应该使用计算属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<!-- 使用方法来实现: -->
<p>Reversed message: "{{ reversedMessage2() }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function() {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
},
methods: {
reversedMessage2: function() {
return this.message.split('').reverse().join('')
}
}
})
</script>
</body>
</html>
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
在使用计算属性和方法时候,有一点重要不一样的是计算属性是基于它们的响应式依赖进行缓存的,只有相关响应式依赖发生改变时才会重新求值,意味着只要message没有改变,多次访问reversedMessage会返回之前计算的结果。
此外还可以使用侦听属性,如下例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="demo1">{{ fullName }}
<input v-model="firstName">
</div>
<div id="demo2">{{ fullName }}
<input v-model="lastName">
</div>
<script>
var vm = new Vue({
el: '#demo1',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function(val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function(val) {
this.fullName = this.firstName + ' ' + val
}
}
})
var vm2 = new Vue({
el: '#demo2',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function() {
return this.firstName + ' ' + this.lastName
}
}
})
</script>
</body>
</html>
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
从上面看出计算属性比监听属性更简洁,计算属性能做的监听属性都能做,但是反过来却不行,使用原则是能用计算属性尽量用计算属性。
注意当监听一个对象时,需要开启深度监控:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="message">
<hr><br>
<input type="text" v-model="person.name"><br>
<input type="text" v-model="person.age"><button @click="person.age++">+</button>
<h2>
姓名为:{{person.name}};年龄为:{{person.age}}
</h2>
</div>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
message: "测试对象监听",
person: {
"name": "xie",
"age": 20
}
},
watch: {
message(newValue, oldValue) {
console.log("新值:" + newValue + ";旧值:" + oldValue);
},
person: {
//开启深度监控;监控对象中的属性值变化
deep: true,
//可以获取到最新的对象属性数据
handler(obj) {
console.log("name = " + obj.name + "; age=" + obj.age);
}
}
}
});
</script>
</body>
</html>
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
计算属性默认只有getter,但是也可以在需要的时候提供一个setter:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="demo">{{ fullName }}
</div>
<script>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
},
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
})
</script>
</body>
</html>
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
# Class与Style操作
操作元素的class和内联样式是常见需求,它们都是属性,可以使用v-bind,计算出字符串的结果即可,但是字符串拼接麻烦易出错,在将v-bind用于class和style时,Vue.js专门做了处理,除了字符串外还可以使用对象和数组。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
<div class="static" v-bind:class="classObject">通过对象来控制</div>
<div v-bind:class="classObjectCalc">绑定计算属性</div>
<!-- 数组模式控制属性 -->
<div v-bind:class="[activeClass, errorClass]">数组列表</div>
<div v-bind:class="[!isActive ? activeClass : '', errorClass]">数组模式切换属性</div>
<div v-bind:class="[{ active: isActive }, errorClass]">数组模式控制属性</div>
</div>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
type: "fatal",
isActive: true,
hasError: false,
classObject: {
active: true,
'text-danger': false
},
activeClass: 'active',
errorClass: 'text-danger',
},
computed: {
classObjectCalc: function() {
return {
active: this.isActive && !this.hasError,
'text-danger': this.hasError && this.type === 'fatal'
}
},
}
});
</script>
</body>
</html>
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
# 条件渲染
我们可以用v-if有选择性地渲染某一块内容而不渲染另一块内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<span v-if="!item.condition">{{item.content}}</span>
<span v-else style="text-decoration: line-through;">{{item.content}}</span>
<button v-show="item.condition">该部分被加载到DOM,只是不显示</button>
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
<br>
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
<br>
<button @click="togglePlay">切换用户名邮箱</button>
</div>
<script type="text/javascript">
var vm2 = new Vue({
el: '#app',
data: {
item: {
content: "显示标题",
condition: false,
},
type: "B",
loginType: "username",
},
methods: {
togglePlay: function() {
if (this.loginType === "username") {
this.loginType = "email"
} else {
this.loginType = "username"
}
}
}
});
</script>
</body>
</html>
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
65
66
67
68
69
70
71
72
73
74
75
注意:
- 可以把<template>元素当成不可见的包裹元素,并在上面使用v-if,最终的渲染结果不包含<template>元素。
- v-else元素必须紧跟在v-if或v-else-if的后面,否则它不会被识别到。
- Vue会尽可能高效地渲染元素,通常复用已有元素而不是从头开始渲染,比如上面切换用户名邮箱时里面的已有的输入内容是不变的。
- 如果希望两个元素时完全独立的,不要复用它们,只需要添加一个具有唯一值的key属性即可。
- v-if是真正条件渲染的,当条件为假时,是不会渲染其中的元素。而v-show不管条件是什么都会渲染元素,但是会根据条件进行显示隐藏。v-if有更高的切换开销,而v-show有更高的初始化渲染开销。
# 列表渲染
我们可用v-for指令来基于一个数组渲染一个列表:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
<br>
使用of代替in
<ul id="example-1.1">
<li v-for="item of items" :key="item.message">
{{ item.message }}
</li>
</ul>
<br>
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
在v-for中使用对象
<ul id="v-for-object" class="demo">
<li v-for="value in objectFor">
{{ value }}
</li>
</ul>
使用键名
<div v-for="(value, name) in objectFor">
{{ name }}: {{ value }}
</div>
<br>
使用键名,索引
<div v-for="(value, name, index) in objectFor">
{{ index }}. {{ name }}: {{ value }}
</div>
<br>
使用数组来进行列表显示
<div v-for="(value, index) in arrayFor" v-bind:key="index">
{{ index }}: {{ value }}
</div>
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
parentMessage: 'Parent',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
],
objectFor: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
},
arrayFor: [
"apple", "orange", "banana"
]
}
});
app.arrayFor.unshift("hello")
app.arrayFor[2] = "hehe"
app.arrayFor = app.arrayFor.filter(function(item) {
return item.indexOf("e") > -1;
})
</script>
</body>
</html>
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
注意:
- Vue使用v-for渲染元素列表时,默认使用"就地更新",如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并确保它们在每个索引位置正确渲染,这个默认模式是高效的,但只适用于不依赖于子组件状态或临时DOM状态的列表渲染输出。
- Vue将被侦听的数组的变更方法进行包裹,它们会触发视图更新,这些方法是:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
- 对于数组有一些方法是返回新数组,比如filter,concat,slice,这些返回的数组可以直接替换原来的数组,Vue并不会丢弃现有DOM并重新渲染整个列表,而是为了使DOM元素最大范围的重用而实现一些智能启发式的方法。
下面是v-for的其他用法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuejs测试</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<li v-for="n in evenNumbers">{{ n }}</li>
<br>
<!-- 计算属性不适合下面这种场景 -->
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
<br>
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
<br>
<ul>
<template v-for="item in numbers">
<li>{{ item }}</li>
</template>
</ul>
<br>
<ol>
<li v-for="item in list">
<span v-if="!item.condition">{{item.content}}</span>
<span v-else style="text-decoration: line-through;">{{item.content}}</span>
</li>
</ol>
<ol>
<li v-for="item in list" v-if="!item.condition">
{{ item.content }}
</li>
</ol>
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
numbers: [1, 2, 3, 4, 5],
sets: [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
],
list: [{
content: "course 1",
condition: false
},
{
content: "course 2",
condition: true
}
],
},
computed: {
evenNumbers: function() {
return this.numbers.filter(function(number) {
return number % 2 === 0
})
}
},
methods: {
even: function(numbers) {
return numbers.filter(function(number) {
return number % 2 === 0
})
}
}
});
</script>
</body>
</html>
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79