什么是MVC

视图(View):用户界面。
控制器(Controller):业务逻辑
模型(Model):数据保存

View 传送指令到 Controller
Controller 完成业务逻辑后,要求 Model 改变状态
Model 将新的数据发送到 View,用户得到反馈

那么怎么做呢

1.模块化你的 js 代码
将同一模块的 js 代码放在同一个文件夹里并正确命名,用立即执行函数相关博客封装代码,防止出现全局变量

1
2
3
4
5
<!-- 模块化 -->
<script src="./js/init-swiper.js"></script>
<script src="./js/auto-slide-up.js"></script>
<script src="./js/sticky-topbar.js"></script>
<script src="./js/smoothly-navigation.js"></script>

2.设置 V 和 C

2.1.首先简单区分出 V 和 C
找到 js 模块对应的 html 模块,即是 view
view 的作用是告诉 js 哪一部分是对应模块的 view
以轮播模块为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 这就是 view ,用户可以看到 -->
<div id="mySlides">
<!-- Slider main container -->
<div class="swiper-container">
<!-- Additional required wrapper -->
<div class="swiper-wrapper">
<!-- Slides -->
<img src="./img/works/nav-page.jpg" class="swiper-slide">
<img src="./img/works/canvas.jpg" class="swiper-slide">
<img src="./img/works/apple-style-slides.jpg" class="swiper-slide">
</div>
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
</div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>

2.1.1.在 js 中声明一个 view 作为 js 模块的 view,如轮播模块的 view 为 #mySlides
2.1.2.声明一个 controller 他是 view 的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
!function(){
var view = document.querySelector('#mySlides')
var controller = function(view){
var mySwiper = new Swiper(view.querySelector('.swiper-container'), {
loop: true,
// If we need pagination
pagination: {
el: '.swiper-pagination',
},

// Navigation arrows
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
})
}
controller(view)
}.call()

2.2.再简化一下
用另一个模块 topNavBar 举例(可以看到每个模块结构是一致的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
! function () {
var view = document.querySelector('#topNavBar')
//把 controller 变成对象
var controller = {
//把函数放到 init 里(init 即是初始化)
init: function (view) {
window.addEventListener('scroll', function (x) {
if (window.scrollY > 0) {
topNavBar.classList.add('sticky')
} else {
topNavBar.classList.remove('sticky')
}
})
}
}
//此时 controller(view) 就变成了 controller.init(view)
controller.init(view)
}.call()

下面是关键

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
! function () {
var view = document.querySelector('#topNavBar')
var controller = {
//开始 controller 有个空的 view
view: null,
//有个初始化函数
init: function (view) {
//把 view 存到 controller 的 view 里
this.view = view
//下面代码 this.bindEvents.call(this)
this.bindEvents()
},
//绑定事件
bindEvents: function () {
//所以这里的 this 就是上面的 this
//上面的 this 就等于 controller.init(view)
//controller.init(view) 的 this 就是 controller
var view = this.view
window.addEventListener('scroll', function (x) {
if (window.scrollY > 0) {
topNavBar.classList.add('sticky')
} else {
topNavBar.classList.remove('sticky')
}
})
}
}
//下面代码等价于controller.init.call(controller,view) 即 this 就是controller
controller.init(view)
}.call()

controller 有个 view,有个初始化函数,并可以绑定事件
下面优化绑定事件函数内部代码,让其只起绑定事件的作用

1
2
3
4
5
6
7
window.addEventListener('scroll', function (x) {
if (window.scrollY > 0) {
topNavBar.classList.add('sticky')
} else {
topNavBar.classList.remove('sticky')
}
})

由于 addEventListener 里面的 this 代表用户触发的元素
但是我们希望 this 与原来一致
解决方法一:用 bind()

1
2
3
4
5
6
7
window.addEventListener('scroll', function (x) {
if (window.scrollY > 0) {
topNavBar.classList.add('sticky')
} else {
topNavBar.classList.remove('sticky')
}
}).bind(this)

解决方法二:
用箭头函数,由于箭头函数没有 this ,所以当我们在其内部使用 this 默认就是外部的 this。
可以说箭头函数内外 this 不变,我们的目的就是让函数内外 this 不变

然后将 addClass 和 remove Class 事件也用各自的函数分隔开,同样用 this 和 view 串起来
完整代码

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
! function () {
var view = document.querySelector('#topNavBar')
var controller = {
view: null,
init: function (view) {
this.view = view
this.bindEvents()
},
bindEvents: function () {
var view = this.view
window.addEventListener('scroll', (x) =>{
if (window.scrollY > 0) {
this.active()
} else {
this.deactive()
}
})
},
active:function(){
this.view.classList.add('sticky')
},
deactive:function(){
this.view.classList.remove('sticky')
}
}
controller.init(view)
}.call()

轮播完整代码

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
! function () {
var view = document.querySelector('#mySlides')
var controller = {
view: null,
swiper: null,
swiperOptions: {
loop: true,
pagination: {
el: '.swiper-pagination',
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
},
init: function (view) {
this.view = view
this.initSwiper()
},
initSwiper: function () {
this.swiper = new Swiper(
this.view.querySelector('.swiper-container'),
this.swiperOptions
)
},
}
controller.init(view)
}.call()

smoothly-navigation.js 模块代码

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
! function () {
var view = document.querySelector('nav.menu')
var controller = {
view: null,
aTags: null,
init: function (view) {
this.view = view
this.initAnimation()
this.bindEvents()
},
initAnimation: function () {
function animate(time) {
requestAnimationFrame(animate);
TWEEN.update(time);
}
requestAnimationFrame(animate);
},
scrollToElement: function (element) {
let top = element.offsetTop
let currentTop = window.scrollY
let targetTop = top - 80
let s = targetTop - currentTop //路程
var coords = {
y: currentTop
}; //起始位置
var t = Math.abs(s / 100) * 300;
if (t > 500) {
t = 500
} //时间
var tween = new TWEEN.Tween(coords) //起始位置
.to({
y: targetTop
}, t) //结束位置和时间
.easing(TWEEN.Easing.Quadratic.InOut) //缓动类型
.onUpdate(function () {
//coords.y 已经变了
window.scrollTo(0, coords.y) //如何更新界面
})
.start(); //开始缓动
},
bindEvents: function () {
let aTags = this.view.querySelectorAll('nav.menu > ul > li > a')
for (let i = 0; i < aTags.length; i++) {
aTags[i].onclick = (x) =>{
x.preventDefault()
let a = x.currentTarget
let href = a.getAttribute('href') //'#siteAbout'
let element = document.querySelector(href)
this.scrollToElement(element)
}
}
}
}
controller.init(view)
}.call()

总结

所有模块结构:
在立即执行函数内部
有个 view
有个 controller
controller 操作 view
将复杂的代码模块化,然后通过对象 controller 将 view 的函数通过 this 串起来,使得每一个 view 的函数都可以被 controller 操控

后续:MVC 的 M:做一个简单的留言——leancloud 数据库


本文仅供个人学习使用