贫下中农版jQuery
之前写过一篇JavaScript命名空间的文章,写完后一对比对jQuery的简单使用很是惊羡,看了看人家源码,用的原理很类似啊,改进一下之前的版本,做个简易版的jQuery
之前的代码
(function () { var _NS = function () { } _NS.prototype.select = function (selector,context) { var context = context || document; return context.querySelectorAll(selector); } _NS.prototype.isArrayLike=function(obj){ if(obj instanceof Array){ return true; } var length=obj.length; if ( obj.nodeType === 1 && length ) { return true; } return false; } _NS.prototype.html = function (obj,value) { var isArray=this.isArrayLike(obj), i=0; if (typeof value == 'string') { if (!isArray) { obj.innerHTML = value; } else { var length = obj.length; while (i < length) { obj[i].innerHTML = value; i += 1; } } } else { if (!isArray) { return obj.innerHTML; } else { return obj[0].innerHTML; } } } window.NS = new _NS(); })();
这样的写法只是对各种自定义方法的隔离,只是能用而已,不支持日趋流行的链式调用,使用jQuery的时候可以很方便的写出$(selector).xxx().xxxx().xxxxxx() 这样格式的代码,既简洁易读效率又高,而上面的写法只能调用一个个孤零零函数,没有对象整体性可言。
上面的select方法返回值为查询的对象(IE低版本浏览器不支持),很多时候获取一个对象后我们希望使用库函数对其直接进行操作,比如我们希望把页面上所有div的innerHTML设为test,并隐藏这些div,如果用jQuery会这么写
$(div).html('test').css(‘display’,’none’);
上面的代码虽然没实现css方法,但是如果有的话得这么写
var divs=NS.select('div'); divs.html('test'); divs.css('display','none');
为什么jQuery很方便
jQuery好用有几个原因:
1. $本身是个function对象,包含一些“静态方法”(不用实例化就可以用的方法),比如$.ajax、$.animation,可以这样$.xxx()直接使用jQuery的一些库函数
2. 因为$本身是一个函数,可以被调用。但是$(selector) 返回结果并不是搜索的结果集,而是一个jQuery实例,结果集被封装在jQuery对象内,这样可以使用一些jQUery的实例方法(也就是定义在prototype内的方法等),例如$(‘div’).html(‘test’), 这样由于$(‘div’)返回的是jQuery实例,所以可以调用实例方法html()。
3. jQuery对象大部分实例方法尽量返回jQuery对象,即调用者本身,这样可以支持链式调用,比如$(‘div’).html(‘test’).css(‘display’,’none’) , $(‘div’)返回jQuery对象,里面包含结果集,调用实例方法html(‘test’) 同样返回jQuery对象,调用 css(‘display’,’none’) 同样也返回jQuery对象,可以这样一直调用下去。
构造函数的一些知识
想要做到上面几点除了prototype等基本知识,还需要了解一些关于JavaScript构造函数的知识。
1.什么样的函数是构造函数
在JavaScript的世界里构造函数并不神秘,也不特殊,任何函数通过new 操作符调用都可以变为构造函数,不使用new 操作符就不是构造函数,而是直接按普通函数调用。
2.构造函数返回什么样的结果
构造函数的返回值分为两种情况,当function没有return语句或者return回一个基本类型(bool,int,string,undefined,null)的时候,返回new 创建的一个匿名对象,该对象即为函数实例;如果function体内return一个引用类型对象(Array,Function,Object等)时,该对象会覆盖new创建的匿名对象作为返回值。
写个小例子验证一下
function A(){ return true; } var a=new A(); console.log(a instanceof A); //true function B(){ return new Array(); } var b=new B(); console.log(b instanceof Array); //true
做个贫下中农版的jQuery
针对上面讲的jQuery的几点好处,尝试写一个版本,为了更像jQuery一些,把返回函数名字也改为$,先写一个框架
version 0.1
(function(){ var $=function(selector,context){ }; $.ajax=function(configs){ //静态方法 //TODO } $.prototype.html=function(value){ //实例方法 //TODO } window.$=$; })();
关于静态方法和实例方法的部分很好实现,就写成类似version 0.1的样子就行,在这个版本中$确实是个函数了,但是怎么让$(selector)执行返回$的实例,同时实例内又包含搜索结果。尝试写下一个版本
version0.2
(function(){ var $=function(selector,context){ var context = context || document; var nodeList = context.querySelectorAll(selector); var $=new $(); $.elements=nodeList; return $; }; $.ajax=function(configs){ //静态方法 //TODO } $.prototype.html=function(value){ //实例方法 //TODO } window.$=$; })();
这个看起来可以,$是个函数,有一些静态方法,$(selector)返回$实例,包含搜索结果集,但是这种方式有语法上的错误,代码中试图在$function体内new自己,在执行到new的时候JavaScript还不认识$,失败!
可以尝试换一个思路 version 0.3
(function(){ function f(selector,context){ return ?; } var $=(function(){ return f; })(); window.$=$; })();
这样$同样也是个函数,但是怎么才能让其执行结果返回本身的实例呢,也就是f到底应该怎样返回$的实例呢,说了这么多遍实例终于想起除了直接new一个对象可以得到其实例,还有一个地方可以得到其实例,在prototype中定义的函数可以访问this对象并返回。这就要求在f的prototype函数内返回this,不断的改啊调啊终于成了这样
version 0.4
(function(){ var $=(function(){ function f(selector,context){ return f.prototype.init(selector,context); } f.prototype.init=function(selector,context){ var context = context || document; var nodeList = context.querySelectorAll(selector); this.length = nodeList.length; this.elements=new Array(); for (var i = 0; i < this.length; i++) { this.elements[i] = nodeList[i]; } return this; } return f; })(); window.$=$; })();
填上刚才自定义的函数
version 1.0
(function(){ var $=(function(){ function f(selector,context){ return f.prototype.init(selector,context); } f.ajax=function(configs){ //TODO } f.prototype.init=function(selector,context){ var context = context || document; var nodeList = context.querySelectorAll(selector); this.length = nodeList.length; this.elements=new Array(); for (var i = 0; i < this.length; i++) { this.elements[i] = nodeList[i]; } , return this; } f.prototype.html=function(value){ //TODO } return f; })(); window.$=$; })();
这样终于所有要求都实现了,在内部匿名函数中定义function f,最后返回赋值给$,这样$是个函数,在执行的时候层层调用,最后调用到f.prototype.init,并返回其返回对象(好绕口),在init中把搜索结果放到this的属性中,最后返回this,然后f在把this返回,这样$(selector)的结果是$对象实例,而且包含搜索结果。
jQuery源码结构
上面的结果已经很让人满意了,仔细读了读jQuery源码,看看jQuery结构
(function( window, undefined ) { var jQuery = (function() { // 构建jQuery对象 var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); } // jQuery对象原型 jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { // selector有以下7种分支情况: // DOM元素 // body(优化) // 字符串:HTML标签、HTML字符串、#id、选择器表达式 // 函数(作为ready回调函数) // 最后返回伪数组 } }; //把jQuery的prototype赋值给init方法的prototype jQuery.fn.init.prototype = jQuery.fn; // 合并内容到第一个参数中,后续大部分功能都通过该函数扩展 // 通过jQuery.fn.extend扩展的函数,大部分都会调用通过jQuery.extend扩展的同名函数 jQuery.extend = jQuery.fn.extend = function() {}; // 在jQuery上扩展静态方法 jQuery.extend({ // ready bindReady // isPlainObject isEmptyObject // parseJSON parseXML // globalEval // each makeArray inArray merge grep map // proxy // access // uaMatch // sub // browser }); return jQuery; })(); window.jQuery = window.$ = jQuery; })(window);
总体上是一致的,但是jQuery的结构要科学很多
1.将window对象传入匿名函数,使匿名函数内部可以直接访问,防止匿名函数内部使用window对象的时候需要层层查找作用域链,最后才能找到window
2. 没有一棍子打死,完全使用$,当出现$命名冲突的时候可以使用jQuery代替
3. 定义jQuery.fn=jQuery.prototype,代码写起来方便了很多,也有利于压缩
4. 没有使用elements属性,而是利用数组特性封装搜索结果集,在使用的时候更容易想到
5. 定义each函数用于遍历结果集
6. 提供extend函数用于向对象内部添加属性
穷人版jQuery Version2.0
看了大师的写法终于可以脱离贫下中农了
(function () { var $ = (function () { var $ = function (selector, context) { return new $.prototype.init(selector, context); } $.prototype.init = function (selector, context) { var context = context || document; var nodeList = context.querySelectorAll(selector); this.length = nodeList.length; for (var i = 0; i < this.length; i++) { this[i] = nodeList[i]; } return this; } $.prototype.each = function (callback, args) { var length = this.length, i = 0; if (args) { while (i < length) { callback.call(this[i], args); i += 1; } } else { while (i < length) { callback.call(this[i]); i += 1; } } return this; } $.prototype.html = function (value) { if (typeof value == 'string') { this.each(function () { this.innerHTML = value; }); return this; } else { return this[0].innerHTML; } } $.prototype.init.prototype = $.prototype; return $; })(); window.$ = $; })();
最后
本文就是在总结我试图实现jQuery的过程,思绪结构有些混乱,希望不要误导读者。在读了很多次jQuery源码,加上网上很多博客解析才一步步把jQuery看清,jQuery的设计非常巧妙,常人很难一次想到实现方式,而且非常有前瞻性,任何时候都在使用jQuery命名空间或jQuery实例,防止了与未来JavaScript原生API的冲突,多研究研究受益匪浅。
前端EASYUI的简化调用
easyui近期一直都比较流行,虽然它在效果上被extjs爆了,它的使用难度低,在IE6下表现不错,的确受到了广泛企业程序员的好评。
但是他的API说明还是比较简陋的,刚上手可能还需要摸索一下,为什么这样做不会来?
我做了一些封装
尽量地封装,隐藏它的API,保持简单调用。
不要担心看不懂,最后还提供Demo下载。
工作开始
Tabs
先贴一张简陋的图,与官方的Demo差不多,但是在Tabs中只加入一个模仿VS的右键菜单,并且设置了选项卡最大个数,打开选项卡时超出最大个数会关闭一个。
另外做了写控制,不会重复打开选项卡。
总体上定义一个jeasyui的类,可以看到他包含Tabs,Messager,Redirect这几个部分的功能。
var jeasyui = { Tabs: {} , //选项卡 Messager: {}, //消息框 Redirect:{} //重定向 };
先来看看Tabs
/* 选项卡 id easyui标签的ID maxlength 设置选项卡最大个数 */ jeasyui.Tabs = function (id, maxlength) { this.id = id; this.maxlength = maxlength; this.currTabCount = 1; //自动关闭选项卡函数 this.autoCloseTab = function () { $('#' + this.id).tabs('close', 1); }; //关闭指定选项卡函数 this.CloseTab = function (title) { $('#' + this.id).tabs('close', title); }; //关闭除选中外所有选项卡函数 this.CloseAllTabExceptThis = function (title) { var alltabs = $('#' + this.id).tabs('tabs'); var currtab = $('#' + this.id).tabs("getTab", title); var titlelist = new Array(); var listcount = 0; for (var i = 0; i < alltabs.length; i++) { if (alltabs[i] != currtab && alltabs[i].panel('options').title != "首页") { titlelist[listcount] = alltabs[i].panel('options').title; listcount++; } } for (var j = 0; j < listcount; j++) { $('#' + this.id).tabs('close', titlelist[j]); } }; }; //添加一个选项卡 jeasyui.Tabs.prototype.addTab = function (titleName, url) { if (!this.exists(titleName)) { //var iframe = $('<iframe style="100%;height:100%;border:0" />'); $('#' + this.id).tabs('add', { title: titleName, //content: iframe, href:url, closable: true, cache: true, fit: true }); //iframe.attr('src', url); } else { this.selectTab(titleName); } }; //选中指定选项卡(参数titleName:选项卡标题名) jeasyui.Tabs.prototype.selectTab = function (titleName) { $('#'+this.id).tabs('select', titleName); }; //获取当前选项卡 jeasyui.Tabs.prototype.getSelected = function () { return $('#' + this.id).tabs('getSelected'); }; //刷新选项卡(参数tab:选项卡) jeasyui.Tabs.prototype.refresh = function (tab) { tab.panel('refresh', tab.panel('options').href); }; //验证选项卡是否存在(参数titleName:选项卡标题名) jeasyui.Tabs.prototype.exists = function (titleName) { var tab = $('#' + this.id).tabs('exists', titleName); return tab; };
使用起来也比较简单,右键菜单的事件如下。
var jtab = new jeasyui.Tabs('tabs', 10); function AddTab(obj) { jtab.addTab(obj.title, obj.url); } function CloseCurrTab() { var title = $('#hidCurrTab').val(); jtab.CloseTab(title); } function ReloadTab() { var selectedTab = jtab.getSelected(); jtab.refresh(selectedTab); } function CloseAllTabExceptThis() { var title = $('#hidCurrTab').val(); jtab.CloseAllTabExceptThis(title); }
构建选项卡,这里注意下tabs的几个事件的绑定。
$(document).ready(function () { //构建手风琴 var aaOptions = { fit: true, border: false }; $('#aa').accordion(aaOptions); //构建选项卡 var tabsOptions = { fit: true, tools: '#tab-tools', onContextMenu: function (e, title) { e.preventDefault(); if (title == "首页") { $('#mm-closeone').attr('style', 'display:none'); } else { $('#mm-closeone').attr('style', ''); } $('#mm').menu('show', { left: e.pageX, top: e.pageY }) $('#hidCurrTab').val(title); }, onAdd: function (title) { jtab.currTabCount++; if (jtab.currTabCount > jtab.maxlength) { jtab.autoCloseTab(); } }, onClose: function (title) { jtab.currTabCount--; } }; $('#tabs').tabs(tabsOptions); });
消息框,这里可以衍生出更丰富的功能,比如确认后执行一个函数,只要稍稍改动即可,比较简单的。
jeasyui.Messager = {}; //弹出提示信息 jeasyui.Messager.Alert = function (title,msg,type) { $.messager.alert(title, msg, type); }; //弹出提示确认后重定向 jeasyui.Messager.ConfirmAndRedirect = function (title, msg, url) { $.messager.confirm(title, msg, function (r) { if (r) { location.href = url; } }); }; //弹出提示确认后关闭窗口 jeasyui.Messager.ConfirmAndClose = function (title, msg) { $.messager.confirm(title, msg, function (r) { if (r) { window.close(); parent.location.href = parent.location.href; } }); }; //弹出提示信息后父窗体重定向 jeasyui.Messager.MRedirect = function (title, msg, url) { $.messager.alert(title, msg, 'info', function () { location.href = url; }); };
这次报告就到这里,感谢阅读。
附件下载:easyuiframe.rar