jQuery 1.3更新的内容

前两天jQuery更新出了jQuery 1.3的beta版,今天有空看了看到底更新了点什么
看了下代码,选择器用上了新的引擎Sizzle咯。(Sizzle为何?见下面简介)
得益于新的引擎,选择器支持:not(div,p)这种了多重的not了
增加了一个closest方法,用来找最近的一个匹配选择器的父元素。
LiveQuery似乎也集成了,用$(elem).live(type,fn)就能注册事件了,新添加的元素也同样享受这个服务~无须重新帮定了。不想要的时候让他die()就行了。
更多的还没来得及看,不过看到了JavaEye上的文章,把这次的更新都说全了。可以移步去看看。Cloudream在两个月前就对jQuery 1.3的alpha有所阐述了,建议也可以看看。
这次刚刚升级的大版本Bug一般都会很多,所以请不要急着用于产品中,建议等到明年3、4月份的时候有没有新的修正版出来的时候再升级到1.3版。

补充内容:
Sizzle是jQuery作者John Resig新写的DOM选择器引擎。其速度号称业界第一……
不过简单看了一下,对于支持document.querySelectorAll的浏览器就主要用这个了(比如IE8),支持getElementsByClassName的也会优先用这个(Firefox等)。
也还有一些其他优化。
如果只想要用一个出色的选择器工具而不想要引入jQuery,可以考虑直接用Sizzle,超好用。压缩后才3、4K。完全可以基于此开发一套自己的脚本库。

Help Test jQuery 1.3 Beta 1

jQuery: » Help Test jQuery 1.3 Beta 1.

没啥说的,jQuery 1.3的beta 1发布了,

有能力的各位都测试一下,有问题的汇报一下

把引用脚本的地址改成

http://code.jquery.com/jquery-1.3b1.js

就行。不要用min或者pack版,不好定位问题

主要改了选择器引擎,DOM操作,offset()方法,事件命名空间和事件触发等部分的逻辑。尤其是事件触发的部分,现在会冒泡了,可能导致问题。

具体jQuery 1.3会预计在1月14日发布。

发现问题可以去jQuery bug tracker 提交问题。别忘了附上精简过的测试代码就行。

[推荐]jQuery 源码分析

不得不承认,我看到这个帖子晚了好几个月。
但幸运的是,我在年末的时候看到了。Better late than never.
就当作圣诞礼物送给我自己吧。
也送给各位:
http://jljlpch.javaeye.com/blog/234218
非常全面得分析了jQuery 1.2.6
100多页的pdf文档,引用fins的话是,这是今年JavaEye上精华中的精华了。
没看过的请务必去看一下。

json2select

这是我之前为了ThinkSNS写的一个小工具。通过json生成无限联动的select。至少要求jQuery 1.2.3 以上,因为我用了data方法。
查看DEMO 源文件 min版

  1. //构建如下json,t为列表框的text,v为列表框的value,s表示子一级对象
  2. var json=[
  3.     {
  4.         t:"欧洲某地",
  5.         v:"欧洲"
  6.     },
  7.     {
  8.         t:"中国某地",
  9.         v:"中国",
  10.         s:[
  11.             {
  12.                 t:"上海",
  13.                 v:"上海"
  14.             },
  15.             {
  16.                 t:"云南某地",
  17.                 v:"云南",
  18.                 s:[
  19.                     {
  20.                         t:"大理",
  21.                         v:"大理"
  22.                     }
  23.                 ]
  24.             }
  25.         ]
  26.     },
  27.     {
  28.         t:"日本某地",
  29.         v:"日本",
  30.         s:[
  31.             {
  32.                 t:"东京",
  33.                 v:"东京"
  34.             },
  35.             {
  36.                 t:"北海道",
  37.                 v:"北海道",
  38.                 s:[
  39.                     {
  40.                         t:"北海道的某个地方",
  41.                         v:"北海道的某个地方"
  42.                     }
  43.                 ]
  44.             }
  45.         ]
  46.     }
  47. ];
  48. //调用即可
  49. $("#selectt").json2select(json,["中国","云南","大理"]);

