Vue生命期钩子
# ES6
常见的es6语法.. 不用担心兼容性问题.大胆用! 因为脚手架里有个Balel模块会进行高版本es语法转化
回顾一点知识: 详情请翻阅 - js基础语法之函数.md
无论是var关键字还是let关键字定义 根据变量的位置, 区分为 局部全局变量
作用域而言
var关键字定义的 作用域 函数内部/局部作用域、全局作用域
let关键子定义的 "作用域 只包含在自己所在的花括号里"
变量被使用时,查找顺序(跟python一样!)
首先在函数内部查找变量, 找不到则到外层函数查找, 逐步找到最外层.
函数的作用域关系是在定义阶段就固定死的,与调用位置无关.
默认情况下,if子代码块内声明变量的作用域是与if所在的位置保持一致的,即if在全局代码块里的变量的作用域就在全局,在函数里就局部
变量提升
变量提升就是将所有的 变量声明语句 提升到变量自己作用域的最上面! 代码还是一行行的依次运行.
var有,let和const没有!
函数提升
函数的提升是将 函数这一个整体 提升到所有代码最前面
也就是说,后面定义的函数,在前面也可以调用!(python的函数必须先定义再调用)
若函数名和变量名一样,函数提升优先于变量提升.
js的函数只能返回一个值.(可将多个值放到数组或类似于字典的对象中)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xx.yy 就是取 xx对象的yy属性!! 喵的, 很简单自己却忽略没注意的知识点, 想到它, 一通就百通了.
# var/let/const
注意它们的作用域范围.
它们仨的变量查找规则没啥区别. 只是this.xx的时候要多思考下.
var
<script>
/* 全局变量 */
var a = "aaa";
if (1 === 2) {
/* 涉及到 变量提升,所以打印b结果为undefined - 申明了未赋值 */
var b = "bbb";
}
console.log(a); // aaa
console.log(b); // undefined
/* 局部变量 */
function show() {
if (1 === 1) {
var c = "ccc";
}
console.log(c);
}
show() // ccc
// console.log(c); // 报错!
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let
<script>
let d = 10
console.log(d) // 10
/* let是以代码块为作用域,即 ★let关键子定义的变量的 作用域 “只包含在自己所在的花括号里” */
if (1 === 1) {
let e = 20
}
// console.log(e) // 报错
</script>
2
3
4
5
6
7
8
9
10
const
<script>
// const常量 - 块级作用域 + 不能修改
if (1 === 1) {
const f = 30
}
// console.log(f) // 报错
/* 常量不能被重新赋值/内存地址不能被修改,但若该常量是对象,那么该对象里面的值是可以修改的. */
const g = {age: 20}
console.log(g) // {age: 20}
g.age = 18
console.log(g) // {age: 18}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
# 模版字符串
<script>
let username = "lqz"
let age = 19
console.log(`姓名:${username},年龄:${age}`)
</script>
2
3
4
5
# 动态参数
...data 作为形参,接受多余的传值 ; ...data 作为实参数, 将对象中的一个个元素拿出来依次传递给函数
<script>
function info1(v1, ...data) {
console.log(v1, data)
}
info1(1, 2) // 1 [2]
info1(1, 2, 3, 4) // 1 [2, 3, 4]
function info2(v1, v2, v3, v4) {
console.log(v1, v2, v3, v4)
}
info2(1, 2, 3, 4) // 1 2 3 4
let nums1 = [2, 3, 4]
info2(1, ...nums1) // 1 2 3 4 正好
let nums2 = [2, 3]
info2(1, ...nums2) // 1 2 3 undefined 传少了
let nums3 = [2, 3, 4, 5]
info2(1, ...nums3) // 1 2 3 4 传多了
function info3(v1, ...data) {
console.log(v1, data)
}
info3(1, 2, 3, 4) // 1 [2,3,4]
let nums4 = [2, 3, 4]
info3(1, ...nums4) // 1 [2,3,4] 注意这里!
info3(1, nums4) // 1 [[2,3,4]] 注意这里!
</script>
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
# 解构赋值
对象和数组都可以解构赋值
<script>
let info = {username: "武沛齐", age: 20, addr: "北京"}
/* 不是瞎写的,要与对象里的键名相对应 */
let {username, addr} = info
console.log(username, addr) // 武沛齐 北京
let data = {a: 11, b: 22, c: 33}
/* 函数参数本应不该写花括号,写了证明它是解构的意思
那么只需要传入对象的某几个键值就可以进行解构, 这就使得就无需 对象.xx 这样的操作了. 需要啥拿啥.
*/
function getData(n1, {a, b}) {
console.log(n1, a, b)
}
getData(0, data) // 0 11 22
let nums = [11, 22, 33, 44]
let [n1, n2] = nums
/* 根据数组中下标顺序给 */
console.log(n1, n2) // 11 22
function f(v1, [n1, n2, n3, n4, n5]) {
console.log(v1, n1, n2, n3, n4, n5)
}
f(0, nums) // 0 11 22 33 44 undefined
</script>
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
# 箭头函数
注意: 与var关键字不同, 使用let在全局作用域中声明的变量不会成为window对象的属性, var声明的变量则会.
看this是什么,this.xx是取this指向对象中的xx属性的值.. 像在全局用var定义的变量就会作为window对象的属性..
# 普通函数中this
我们先来看看js中普通函数的this指向.. 切记一点, 普通函数内部的this,是在函数调用的时候来确定其指向的!!
<body>
<button id="btn">按钮</button>
<script>
/* ****** 示例1 ****** */
/* 直接调用函数,函数内部的this指向的是window对象. why?
* 因为每个函数都是window对象的属性,你调用了某个函数,其实就是调用了window对象的该属性!!
* 函数中的this就自然指向了window.
* */
function xx() {
console.log(this === window) // true
}
xx() // 等同于 window.xx()
/* ****** 示例2 ****** */
/* 根据结果可以看的,this指向的是yy这个对象. why?
* c函数是被yy对象调用的.那么c函数中的this自然就指向了yy对象.
* (c函数,准确点说应该是yy对象中c键对应的匿名函数)
*/
var yy = {
a: 2,
c: function () {
console.log(this) // {a: 2, c: ƒ}
}
}
yy.c()
/* ****** 示例3 ****** */
var zz = {
a: 2,
c: function () {
console.log(this) // {a: 2, c: ƒ}
/* f1函数只是在c函数里运行罢了,f1函数并不是yy对象的属性 */
function f1() {
console.log(this) // window对象
}
f1()
}
}
zz.c()
/* ****** 示例4 ****** */
/* why?
* 函数是对象,对象是按照引用传递的,o.s赋值给变量f,相当于把o.s函数指向了变量f
* 并且在f变量处调用,而变量f其实是window对象的属性
* 所以实际在执行时就变成了调用window对象的f函数.
* 细品这句话: 函数内部的this,是在函数调用的时候来确定其指向的!!
* */
var o = {
s: function () {
console.log(this) // windows对象
}
}
var f = o.s; // 注意此时 o.s函数并没有被调用执行
f()
/* ****** 示例5 ****** */
/* 点击后,打印该this,是dom节点!!为啥不是window对象呢?why?
* 你再细品这句话: 函数内部的this,是在函数调用的时候来确定其指向的!!
* 在该示例里,是dom节点调用的该函数,so,本例中的this指向触发事件的对象 - dom节点.
*/
var btn = document.getElementById('btn');
btn.onclick = function () {
console.log(this) // <button id="btn">按钮</button>
};
// Ps:其他的,new关键字活改变函数内this的指向,使其指向刚创建的对象.关乎new操作的运行原理.
</script>
</body>
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
# 箭头函数中this
箭头函数中this的指向是它所在的父级函数的作用域, 而不是它所在的那个函数.
因为js中的函数通过function关键字进行定义, 所以箭头函数没有形成自己的作用域. 所以业内公认es6只是语法糖,让你写的爽.
<script>
/* 等同于
function xx2() {
return 555
}
* */
let xx = () => 555
console.log(xx()) // 555
/* 若返回的是对象,需要用()包裹,因为不加的话,直接{},在js引擎里会被认为是代码块,会报错*/
let _obj = _n => ({id: _n, name: "dc"})
console.log(_obj(1)) // {id: 1, name: "dc"}
let bb = {
name:'lqz',
init:function (){
console.log(this) // bb对象
}
}
bb.init()
let cc = {
name:'dc',
init:()=>{
console.log(this) // window对象
}
}
cc.init()
</script>
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
进阶,上点难度
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
let ee = {
data: null,
send: function () {
axios({
method: "get",
url: "https:/127.0.0.1:8000/user/",
}).then(function(res) {
console.log(this); // windows这个对象
})
}
}
ee.send();
let ff = {
data: null,
send: function () {
axios({
method: "get",
url: "https:/127.0.0.1:8000/user/",
}).then((res) => {
console.log(this); // ff这个对象
})
}
}
ff.send();
</script>
<script>
const obj1 = {
name: 'Alice',
sayHello: function() {
console.log(this) // obj1对象
setTimeout(function() {
console.log(this) // window对象
}, 1000);
}
};
obj1.sayHello();
const obj2 = {
name: 'Alice',
sayHello: function() {
console.log(this) // obj2对象
setTimeout(() => {
console.log(this) // obj2对象
}, 1000);
}
};
obj2.sayHello();
</script>
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
# 细品
你得先分析, 里面的两个this分别是什么.
<script>
let d = "源代码"
let info1 = {
d: "武沛齐",
f3: function () {
console.log(this.d) // 武沛齐
function getData() {
/* 与var关键字不同, 使用let在全局作用域中声明的变量不会成为window对象的属性, var声明的变量则会
* So,若在全局定义的是 var d="源代码" 而不是let d = "源代码"
* 那么window对象中就有d,console.log(this.d)就能打印出 "源代码"
* /
console.log(this.d); // undefined
console.log(d) // 源代码 遵循变量查找规则
}
getData();
}
}
info1.f3()
</script>
<!-- 再来,举一反三,getData若写成箭头函数 -->
<script>
let d = "源代码"
let info1 = {
d: "武沛齐",
f3: function () {
console.log(this.d) // 武沛齐
var a = () => {
console.log(this.d); // 武沛齐
console.log(d) // 源代码
}
a();
}
}
info1.f3()
</script>
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
# 模块导入导出
在vue3中, 啥都要导入. 需要什么就导入什么.
基本用法
导入时起个别名
导出一个对象
# 何为钩子
何为钩子函数?
程序从开始到结束,在这一过程的某处,挂上钩子,钩子上有个函数,走到此处就会执行这个钩子. [写了就执行,不写就不执行]
扩展:
- 面向过程编程
- oop 面向对象编程
- AOP 面向切面编程(是对oop的一个补充)
注:AOP它是一种设计理念!!代码原本应从头到尾执行的好好的,但你可以在中间插入一个函数,让其执行.
- java的spring框架中大量使用
- python中的装饰器
- drf的序列化源码的钩子函数,用了反射,有则执行,没有就不执行
2
3
4
5
6
7
8
9
10
# Vue生命周期图
图中已经写的很详细了.
进一步解释.
Vue是单页面的组件开发.
- vue的实例 vm (new Vue)
- 页面里的组件 vc (Vue.component)
vm和vc都是有生命周期的.
生命周期共有8个钩子函数(4对),会依次调用执行!
- beforeCreate 创建Vue实例之前调用
- created 创建Vue实例成功后调用(可以在此处发送ajax请求后端数据) '用的比较多'
- beforeMount 挂载/渲染DOM (即插值、指令等) 之前调用
- mounted 挂载/渲染DOM (即插值、指令等) 之后调用
- == '到这里,初始化完成了' == -
- beforeUpdate 重新渲染之前调用 (数据更新等操作时,有变化,会控制DOM重新渲染)
- updated 重新渲染完成之后调用
- == '只要不销毁, 那么当页面发生变化,就会重复执行beforeUpdate、updated这两个阶段/一直循环 ' == -
- beforeDestroy 销毁之前调用
- destroyed 销毁之后调用
特别注意两点
1> 在beforeCreate阶段发送ajax请求太早了,因为该阶段vue实例还没有创建出来,意味着变量还未初始化出来!
请求的数据回来了,变量还没有肯定是不行的.
So,向后端发送请求应该写在created中.保证vue实例已经存在了.
2> beforeUpdate
vue的m-v-vm架构, view(即html、css) - vm(虚拟dom) - model(即js数据).
当js数据变化时,没有直接替换上,当要beforeUpdate的时候才会将数据替换到页面上!!开始实现双向数据绑定.页面重新渲染.
3> 组件销毁,可用v-if将该组件移走;组件销毁,直接关闭页面
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vue的钩子函数使用总结:
参考链接 - https://blog.csdn.net/zhouzuoluo/article/details/84825943
1.beforeCreate(创建前):
beforeCreate钩子函数,这个时候, vue实例的挂载元素$el和数据对象data都为undefined, 还未初始化.
无法访问到数据和真实的dom和data中的数据,可以在这里面使用loading
2.created(创建后):
created函数中可以对data对象里面的数据进行使用和更改, 不会触发其他的钩子函数
一般可以在这里做初始数据的获取,也可以结束loading; 这里进行dom操作需要使用vue.nextTick()方法
3.beforeMount(挂载前):
beforeMount钩子函数,vue实例的$el和data都初始化了,但还是虚拟的dom节点,具体的data.filter还未替换.
在这里也可以更改数据,不会触发其他的钩子函数,一般可以在这里做初始数据的获取
4.mounted(挂载后):
mounted钩子函数,此时,组件已经出现在页面中
数据、真实dom都已经处理好了,事件都已经挂载好了,data.filter成功渲染,可以在这里操作真实dom等事情..
5.beforeUpdate (更新前):
当组件或实例的数据更改之后, 会立即执行beforeUpdate
然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染, 一般不做什么事儿
6.updated(更新后):
当更新完成后, 执行updated, 数据已经更改完成, dom也重新render完成, 可以操作更新后的虚拟dom
7.beforeDestroy(销毁前):
当经过某种途径调用$destroy方法后,立即执行beforeDestroy
一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等等
8.destroyed(销毁后):
vue实例解除了事件监听以及和dom的绑定(无响应了),但DOM节点依旧存在. 这个时候,执行destroyed, 在这里做善后工作也可以
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
# 8个钩子函数
钩子函数 | 描述 | el (挂载模版) | data |
---|---|---|---|
beforeCreate | 创建Vue实例之前调用 | 无 | 无 |
created | 创建Vue实例成功后调用 (可以在此处发送异步请求后端数据) 在这个里面向后端发送请求,获取数据. -- 注意通过请求,数据有了,但模版还未挂载 | 无 | 有 |
beforeMount | 渲染DOM之前调用 | 无 | 有 |
mounted | 渲染DOM之后调用 在这个里面写定时器. -- 模版已经挂载完成啦,可以启动一些定时任务、延迟任务. | 有 | 有 |
beforeUpdate | 重新渲染之前调用 (数据更新等操作时, 控制DOM重新渲染) | 有 | 有 |
updated | 重新渲染完成之后调用 | 有 | 有 |
beforeDestroy | 销毁之前调用 | 有 | 有 |
destroyed | 销毁之后调用 若有定时器,记得清除定时器!! 因为组件销毁后,不清理定时器,定时器会一直执行 | 有 | 有 |
# 测试代码.
这里为了方便理解生命周期,需提前引入新知识, 定义Vue组件.
每个组件都有自己的html、css、数据以及方法
# 实验一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="box">
<!-- isShow的bool值决定child标签的内容显示还是不显示 -->
<!-- 《child标签的内容是一个vue组件》 -->
<child v-if="isShow"></child>
<br>
<!-- 点击"删除子组件"按钮,vue组件销毁,8个生命周期钩子函数会依次执行 -->
<button @click="terminate">删除子组件</button>
<button @click="reborn">显示子组件</button>
</div>
</body>
<script>
// 如何定义Vue组件? 这里为了方便理解生命周期,需定义Vue组件.
// 在这个组件里写的8个生命周期钩子函数和在Vue实例里写一样的逻辑
// 之所以在组件里写,是因为想看到销毁的效果.
/*
Vue.component()定义一个组件
第一个参数是组件的名字;
第二个参数是一个对象,对象中template是组件的html内容、对象中的函数data(){return{}}是数据
*/
Vue.component('child', {
// ``反引号是es6的模版字符串
// {{name}} 运用了插值语法
template: `
<div>
{{name}}
<!-- 点击button按钮,name的值会发生对应的变化! -->
<button @click="name='Darker1'">更新数据1</button>
<button @click="name='Darker2'">更新数据2</button>
</div>`,
data() {
return {
name: 'Darker1',
}
},
// this指的是我们定义的名为‘child’的vue组件.可查看该vue组件的template模版、data属性.可查看data中name变量的值.
// console.group将打印的分为了一组,可以折起来.
beforeCreate() {
console.group('当前状态: beforeCreate')
// 全是undefined,在创建之前这都东西肯定是没有的.
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
},
created() {
console.group('当前状态:created')
// el没有挂载,即html的内容是没有的;但data和name是有的.
// ★ 所以往往会在created钩子函数里去后端拿数据!!因为此时页面还未挂载.
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
},
// 挂载之前
beforeMount() {
console.group('当前状态:beforeMount')
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
},
// 挂载之后,页面有啦
mounted() {
console.group('当前状态:mounted')
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
// console.log("页面已被vue实例渲染, data, methods已更新");
// 向后端加载数据,创建定时器等 下面是用js实现的定时器 - 每隔3秒就打印一次123.
// ★ 开发中可以隔几秒就向服务器发送一个请求,拿到服务器的性能. -- 轮询.
// http是基于请求的响应. eg: web版微信,实现实时的前后端通信,开启了定时器一直向后端发请求拿数据.
// Ps:还有一个延时任务setTimeout()
// 注意,创建了定时器,记得在beforeDestroy或者destroyed中清理!!
// PS: 别那么死板,定时器写在其他生命周期函数中,比如created中也是可以的..
this.t = setInterval(function () {
console.log('123 ')
}, 3000)
},
// 前四个钩子函数执行完后,会停住,当组件中有数据变化,才会触发beforeUpdate和updated的执行.
beforeUpdate() {
console.group('当前状态:beforeUpdate')
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
},
updated() {
console.group('当前状态:updated')
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
},
// 删除子组件,才会执行beforeDestroy和destroyed
beforeDestroy() {
console.group('当前状态:beforeDestroy')
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
},
destroyed() {
console.group('当前状态:destroyed')
console.log('当前el状态:', this.$el)
console.log('当前data状态:', this.$data)
console.log('当前name状态:', this.name)
//组件销毁,并清理定时器
clearInterval(this.t)
this.t = null
// console.log('destoryed')
},
})
// Vue实例
let vm = new Vue({
el: '#box',
data: {
isShow: true
},
methods: {
terminate() {
this.isShow = false
},
reborn() {
this.isShow = true
}
}
})
</script>
</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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# 实验二
我们说, 每个组件都有自己的html、css、数据以及方法, 那将vue的实例vm也添加上这8个生命周期函数呢?
可以看到, 我们在此处定义的vue组件child 挂载完后,根vue才会接着挂载完; child组件的created在根Vue的created后面.
点击数据更新只涉及到child组件的变化; 点击删除子组件、显示子组件才涉及到vm根组件的变化.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="box">
<child v-if="isShow"></child>
<br>
<button @click="terminate">删除子组件</button>
<button @click="reborn">显示子组件</button>
</div>
</body>
<script>
Vue.component('child', {
template: `
<div>
{{ name }}
<button @click="name='Darker1'">更新数据1</button>
<button @click="name='Darker2'">更新数据2</button>
</div>`,
data() {
return {
name: 'Darker1',
}
},
beforeCreate() {
console.log('child beforeCreate')
},
created() {
console.log('child created')
},
beforeMount() {
console.log('child beforeMount')
},
mounted() {
console.log('child mounted')
},
beforeUpdate() {
console.log('child beforeUpdate')
},
updated() {
console.log('child updated')
},
beforeDestroy() {
console.log('child beforeDestroy')
},
destroyed() {
console.log('child destroyed')
},
})
// Vue实例
let vm = new Vue({
el: '#box',
data: {
isShow: true
},
methods: {
terminate() {
this.isShow = false
},
reborn() {
this.isShow = true
}
},
beforeCreate() {
console.log('vm beforeCreate')
},
created() {
console.log('vm created')
},
beforeMount() {
console.log('vm beforeMount')
},
mounted() {
console.log('vm mounted')
},
beforeUpdate() {
console.log('vm beforeUpdate')
},
updated() {
console.log('vm updated')
},
beforeDestroy() {
console.log('vm beforeDestroy')
},
destroyed() {
console.log('vm destroyed')
},
})
</script>
</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
83
84
85
86
87
88
89
90
91
92
93
94