文章比较浅显,高手勿入。

感谢**@樱花坡道_CZT**同学纠错,文章已于2013-05-21更新, 添加了简单的选择器支持,以及新的隐式遍历操作元素。

DEMO页面(2013/05/25更新):

                var length = elms.length;
                if (length) {
                    var tmp = {}
                    for(var i=0;i<elms.length;i++){
                        that = elms;
                    }
                    tmp.length = elms.length;
                    that = tmp;
                    delete tmp;
                } else {
                    return clear(that);
                }

对于IE6,7,8看DEMO就知道是没有打算支持的,对于非标浏览器做兼容支持的话,参考类似属性和方法多写一点判断就好了。

常常写小页面自己玩的同学有没有发现,偶尔需要的一些功能,如果引用JQ,PROTOTYPE,MOOTOOLS这些著名JS库的话,代价略大,感觉略重了点。

如果你也纠结这些,想让自己的小项目略完美一些,那么为什么不写一个自己的小脚本库呢。

可能有的同学写过很多了。像是下面的这种:


	function toolDoSomeThingA(){
		//code...
	}
	function toolDoSomeThingB(){
		//code...
	}
	function toolDoSomeThingC(){
		//code...
	}
	function toolDoSomeThingD(){
		//code...
	}

这样是可以称作脚本库,比较简单直接,但是通常情况这不一定是一个最佳实践。

如果考虑到扩充不发生冲突的话,你的函数应该有一个命名空间,或者挂载在一个非WINDOW的对象之下。(特殊需求特殊考虑)

那么如果完成一个类似JQ,自己完全可以掌握,可以控制的脚本库呢?如果你感兴趣,那么就继续看下去吧:D

首先我们希望创建一个共有的元素来包装或者说来代理所有的操作,可以简化我们的重复操作,以及进行一些必要的变量保存。

然后我们希望这个元素的使用方法和JQEURY一样,可以JQUERY(ANY)来选择操作某个元素;

那我们不妨这样做,具体看注释:

// 防止重复定义,函数重载
var soulteary = soulteary ||
function(e) {
	// 例子就限定带ID的元素,比较简单
	// 你可以根据你的项目对浏览器的支持
	// 扩展浏览器支持和方法,比如:document.querySelector
	// 或者自己写兼容IE的XPATH等...
	var target = document.getElementById(e) || target;
	return target;
}

接着, 我们开始扩充这个对象,把我们的工具函数都挂载上去.这里我们使用稳妥构造创建的方法,好处详见设计模式.

接下来我们要搞一个比较简单的,仅支持webkit的mini库,支持jquery的bind,unbind,创建callback,创建ajax.

至于其他浏览器,感兴趣的童鞋可以看完本文后扩充。

我们先来说说jquery的bind和unbind,

jquery代理了我们的绑定和解除绑定, 尤其是解除绑定,我们知道 removeEventListener这个函数需要传入相同的函数, 言简意赅的说就是, 你的函数的handle要一样。

文章开始说的那种定义式的函数,很容易解决,全局的名字就是他的句柄,但是闭包内的函数呢,函数内的函数呢,callback回来的函数呢。 这个时候,我们使用对象内部空间就有了用武之地。

我们可以先设计一个数据仓库,来存放我们在这个文档中的元素和元素下绑定的事件,以及事件下的函数们。 转换成伪代码就是这样。


var funcList = {}; // 想想之中,这货该是这样的... { HTML_ELEMENT_ID: { EVENT: [ FUNCTION, ...] }, ... }, ... }

	funcList = {
		'a#click-me':{
			'click':[function A(){},function A(){}],
			'dbclick':[function A(){},function A(){}]
		}
	}

然后因为我们的句柄和原始函数都存在了这个对象中,解除绑定的时候,我们就也可以指定某个元素的某类事件,或者指定某元素,或者什么都不传入,卸载所有的事件了。

bind和unbind简单实现如下:



var soulteary = soulteary ||
function(e) {

	var target = document.getElementById(e) || target;
	// 内部对象
	var me = new Object();
	var funcList = {}; // 想想之中,这货该是这样的... { HTML_ELEMENT_ID: { EVENT: [ FUNCTION, ...] }, ... }, ... }
	me.bind = function(type, func) {
		if(!target) {
			return this;
		}
		if(typeof func !== 'function') {
			return this;
		}

		// 例子就先管潮流浏览器 和非冒泡情况
		target.addEventListener(type, func, false);
		// 例子就先管单一事件 不进行不同类型的事件叠加管理
		funcList = funcList || {};
		funcList[e] = funcList[e] || [];
		funcList[e][type] = funcList[e][type] || [];
		if(!(func in funcList)) {
			funcList[e][type].push(func);
		};

		// V8太快了, 连续绑定, 基本感觉和写一起一样,
		// 莫非是V8的语句优化, 除非timeout设置特别大的时间.
		// 区别的话,有测试的同学知道 :D
		console.log(this, '@:', funcList)

		return this;

	}
	me.unbind = function(any) {
		var mode = arguments.length;
		switch(mode) {

		case 1:
			// 偷懒就先支持某类型吧
			for(var func in funcList) {
				for(var obj in funcList[func]) {
					if(obj == any) {
						for(var e in funcList[func][obj]) {
							if(typeof funcList[func][obj][e] == 'function') {
								target.removeEventListener(obj, funcList[func][obj][e], false);
							}
						}
					}
				}
			}
			break;
		case 0:
		default:
			for(var func in funcList) {
				for(var obj in funcList[func]) {
					for(var e in funcList[func][obj]) {
						if(typeof funcList[func][obj][e] == 'function') {
							target.removeEventListener(obj, funcList[func][obj][e], false);
						}
					}
				}
			}
		}
		return this;
	}




	return target?target:me;
}