另外,如果用1.2.1的也想用的话,可以手动提取1.2.6里的data方法,或者简单用下面的模拟一下即可

  1. (function($) {
  2.     $.fn.data=function(key,value) {
  3.         return $.data(this[0],key,value);
  4.     }
  5. })(jQuery);

update:
附: 全国城市数据
并且更新到了 1.0.1版。以前下载的同学可以重新下载一份。

FireUnit:基于Firebug的JavaScript单元测试扩展

John Resig最近在他的博客中发表文章John Resig – FireUnit: JavaScript Unit Testing Extension.发布了他与Jan Odvarko合作开发的一款基于Firebug的扩展FireUnit。(Firebug真是越来越强大了……)
简单说来,FireUnit给Firebug增加了一个标签面板,并提供了一些简单的JavaScript API来记录和查看测试。
举例来说:

  1. // Simple true-like/false-like testing
  2. fireunit.ok( true, "I'm going to pass!" );
  3. fireunit.ok( false, "I'm going to fail!" );
  4.  
  5. // Compare two strings - shows a diff of the
  6. // results if they're different
  7. fireunit.compare(
  8.   "The lazy fox jumped over the log.",
  9.   "The lazy brown fox jumped the log.",
  10.   "Are these two strings the same?"
  11. );
  12.  
  13. // Compare a string using a regular expression
  14. fireunit.reCompare(
  15.   /The .* fox jumped the log./,
  16.   "The lazy brown fox jumped the log.",
  17.   "Compare a string using a RegExp."
  18. );
  19.  
  20. // Display the total results
  21. fireunit.testDone();

就可以在Firebug中的Test标签面板中看到下图:

fireunit测试

FireUnit还可以模拟触发原生浏览器事件:

  1. // You can also simulate browser events
  2. var input = document.getElementsByTagName("input")[0];
  3. fireunit.mouseDown( input );
  4. fireunit.click( input );
  5. fireunit.focus( input );
  6. fireunit.key( input, "a" );

此外,可以批量测试文件(每个文件都含有一些独立测试)

  1. // Or run multiple pages of tests:
  2. fireunit.runTests("test2.html", "test3.html");
  3.  
  4. // Place at the end of every test file in order to continue
  5. fireunit.testDone();

fireunit批量测试

推荐把测试代码包含在下面的if语句内

  1. if ( typeof fireunit === "object" ) {
  2.         //Your code for test
  3. }

如果感兴趣,可以访问Fireunit.org下载并测试(这个网站内就包含了测试代码,安装后即可看到第一张图的内容)
可以在Github上获取源码,得用git而不是svn
Jan也写了一篇文章,可以看看。

jQuery中不为人知的秘密之三——判断元素上是否绑定了事件

好久没写博客了,抱歉抱歉,最近半年很忙很忙。
正好群里 Q.Lee.lulu 问起怎样判断有没有绑定一个事件。
我研究了一下之后发现,jQuery都将事件缓存起来了,其实也是为了防止内存溢出以及页面unload的时候的速度,也包括多函数触发,方便管理等诸多好处,具体可以参考此文
jQuery会在window.unload的时候卸载所有绑定过的事件,释放内存的。

OK,言归正传。判断元素上是否绑定过事件用如下语句

  1. jQuery.data(elem,"events")[type]  //老版本也能用
  2. $(elem).data("events")[type]  //1.2.3以后才能用

返回值:
一个Object,可以用for in来遍历。或者undefined。
参数:
elem是一个DOM对象,type是事件类型。
举例:
判断id为foo的元素上是否绑定了click事件

  1. if( $("#foo").data("events")["click"] ){
  2.     //your code
  3. }

“另类”的阻止冒泡

论坛里有人问

HTML:
<div id=”b”>
<div>sssssssssssss</div>
<div>aaaaaaaaaaaa</div>
</div>

