js组件开发
首先附上jquery组件开发的网站:http://www.poluoluo.com/jzxy/201406/277886.html
jquery组件开发保证chainability,通过返回this.each();
jQuery.fn.test2= function(){
this.css("background","#ff0");//这里面的this为jquery对象,而不是dom对象
return this.each(function(){ //遍历匹配的元素,此处的this表示为jquery对象,而不是dom对象
alert("this"+this+this.innerHTML); //提示当前对象的dom节点名称,这里的this关键字都指向一个不同的DOM元素(每次都是一个不同的匹配元素)。
});
};
this.css(),this.each()里面的this为jquery对象,但是alert里面this为dom对象.
为什么要return this.each()对象,所以这样就可以继续链式操作了。
js组件写法
参考链接:http://blog.csdn.net/bingqingsuimeng/article/details/44451481
组件写的不多,使劲回忆以前用到的组件,完全用js开发的组件,主要有两部分,tag名称定义和组件api,具体的实现过程不记得了。
先给出一个计算输入字符数组件的原始写法:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test</title>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(function() {
var input = $('#J_input');
//用来获取字数
function getNum(){
return input.val().length;
}
//渲染元素
function render(){
var num = getNum();
//没有字数的容器就新建一个
if ($('#J_input_count').length == 0) {
input.after('<span id="J_input_count"></span>');
};
$('#J_input_count').html(num+'个字');
}
//监听事件
input.on('keyup',function(){
render();
});
//初始化,第一次渲染
render();
})
</script>
</head>
<body>
<input type="text" id="J_input"/>
</body>
</html>
网上查了js写组件的过程,在结合自己的实践工,主要使用了三种写组件的方式:
1.作用域隔离
使用对象字面量定义一个对象,调用对象的函数方法;
var textCount = {
input:null,
init:function(config){
this.input = $(config.id);
this.bind();
//这边范围对应的对象,可以实现链式调用
return this;
},
bind:function(){
var self = this;
this.input.on('keyup',function(){
self.render();
});
},
getNum:function(){
return this.input.val().length;
},
//渲染元素
render:function(){
var num = this.getNum();
if ($('#J_input_count').length == 0) {
this.input.after('<span id="J_input_count"></span>');
};
$('#J_input_count').html(num+'个字');
}
}
$(function() {
//在domready后调用
textCount.init({id:'#J_input'}).render();
})
这样写的优点是:所有的功能都在一个变量下面。代码更清晰,并且有统一的入口调用方法
缺点:没有私有的概念,所有的方法都是公开的,例如getNum和bind方法应该是私有的方法,但是其他代码可以访问和更改他们,当代码量特别特别多的时候,很容易出现变量重复,或被修改的问题。
为了私有化方法,出现的闭包的写法。
2.闭包写法
var TextCount = (function(){ //私有方法,外面将访问不到 var _bind = function(that){ that.input.on('keyup',function(){ that.render(); }); } var _getNum = function(that){ return that.input.val().length; } var TextCountFun = function(config){ } TextCountFun.prototype.init = function(config) { this.input = $(config.id); _bind(this); return this; }; TextCountFun.prototype.render = function() { var num = _getNum(this); if ($('#J_input_count').length == 0) { this.input.after('<span id="J_input_count"></span>'); }; $('#J_input_count').html(num+'个字'); }; //返回构造函数 return TextCountFun; })(); $(function() { new TextCount().init({id:'#J_input'}).render(); })
这种写法把所有的东西都放在一个自动执行的闭包模块中,所以不受外界的影响,并且只对外返回了TextCountFun的构造函数,生成的对象只能访问到init,render方法。这种写法已经满足绝大多数的需求了。事实上大部分的jQuery插件都是这种写法。
3.面向对象方式
上面的写法已经可以满足大部分需求,但是当一个页面特别复杂,我们需要做一套组件。仅仅用这个就不行了。首先的问题就是,这种写法太灵活了,写单个组件还可以。如果我们需要做一套风格相近的组件,而且是多个人同时在写。那真的是噩梦。
在编程的圈子里,面向对象一直是被认为最佳的编写代码方式。比如Java,就是因为把面向对象发挥到了极致,所以多个人写出来的代码都很接近,维护也很方便。但是很不幸的是,javascript不支持class类的定义。但是我们可以模拟。
先实现一个简单的js类,作为base类。
var Class = (function() {
var _mix = function(r, s) {
for (var p in s) {
if (s.hasOwnProperty(p)) {
r[p] = s[p]
}
}
}
var _extend = function() {
//开关 用来使生成原型时,不调用真正的构成流程init
this.initPrototype = true
var prototype = new this()
this.initPrototype = false
var items = arguments.slice() || []
var item
//支持混入多个属性,并且支持{}也支持 Function
while (item = items.shift()) {
_mix(prototype, item.prototype || item)
}
// 这边是返回的类,其实就是我们返回的子类
function SubClass() {
if (!SubClass.initPrototype && this.init)
this.init.apply(this, arguments)//调用init真正的构造函数
}
// 赋值原型链,完成继承
SubClass.prototype = prototype
// 改变constructor引用
SubClass.prototype.constructor = SubClass
// 为子类也添加extend方法
SubClass.extend = _extend
return SubClass
}
//超级父类
var Class = function() {}
//为超级父类添加extend方法
Class.extend = _extend
})()
基类的使用方式为:
/继承超级父类,生成个子类Animal,并且混入一些方法。这些方法会到Animal的原型上。 //另外这边不仅支持混入{},还支持混入Function var Animal = Class.extend({ init:function(opts){ this.msg = opts.msg this.type = "animal" }, say:function(){ alert(this.msg+":i am a "+this.type) } }) //继承Animal,并且混入一些方法 var Dog = Animal.extend({ init:function(opts){ //并未实现super方法,直接简单使用父类原型调用即可 Animal.prototype.init.call(this,opts) //修改了type类型 this.type = "dog" } }) //new Animal({msg:'hello'}).say() new Dog({msg:'hi'}).say()
其实就是重新覆盖或者定义了基类中的抽象出的方法。
本文实例请参考原文链接,
抽象出base
可以看到,我们的组件有些方法,是大部分组件都会有的。
- 比如init用来初始化属性。
- 比如render用来处理渲染的逻辑。
- 比如bind用来处理事件的绑定。
抽象出这三个方法,都按照这个约定写,开发大规模组件库就变得更加规范,相互之间配合也更容易。
事实上,这边的init,bind,render就已经有了点生命周期的影子,但凡是组件都会具有这几个阶段,初始化,绑定事件,以及渲染。当然这边还可以加一个destroy销毁的方法,用来清理现场。
在组件开发中,引入事件机制 ,以便向外部报出组件当前状态。
观察者模式
想象一下base是个机器人会说话,他会一直监听输入的字数并且汇报出去(通知)。而你可以把耳朵凑上去,听着他的汇报(监听)。发现字数超过5个字了,你就做些操作。
//辅组函数,获取数组里某个元素的索引 index
var _indexOf = function(array,key){
if (array === null) return -1
var i = 0, length = array.length
for (; i < length; i++) if (array[i] === item) return i
return -1
}
var Event = Class.extend({
//添加监听
on:function(key,listener){
//this.__events存储所有的处理函数
if (!this.__events) {
this.__events = {}
}
if (!this.__events[key]) {
this.__events[key] = []
}
if (_indexOf(this.__events,listener) === -1 && typeof listener === 'function') {
this.__events[key].push(listener)
}
return this
},
//触发一个事件,也就是通知
fire:function(key){
if (!this.__events || !this.__events[key]) return
var args = Array.prototype.slice.call(arguments, 1) || []
var listeners = this.__events[key]
var i = 0
var l = listeners.length
for (i; i < l; i++) {
listeners[i].apply(this,args)
}
return this
},
//取消监听
off:function(key,listener){
if (!key && !listener) {
this.__events = {}
}
//不传监听函数,就去掉当前key下面的所有的监听函数
if (key && !listener) {
delete this.__events[key]
}
if (key && listener) {
var listeners = this.__events[key]
var index = _indexOf(listeners, listener)
(index > -1) && listeners.splice(index, 1)
}
return this;
}
})
var a = new Event()
//添加监听 test事件
a.on('test',function