看到上面的处理,有的同学会提问,什么叫做不处理事件叠加。

是这样的,之前元素可能绑定了事件,我们需要枚举和保存下来,等处理完我们的事件后,再把事件叠加上去。 这里有一个额外的处理,就是针对在元素内写死onclick之类的事件情况,我们要保存属性事件到自己的函数变量中, 然后obj.removeAttribute(‘onclick’)来完全解除绑定…

我们接着来说下callback,我写的很简单,你可以扩充一下。


me.callback = function(params){
	if (!params.url || !params.id) {return this;}
	params.id+='__cb';
	var dom = document.getElementById(params.id);
	if (dom) {dom.parentNode.removeChild(dom);}
	var dom = document.createElement('script');
		dom.type = 'text/javascript';
		dom.src = params.url;
		dom.id = params.id;
	var body = document.getElementsByTagName('body')[0];
		body.appendChild(dom);
	return this;
}

大概思路就是创建元素,把你的url请求的元素添加文档,之前的体验都是加在head中,但是实际上不算是太友好,IE head元素有个小坑,感兴趣的童鞋可以搜索一下,题外话了,就不说了,body尾部算是一个不错的选择.

接着我们来看看AJAX,同样很简单,随便写的.



me.ajax = function(params) {
	if(!params.url) {return this;}

	// 这里做个最简单的实现
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function() {
		if(xhr.readyState == 4 && xhr.status == 200) {
			params.callback = params.callback ||
			function(data) {
				eval(data);
				return true;
			}

			params.callback(xhr.responseText);
		}
		return false;
	}
	var r = new Date();
		r = r.getMilliseconds();
		r*=r;
	params.url += '?c=' + r;
	params.mode = params.mode || 'GET';
	xhr.open(params.mode, params.url, true);
	xhr.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
	xhr.send();

	return this;
}

ajax回调的话,自定义比较多,这个参考jq或者mootools都可以…

然后是常见的属性赋值,这里少写一个获取,大家懂的。 如果你只是使用一般的标签和textarea的话… 至于多什么,自己判断type和浏览器,分别获取一下就好了,推荐做成list。

me.text = function(str){
	// 这个可以自己补全
	if ('textarea' == target.type) {
		target.value = str;
	}else{
		target.textContent = str;
	}
	return this;
}

然后这个对象或许名字长了点,和楼主的英文名都撞一起了。 那么我们给他个alias

var $ = soulteary;

然后我们就可以简单的使用下面的方法来进行便捷操作了.

$('test-me').bind('click', function(){}).unbind('click').text('内容');
$('test-me').unbind();
$.ajax({url:'/?a=1'});
$.callback({url:'/?a=2'});

这个脚本的扩展还有很多,这里没有把全局的工具方法用prototype超类重写挂载到原型上,你可以在对象外部再来一层,把对象挂载上去,看起来会更加的美观.

感谢你耐心看完这篇浅显的渣文,欢迎吐槽,欢迎留言讨论。

最后,把上面的不完整版本的完整版本(绕嘛)贴一下。