JQ;
$(‘div:contains(aaa)’).addClass(‘aa’);

如何才能让这个id=”b”的div不要被选择器匹配到呢?

底下讨论了老半天基本都是无解,只能回避这个问题,加入class之类的限定条件。

后来我仔细翻了翻API,发现还有filter这个方法可以用。

之后还不过瘾,写了个选择器插件,需要的同学可以拿去用。

直接上代码了,写的不好,有能力的帮忙优化一下,毕竟选择器函数属于运算密集型函数。
filter方法:

  1. $('div:contains(aaa)').filter(function() {
  2.       return $(this).children().length == 0;
  3. });

选择器插件:

  1. <div>
  2.         <div>shawphy</div>
  3.         <div>this is a demo -- shawphy</div>
  4.         <div>test</div>
  5.         <div>test</div>
  6. </div>
  1. /*
  2.         Shawphy:用于查找不含子元素的元素(可以包含文本,不同于:empty选择器)
  3. */
  4.         ;(function($) {
  5.                 $.extend($.expr[":"], {
  6.                         nochild : function( a ) {
  7.                                 return $(a).children().length == 0; //Shawphy:子元素数量为0的元素会被保留
  8.                         }
  9.                 });
  10.         })(jQuery);
  11.         //Shawphy:测试是否能查到所需要的东西
  12.         alert($('div:contains(shawphy):nochild').length)

jQuery中不为人知的秘密之二

又开始写jQuery的秘密了~
这里写的基本上都是没啥用的东西,大家随便看看好了,有用的都放在文档里了~

这回先说选择器,
两个特殊的选择器,没有在文档里列出来。有可能只是选择器正则匹配的失误,也可能是故意的,但我源码中看不出来。望各位看官指点。注意,下面两个特殊选择器,随时可能在以后版本中消失,慎用。本文涉及的是1.2.6,经测试1.2.3也可使用。

创建一个空jQuery对象:

  1. $(" ");   //切记,引号中间至少有_一个_空格

这段简易的代码即可创建一个空的jQuery对象。这就类似创建一个代码片段。但由于是空的,所以无法append进任何东西。但你可以用add将你要的东西添加进去。

获取document,有时候需要写一些关于document的事件,比如keypress之类的,或者其他的,就可以用

  1. $("");   //切记,引号中间_没有_空格

这一定程度上可以用于混淆代码,比如将$(document).ready()写成$(“”).ready(),不知道的人就看不懂了。当然,要看过本文的人就都知道了~~(笑~)

顺便一说,前端加密是没有可能的,混淆也是有限度的。有jQuery的操作是链式操作,没有中间变量,同时通常绑定函数也是匿名函数,所以对于jQuery代码的混淆更是没有太大用处。除非,不再使用匿名函数,每次都外部定义一个。同时不再使用链式操作,用next,parent之类操作的时候,统统再定义一个中间变量。这样你的jQuery代码已经没有jQuery风格了,对混淆会稍微有点点帮助。注意,以上所有混淆操作,只防君子不防小人!切记,前端开发不可能加密!所以一切都做好开源的准备!了解GPL、MIT之类的都是有必要的!

如果你要用中间变量,像前面说的为了混淆,有时候你可能需要用到之前的一个对象,就好像在链式写法中用end()的效果,那就可以用prevObject属性,end()就是返回这个属性。

  1. $("#id").prevObject;   //切记,最后没有括号,这个是属性不是方法

下面说的这个其实不是很秘密,但值得一提
attr是很伟大的方法,以下这些都是可以执行的,你就可以通过这些来进行一些简单的判断,比如这个元素的标签名啦之类的。

  1. $("body").attr("tagName");
  2. $("body").attr("nodeName");
  3. $("body").attr("nodeType");

还有个相关的,他返回的是布尔值true/false

  1. jQuery.nodeName( elem, nodeName )

