文章比较浅显,高手勿入。
感谢**@樱花坡道_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;