文章比较浅显,高手勿入。
常常写小页面自己玩的同学有没有发现,偶尔需要的一些功能,如果引用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 target = document.getElementById(e) || target; var me = new Object(); var funcList = {}; 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); }; 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; } 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; } 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; } me.text = function(str) { console.log(target); if('textarea' == target.type) { target.value = str; } else { target.textContent = str; } return this; } return me; } var $ = soulteary;
作者: 苏洋
当黑夜张开翅膀,月光静悄悄在流淌。风带不走的忧伤,孤单的梦想在远航。关注互联网,热爱计算机,我是苏洋。
VIA: http://soulteary.com/2013/01/25/write-js-lib.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述