第一个参数是一个DOM对象,不是jQuery对象,第二个就是节点名称(标签名)了,大小写无关。返回true/false
有人问,为什么我不直接使用elem.nodeName==nodeName呢,爱用写这么麻烦呢?
看一下jQuery的源码就知道了

  1. nodeName: function( elem, name ) {
  2.     return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
  3. },

明白了么?浏览器兼容性问题,某些浏览器中的nodeName是大写,有些是小写,够恶心吧?
兼容一下总是好的~~

jQuery中Ajax事件

参考资料地址:

http://docs.jquery.com/Ajax_Events

Ajax会触发很多事件。
有两种事件,一种是局部事件,一种是全局事件:

局部事件:通过$.ajax来调用并且分配。

  1. $.ajax({
  2.     beforeSend: function(){
  3.      // Handle the beforeSend event
  4.     },
  5.     complete: function(){
  6.      // Handle the complete event
  7.     }
  8.     // ...
  9. });

全局事件,可以用bind来绑定,用unbind来取消绑定。这个跟click/mousedown/keyup等事件类似。但他可以传递到每一个DOM元素上。

  1. $("#loading").bind("ajaxSend", function(){ //使用bind
  2.     $(this).show();
  3. }).ajaxComplete(function(){  //直接使用ajaxComplete
  4.     $(this).hide();
  5. });

当然,你某一个Ajax请求不希望产生全局的事件,则可以设置global:false

  1. $.ajax({
  2.    url: "test.html",
  3.    global: false,
  4.    // ...
  5.  });

事件的顺序如下:
ajaxStart 全局事件
开始新的Ajax请求,并且此时没有其他ajax请求正在进行。
beforeSend 局部事件
当一个Ajax请求开始时触发。如果需要,你可以在这里设置XHR对象。
ajaxSend 全局事件
请求开始前触发的全局事件
success 局部事件
请求成功时触发。即服务器没有返回错误,返回的数据也没有错误。
ajaxSuccess 全局事件
全局的请求成功
error 局部事件
仅当发生错误时触发。你无法同时执行success和error两个回调函数。
ajaxError 全局事件
全局的发生错误时触发
complete 局部事件
不管你请求成功还是失败,即便是同步请求,你都能在请求完成时触发这个事件。
ajaxComplete 全局事件
全局的请求完成时触发
ajaxStop 全局事件
当没有Ajax正在进行中的时候,触发。

局部事件回调的参数在文档中写的很清楚了,这里就不累述了。

全局事件中,除了ajaxStart和ajaxStop之外,其他的事件都有3个参数
event, XMLHttpRequest, ajaxOptions
第一个是事件,第二个是XHR对象,第三个参数最有用,是当时调用这个ajax的时候的参数。
对于ajaxError,还有第四个参数thrownError,只有当异常发生时才会被传递。
我们可以利用ajaxOptions来写一个全局的ajax事件。
比如

  1. $("#msg").beforeSend(function(e,xhr,o) {
  2.     $(this).html("正在请求"+o.url);
  3. }).ajaxSuccess(function(e,xhr,o) {
  4.     $(this).html(o.url+"请求成功");
  5. }).ajaxError(function(e,xhr,o) {
  6.     $(this).html(o.url+"请求失败");
  7. });

对于这个例子,
这样我们就可以很方便的全局地在某个地方显示当前的ajax状态。
当然,之前说了,第三个参数实际上是传递给ajax的参数。get/post/load/getScript/getJSON等方法本质上都是调用ajax方法的,所以ajaxOptions.url属性总是有效的。

还有更丰富的例子。
如果你用ajax调用,还可以传递自定义参数。下面的例子我就自定义了一个msg参数给了ajax调用

  1. //自定义参数msg
  2. $.ajax({url:"test1.html",type:"get",msg:"页面一"});
  3. $.ajax({url:"test2.html",type:"get",msg:"页面二"});
  4. $.ajax({url:"test3.html",type:"get",msg:"页面三"});
  5. $.ajax({url:"test4.html",type:"get",msg:"页面四"});
  6.  
  7. //这里就能获取到自定义参数msg。
  8. //这可以用来区别对待不同的ajax请求。
  9. $("#msg").beforeSend(function(e,xhr,o) {
  10.     $(this).html("正在请求"+o.msg);
  11. }).ajaxSuccess(function(e,xhr,o) {
  12.     $(this).html(o.msg+"请求成功");
  13. }).ajaxError(function(e,xhr,o) {
  14.     $(this).html(o.msg+"请求失败");
  15. });