var soulteary = soulteary || function(e) {

    //这里添加你的选择器
    var me = function(e) {
        var that = this;
        //存储选择器选中的元素
        //这里添加你的选择器方法,DEMO仅支持简单的选择器 
        // h1#title      #title
        // .normal       li.normal       li
        //选择器不正确的时候清理对象
        var clear = function(e) {
            for (var o in e) {
                if (typeof o == "number") {
                    delete e[o];
                }
            }
            e.length = 0;
            return;
        }
        if (e && typeof e == 'string') {
            that.selector = e;
            if (e.indexOf('#') != -1) {
                var worker = e.toLowerCase().split('#');
                if (worker.length != 2) {
                    return clear(that);
                }
                var tagType = worker[0];
                var tagID = worker[1];
                var elem = document.getElementById(tagID);
                if (elem) {
                    if (tagType) {
                        if (elem.nodeName.toLowerCase() != tagType) {
                            that.content.length = 0;
                        }
                    } else {
                        that[0] = elem;
                        that.length = 1;
                    }
                } else {
                    return clear(that);
                }
            } else if (e.indexOf('.') != -1) {
                var worker = e.toLowerCase().split('.');
                if (worker.length < 2) {
                    return clear(that);
                }
                if (!document.getElementsByClassName) {
                    alert('如果你要支持古老的浏览器,请手动添加支持函数 :)');
                    return clear(that);
                }
                var tagType = worker[0];
                var tagClass = [];
                for (var i = 1; i < worker.length; i++) {
                    tagClass.push(worker[i]);
                }
                var elms = document.getElementsByClassName.apply(document, tagClass);
                var length = elms.length;
                if (length) {
                    if (tagType) {
                        var match = [];
                        for (var i = 0, j = elms.length; i < j; i++) {
                            if (elms[i].nodeName.toLowerCase() == tagType) {
                                match.push(elms[i]);
                            }
                        }
                        length = match.length;
                        if (length) {
                            elms = match;
                            for (var i = 0, j = elms.length; i < j; i++) {
                                that[i] = elms[i];
                                that.length = length;
                            }
                        } else {
                            return clear(that);
                        }
                    } else {
                        for (var i = 0, j = elms.length; i < j; i++) {
                            that[i] = elms[i];
                            that.length = length;
                        }
                    }
                } else {
                    return clear(that);
                }
            } else {
                var elms = document.getElementsByTagName(e);
                var length = elms.length;
                if (length) {
                    for (var i = 0, j = elms.length; i < j; i++) {
                        that[i] = elms[i];
                        that.length = length;
                    }
                } else {
                    return clear(that);
                }
            }
        } else {
            return clear(that);
        }
        //这里是支持的方法
        //设置内容方法
        that.text = function(str) {
            if (str) {
                for (var i = 0, j = that.length; i < j; i++) {
                    if ('textarea' == that[i].type) {
                        that[i].value = str;
                    } else {
                        that[i].textContent = str;
                    }
                }
                return that;
            } else {
                str = [];
                for (var i = 0, j = that.length; i < j; i++) {
                    if ('textarea' == that[i].type) {
                        str.push(that[i].value);
                    } else {
                        str.push(that[i].textContent);
                    }
                }
                return str;
            }
        }
        //保存一份函数列表
        that.funcList = that.funcList || [];
        //绑定函数
        that.bind = function(type, func) {
            if (!that.length || (typeof func !== 'function') || (typeof type !== 'string')) {
                return that;
            }
            for (var i = 0, j = that.length; i < j; i++) {
                that[i].addEventListener(type, func, false);
                funcList[type] = funcList[type] || [];
                funcList[type].push({
                    'rel': that[i],
                    'func': func
                })
            }
            return that;
        }
        //解除绑定
        that.unbind = function(any) {
            var mode = arguments.length;
            switch (mode) {
                case 1:
                    for (var i = 0, j = that.length; i < j; i++) {
                        for (var func in funcList) {
                            if (func == any) {
                                for (var j = 0, k = funcList[func].length; j < k; j++) {
                                    if (funcList[func][j]['rel'] == that[i]) {
                                        if (typeof funcList[func][j]['func'] == 'function') {
                                            that[i].removeEventListener(func, funcList[func][j]['func'], false);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return that;
                    break;
                case 0:
                default:
                    for (var i = 0, j = that.length; i < j; i++) {
                        for (var func in funcList) {
                            for (var j = 0, k = funcList[func].length; j < k; j++) {
                                if (funcList[func][j]['rel'] == that[i]) {
                                    if (typeof funcList[func][j]['func'] == 'function') {
                                        that[i].removeEventListener(func, funcList[func][j]['func'], false);
                                    }
                                }
                            }
                        }
                    }
                    return that;
                    break;
            }
        }
        return that;
    }
    return me.apply(null, [e]);
}
soulteary.callback = function(params) {
    if (!params.url || !params.id) {
        return false;
    }
    params.id += '__cb';
    var dom = document.getElementById(params.id);
    if (dom) {
        dom.parentNode.removeChild(dom);
    }
    var dom = document.createElement('script');
    dom.type = 'text/javascript';
    dom.src = params.url;
    dom.id = params.id;
    var body = document.getElementsByTagName('body')[0];
    body.appendChild(dom);
    return true;
}

soulteary.ajax = function(params) {
    if (!params || !params.url ) {
        return false;
    }

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
            params.callback = params.callback || function(data) {
                data = JSON.parse(data);
                console.log(data);
                return true;
            }
            params.callback(xhr.responseText);
        }
        return false;
    }
    var r = new Date();
    r = r.getMilliseconds();
    r *= r;
    params.url += '?c=' + r;
    params.mode = params.mode || 'GET';
    xhr.open(params.mode, params.url, true);
    xhr.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
    xhr.send();

    return true;
}


var $ = soulteary;