YongSir

专业程序员伪装者

js组件化演进探讨-从简单的封装说起

先从一个slideShow的封装说去

在前端虽说已经有了一些很成熟的UI库,但是对于我这个有过客户端开发经验的人总是忍不住会对一些常见的组建进行自以为是的封装,总想着向MFC,Cocoa那样完全组件化的开发,特别是针对于UI层级,但是考虑到前端的特殊性,这两种封装思路有时候并不经相同,所以想借着一个轮播slide的Demo,浅显的梳理一下。

直接从最关键的接口设计入手:于客户端封装相比,前端的组件可以全部js化,也可以html实现,更多的是二者兼而有之,而客户端平台往往只需要管理好接口API权限和参数,没有这种选择性的问题,比如在本例中,可以这样:

1
2
<script scr='slideShow.js'></script>
<div class='slideShow'></div>

仅仅通过一个div借助js来实现,至于轮播的图像数据,以及其他元素全部通过js来添加,看起来很美妙:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$.fn.slide = function(opt) {
// 创建5个img
// img设置样式,src等等
// img包裹个几层
...

// 创建一个指示器
...

// 创建next/pre btn等等
}

// 调用
$('.slideShow').slide({
width: 300px;
height: 200px;
numOfCell: 5;
auto: true;
...
})

也可在更多html的基础上,如:

1
2
3
4
5
6
7
<div class='banner slideShow'>
<img class='picCell' scr='http://001.png'></img>
<img class='picCell' scr='http://001.png'></img>
<img class='picCell' scr='http://001.png'></img>
<img class='picCell' scr='http://001.png'></img>
<img class='picCell' scr='http://001.png'></img>
</div>

这一种明显给了更多的html内容,特别是img元素样式的单独分类,让变动更加直接,看起来也还不错,为了方便使用jQuery,具体例子的gits:https://github.com/13hoop/sliderShow/blob/master/index.html

1
2
3
4
5
6
// 调用方式
$('.banner').slides({
width: 480,
height: 360,
auto: true
})

以上2中共同之处都是直接封装的$.fn上,这样调用时通过$('.banner')调用,看起来的确是很方便,但遗憾的是直接从是对jQuery的扩展,但一下子让所有的页面都具有了sliderShow,太过粗暴的硬塞给所有依赖jQuery的文件,很多时候是没有必要的

困惑 😖

我自己第一个很困惑的问题是对于样式在封装中的处理
是用js创建样式,还是不使用?如果全部用js会不会引入其他问题,比如有2个同样的控件,同样的样式要js创建2遍,性能如何?如果按照web规范,结构是结构,样式是样式,那对于基本不变的样式如在移动端页面,岂不是不能做到开箱即用?在纠结以一段时间之后,很可悲的发现与其这样瞎想,不如直接动手试试,尝试几次之后,不得不接受这样的结论:没有一个一劳永逸的准则,具体控件具体实现?🤔,完全是废话根没说一样😂,看来这个问题只能探讨到这儿了

接着说封装形式,前边是封装到jQuery的fn上,如果借助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
// 创建实例
new Banner($('.banner'), function() {

})

// Banner`类`
var Banner = function($target, callback) {
this.$target = $target
this.callback = callback
this.init()
this.bind()
}
Banner.prototype.init = function() {
// 创建5个img
// img设置样式,src等等
// img包裹个几层
...

// 创建一个指示器
...

// 创建next/pre btn等等
}
Banner.prototype.bind = function() {
// 事件 here
}

很方便简洁的代码组织形式,自己偶尔写写简单基本都是按这个套路的,比如一个懒加载的图片墙,采用类型的封装之后就是:

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
var Explode = function ($target, callback) {
this.$target = $target
this.callback = callback
this.check()
this.bind()
}
Explode.prototype.bind = function () {
var _this = this
var clock // 模拟网络延伸
$(window).on('scroll', function (e) {
if (clock) {
clearTimeout(clock)
}
clock = setTimeout(function () {
console.log('hello')
_this.check()
}, 100)
})
}
Explode.prototype.isVisible = function () {
var height_win = $(window).height()
var scrolled = $(window).scrollTop()
var height = this.$target.height()
var offTop = this.$target.offset().top
if (height_win + scrolled >= offTop && offTop + height >scrolled) {
return true
} else {
return false
}
}
Explode.prototype.check = function() {
if(this.isVisible(this.$target)) {
this.callback(this.$target)
}

$('.container img').each(function (k, img) {
var $value = $(img)
new Explode($value, function ($target) {
var url = $value.attr('data-src')
if ($value.hasClass('loaded')) return
console.log(k + '~~~')
$value.attr('src', url)
$value.addClass('loaded')
})
})

最好的方式

但缺陷还是有的,最明显的就是没有实现权限管理,暴漏了太多,不妨再来一次:

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
var Explode =(function () {
var _Explode = function ($target, callback) {
this.$target = $target
this.callback = callback
this.check()
this.bind()
}
_Explode.prototype.bind = function () {
var _this = this
var clock // 模拟网络延伸
$(window).on('scroll', function (e) {
if (clock) {
clearTimeout(clock)
}
clock = setTimeout(function () {
console.log('hello')
_this.check()
}, 100)
})
}
_Explode.prototype.isVisible = function () {
var height_win = $(window).height()
var scrolled = $(window).scrollTop()
var height = this.$target.height()
var offTop = this.$target.offset().top
if (height_win + scrolled >= offTop && offTop + height > scrolled) {
return true
} else {
return false
}
}
_Explode.prototype.check = function () {
if (this.isVisible(this.$target)) {
this.callback(this.$target)
}
}

return {
init: function(targets, callback) {
$(targets).each(function(idx , target){
new _Explode($(target), callback)
})
},
// once: function(targets, callback) {
// $(targets).each(function(idx , target){
// new _Explode($(target), callback)
// })
// }
}

})()

Explode.init($('.container img'), function($elNode) {
var url = $elNode.attr('data-src')
if ($elNode.hasClass('loaded')) return
$elNode.attr('src', url)
$elNode.addClass('loaded')
})

通过一个匿名函数,实现对_Explode代码的封装,同时借助return返回需要对外开放的方法,即添加了权限开放管理,又节省了命名空间,还在调用上获得得了便利,这才是比较容易接受的好方法

总结

扯了这么多,总是觉得单单控件的实现并没有怎样的难度,在前端反倒是对这些空间站的管理才是令人头大的,好在现在的套路基本被摸索出来了,那就是模块化js的开发规则,上述的最后一种方式,其实已经有一些模块化的影子了,但并不能很好的处理组建的依赖问题,而这些将在模块化js中一并解决,具体会在js模块化管理的探索后续文章中讨论