最后对于load方法,还有话说。
其他的简易ajax方法,比如get,post,getJSON等,他们的回调函数都是设置了success回调。
而只有load设置的其实是complete回调。
所以,load里设置的回调函数的参数应该有2个。
XMLHttpRequest和textStatus
但实际上也并非如此。
load的回调有三个参数
XMLHttpRequest.responseText, textStatus, XMLHttpRequest
所以,你可以在load的回调里
通过textStatus==”success”或者textStatus==”error”来判断是否调用成功。
或者用XMLHttpRequest.status属性判断是200还是404或者其他的。

这点上,我认为比普通的get/post等方法更先进。如果要单数设置每get的error是不可能的。但是设置一个全局的ajaxError其实也是不错的选择。

页面加载完成后新建的元素事件绑定问题(下)

页面加载完成后新建的元素事件绑定问题(上)
=============================
2号解决方案——事件冒泡法
利用事件冒泡的原理,我们给这个按钮的祖先元素绑定事件处理函数。
然后通过event.target这个对象来判断,这个事件是不是我们要找的对象触发的。
通常可以利用一些DOM属性,比如event.target.className、event.target.tagName等之类的来判断。

  1. <td><button class="del">删除</button></td>
  1. jQuery(function($) {
  2.     //第四个表格的删除按钮事件绑定
  3.     $("#table4").click(function(e) {
  4.         if (e.target.className=="del") {
  5.             $(e.target).parents("tr").remove();
  6.         };
  7.     });
  8.     //第四个表格的添加按钮事件绑定
  9.     $("#add4").click(function(){
  10.         $("#table4>tbody").append('<tr><td>新增行</td><td><button class="del">删除</button></td></tr>')
  11.     });
  12. });

=============================
3号解决方案——复制事件法
上面几种方案可以说即便你没有用到jQuery库,你也能相对比较容易的实现。但这种方案相对依赖jQuery的程度更高。而且必须要求jQuery 1.2版以上。低版本jQuery需要插件。
上面两个方案都是对删除函数动了很多脑筋,换了多种触发、绑定的方式。这个方案不同,可以与平时纯静态的元素一样在domready的时候绑定。但在我们添加新行的时候我们改动一下,不再想上面那样拼接字符串来添加新行了。这回我们尝试使用复制DOM元素的方式。并且复制的时候连同绑定的事件一起复制,复制完之后再用find之类的修改内部的元素。
同时,就像这个例子,如果你会把所有元素都删除光,那template这个模板是必须的,如果不会删光,那就未必需要用template了。为了防止被误删,此处我把template设了隐藏。
我使用了jQuery中特有的clone(true)

  1. .template{display:none;}
  1. <tr class="template">
  2.             <td>这里是模板</td>
  3.             <td><button class="del">删除</button></td>
  4.         </tr>
  5.         <tr>
  6.             <td>这行原来就有</td>
  7.             <td><button class="del">删除</button></td>
  8.         </tr>
  9.         <tr>
  10.             <td>这行原来就有</td>
  11.             <td><button class="del">删除</button></td>
  12.         </tr>
  1. jQuery(function($) {
  2.     //第五个表格的删除按钮事件绑定
  3.     $("#table5 .del").click(function() {
  4.         $(this).parents("tr").remove();
  5.     });
  6.     //第五个表格的添加按钮事件绑定
  7.     $("#add5").click(function(){
  8.         $("#table5>tbody>tr:eq(0)")
  9.             //连同事件一起复制
  10.             .clone(true)
  11.             //去除模板标记
  12.             .removeClass("template")
  13.             //修改内部元素
  14.             .find("td:eq(0)")
  15.                 .text("新增行")
  16.                 .end()
  17.             //插入表格
  18.             .appendTo($("#table5>tbody"))
  19.     });
  20. });

=============================
总评:
上面4种方案,各有优劣。
0号方案,结构与行为完全没有分离,而且污染全局命名空间。最不推荐。所以我都不把它当作一个方案来看。但对于js初学者,可以用来项目救急。
1号方案,中规中矩,没啥好也没啥不好
2号方案,这种方法充分的发挥了js事件冒泡的优势。而且效率最高。但同时由于这种方案无视了jQuery强大的选择器,所以如果涉及的元素属性要求过多就会比较麻烦了。你会徘徊在众多if的条件的是非关系之中。后来我想起来,可以用jQuery中的$(event.target).is(selector)来作为条件。这样可以极大提升开发效率,但略微降低执行效率。
3号方案,这是我认为最能体现结构与行为分离的思想的一种方案。但缺点也很明显,对于jQuery依赖性过于高了,要不就自己写一个复制连同事件一起复制的函数,但这也显然对于初学者来说异常困难。但从未来的趋势的角度来看,还是很推荐使用这种方案的。

具体选用哪一个方案,没有定数。具体看你的项目以及你js还有结构与行为分离的思想的掌握程度。最适合的才是最好的。

=============================
附加:
把3号方案改造成完美的结构行为分离的样式。
首先,带有template的是模板元素。他是一切复制的源泉,为了防止被误删,所以设为不可见。如果不会删除光,那么这个模板元素也是可选的。因为你可以复制任何一个已经存在的用于循环元素。
其次,给每个重复的元素加上一个repeat,方便用于删除按钮找到这一级元素。这个是可选的,有时候并不需要。
最后是给每一个要修改的元素加上一个类,便于用find找到。比如我这里就家了content类,新增加的可以修改里边的值。
这样一个完美的结构与行为分离的案例就完成了。

  1. <table id="table6">
  2.     <tbody id="tbody6">
  3.         <tr class="template repeat">
  4.             <td class="content">这里是模板</td>
  5.             <td><button class="del">删除</button></td>
  6.         </tr>
  7.         <tr class="repeat">
  8.             <td class="content">这行原来就有</td>
  9.             <td><button class="del">删除</button></td>
  10.         </tr>
  11.         <tr class="repeat">
  12.             <td class="content">这行原来就有</td>
  13.             <td><button class="del">删除</button></td>
  14.         </tr>
  15.     </tbody>
  16.     <tfoot>
  17.         <tr>
  18.             <td>&nbsp;</td>
  19.             <td><button id="add6">添加</button></td>
  20.         </tr>
  21.     </tfoot>
  22. </table>
  1. jQuery(function($) {
  2.     //第六个表格的删除按钮事件绑定
  3.     $("#tbody6 .del").click(function() {
  4.         $(this).parents(".repeat").remove();
  5.     });
  6.     //第六个表格的添加按钮事件绑定
  7.     $("#add6").click(function(){
  8.         $("#tbody6>.template")
  9.             //连同事件一起复制
  10.             .clone(true)
  11.             //去除模板标记
  12.             .removeClass("template")
  13.             //修改内部元素
  14.             .find(".content")
  15.                 .text("新增行")
  16.                 .end()
  17.             //插入表格
  18.             .appendTo($("#tbody6"))
  19.     });
  20. });

同样,这段js也适用于如下的html结构

  1. <ul id="tbody6">
  2.     <li class="template repeat">
  3.         <span class="content">这里是模板</span>
  4.         <span><button class="del">删除</button></span>
  5.     </li>
  6.     <li class="repeat">
  7.         <span class="content">这行原来就有</span>
  8.         <span><button class="del">删除</button></span>
  9.     </li>
  10.     <li class="repeat">
  11.         <span class="content">这行原来就有</span>
  12.         <span><button class="del">删除</button></span>
  13.     </li>
  14. </ul>