【转载】jQuery结构分析

jQuery结构分析
     听别人介绍jQuery是一个很优秀的Javascript库,正好最近需要用js完成很多效果,于是乘机学习一下^_^

1、jQuery的DOM访问的实现
 要清楚了解一个库的实现,最好是清楚它的使用和工作方式,现在先看看jQuery的使用
 1.1、jQuery的设计思想
  jQuyer的基本思想就是把jQuery对象转变为"类数组"对象,并把对应的DOM元素以子对象的形式保存在jQuery对象中,然后以历遍的方式将操作作用在各个DOM对象中。也就是说jQuery是面向集合操作的。另外,jQuery最精彩的地方就是兼容了多种的DOM元素选择方式。
   
 1.2、jQuery的功能模块
  现在先来看看jQuery的DOM操作方式,Ajax部分后面再看^_^。
   2.1、jQuery支持的DOM元素提取方式
      2.1.1、CSS的路径选择方式:

jQuery结构分析 - happyiww.popo - Happy I.W.W
          
         2.1.2、XPath的路径选择方式

jQuery结构分析 - happyiww.popo - Happy I.W.W        
         2.1.3、jQuery定义的路径选择方式

jQuery结构分析 - happyiww.popo - Happy I.W.W
 1.3、jQuery的DOM访问方式的实现
       1.3.1、jQuery得名空间
            1、对于全局来说jQuery是一个函数,也是一个jQuery对象得构造函数
            2、jQuery.fn = jQuery.prototy = {}
               这里描述得jQuery对象的原型,也就是说修改jQuery.fn将会影响jQuery对象的属性,这个在开发jQuery插

        件的时候使用jQuery.extend = jQuery.fn.extend = function(...){...},在这个函数中需要注意的是如果只用一个

        参数调用,那么新的"属性"将会合并到"this"对象上,对于jQuery.extend,this指向的是jQuery函数本身,而对

        于jQuery.fn.extend指向的是jQuery对象的原型定义集合。
               
       1.3.2、jQuery的处理
       现在我们来跟踪一下几个常用的jQuery表达式的处理流程,从而搞清楚jQuery对于DOM的访问方式:
       1、最简单的访问方式
       $('/div')
         |                    |--->寻路的上下文环境
       jQuery('/div', null)
         |           |-->DOM寻路目标
      检查$是不是在全局环境中调用,这是通过检查this变量与window变量来达到的
      if(window == this) --> return new jQuery('/div', null) 这样做的目的主要是划分名空间
                                                  |
         |--------------------------------|
    由于查找的对象并不是完整的html标签('<div>'),所以,最后会调用
  |---new jQuery( null ).find( '/div' );
  |         |
  |     a = document
  |     this.setArray([document])这个函数就是jQuery的DOM算法的关键处理流程
  |         |-->this.length = 0;
  |     [].push.apply( this, [document] );-->这里使用js函数对象的apply函数将jQuery对象转换为
  |     类Array对象。
  |
返回了一个jQuery对象,它的wrap元素包括document,注意,这里的document并没有封装为jQuery对象
    jQuery.find('/div')
            |                                                             |------------------------------------|
        this.pushStack(jQuery.map(this, function(a){return jQuery.find(t='/div', a)}),
                                |                                                                     |-------------------------------------|
               '/div'          |这里调用的是jQuery的函数,而不是新的对象的成员函数                            |
               )               |                                                                                                                    |
                          在map函数中会对this封装的每个子对象调用函数,并作为第一个参数调用函数 |
                          而把这个对象的下标作为第二个参数                                                                  |
                               |                                                                                                                    |
                          var = fn(element, index)                                         |----------------------------------|
    |--------------------------|                                                                  |
将返回值保存为数组并从中清除相同的成员           从上下文中提取需要查找的对象jQuery.find(taret='/div', context)
map返回的是一个数组,每个成员可以是任何对象,                       |
包括自定义对象和jQuery对象                      find函数是根据string来查找对象的,如果查找目标不是tring的话,那么
                                                                   直接返回[target]          |
             |-----------------------------------------------------------------------|
             |
       现在查找的路径是'/div',我们继续跟踪(跳到查询tag的逻辑分支)
       t = t.substring(1, t.length)-->t = 'div'
             |
       jQuery.each( ret=[document], function(){
          |                                                                      //m = [路径字符串, "#", tag_name ]       
          |                                                                       var tag = m[1] != "" || m[0] == "" ? "*" : m[2];
   each函数通过apply将自定义函数作用到,第一     
   个参数的每个子对象(对于类数组对象)或者是        // Handle IE7 being really dumb about <object>s
   目标对象的每个属性上(没有length属性的对象)     if ( jQuery.nodeName(this, "object") && tag == "*" )
    |                                                                                tag = "param";
   由于使用了apply的形式,所以在函数中是可以
   通过this这个内建变量来访问调用的对象的,事     jQuery.merge( r, m[1] != "" && ret.length != 1 
   实上即使调用对像本身并没有这个函数,也是                   |         ? jQuery.getAll( this, [], m[1], m[2], rec ) 
   可以通过这种方式调用的。                                               |         : this.getElementsByTagName( tag )-->这里已

    |                                                                                       |           经可以得到我们想要的所有div对象
    |                                                                               );});  |
删除查询路径中的已处理的信息                         合并两个类数组对象,并从中删除相同的对象
   t = t.replace( re2, "" );                                     var r = [].slice.call( first, 0 );-->复制对象,这里是做了一个"快照"
    |                                                                                       |
   过滤剩下的路径信息                                       if ( jQuery.inArray( second[i], r ) == -1 )-->first.push( second[i] );
   var val = jQuery.filter(t,r);
   ret = r = val.r;
   t = jQuery.trim(val.t);
   
   2、完整的DOM寻路处理流程
      了解了最简单的处理流程后,现在我们来看看jQuery中完整的方式
      首先是调用jQuery函数来生成jQuery对象(jQuery函数和jQuery对象是有区别的,最明显的就是this变量的值,而且对于"对象"而言,它是可以通过prototype属性来定制它的原型,而对于jQuery函数,prototype只是一个"普通"的属性)
      $(filter_path, context)
             |
      如果这个调用是以函数方式调用的,那么创建新的jQuery对象new jQuery(f_p, context)
             |
      检查filter_path是否已经指定,如果没有,就赋值为document
      如果f_a是一个函数(这是为$(function)的形式创建的快捷方式),那么就以document为f_a创建jQuery对象,并注册相应的函数
      new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( a );
            |
   最后是处理f_a为string的情况,当f_a是以html标签的形式提供时,提取html的标签
   m = /^[^<]*(<(.|\s)+>)[^>]*$/.exec(f_a)并规范化f_a, f_a = jQuery.clean( [ m[1] ] )-->这里调用的是函数jQuery.clean
      如果f_a以css或者是xpath的寻路字符串方式提供的话,就以context为上下文查找f_a, 
  |---new jQuery( context ).find( f_a )-->这种最常用的情况,下面我们详细的看看它所做的工作
  |         |
  |   不管是哪一种情况,最后调用的是将查询结果以数组的形式返回(也就是将jQuery对象转变为类数组)
  |   return this.setArray( a.constructor == Array && a ||
  | (a.jquery || a.length && a != window && !a.nodeType && a[0] != undefined && a[0].nodeType) && jQuery.makeArray( a ) ||[ a ] );
 |           |-->这个函数通过[].push.apply函数调用将jQuery对象转变为一个类数组对象,也就是可以通过[]来访问它所

 |                 封装的html元素
 |
 对于最常用的情况是只提供f_a,而context=null,这时jQuery(null)等价为
 new jQuery(document).find(f_a)
                       |
      this.pushStack( jQuery.map( this, function(a){return jQuery.find(t,a);}), t );
      事实上pushStack函数只是接受一个参数,所以t作为pushStack的第二个参数其实是无效的.pushStack函数的作用是首先用传入的第一个参数生成一个新的var ret = jQuery(arguments[0])对象,并将this挂接到ret.prevObject = this, 同时将ret返回                                                                       |
         |                                                                                         ||
         |         这里arguments[0]是jQuery.map的返回值,jQuery.map将第一个参数的每个子元素和对应的下标(对

         |    于类数组对象而言,就是它所封装的子元素)作为参数调用以map的第二个参数形式传入的处理函数,并

         |    过滤返回值中的 重复元素,也就是说jQuery.map的返回值是一个真正的数组
         |                  |
   在这里map的第二个参数是function(a) {return jQuery.find(t,a)}--->这里a是作为find的context参数传入的,而a则是map传入的子元素
   我们可以看到,jQuery的DOM寻路算法都集中在jQuery.find函数中实现,现在我们来看看jQuery.find的实现(这里是jQuery函数的上挂结的函数,而不是jQuery对象的原型函数)
   jQuery.find(target, context)
          |
    检查target是否为字符串,对于非字符串,那么简单地返回[target]
          |
    检查context,如果context不是DOM对象,直接把context指向document
    这里有两个有点怪地处理:
    1、当用户通过XPath访问时('//'开始),那么搜索是直接从document开始的,而不管是否提供了context
    2、如果搜索路径是从'/'开始的,搜索也是从document开始的,而且是直接略过了首个路径,也就是说
       以绝对路进搜索的话第一个搜索层次其实是无效的,因为最后都会变成<html>DOM元素。
          |
    (进行递规搜索)
    ret = [context]
    while(target)-->target是一个字符串
       删除路径中最前端的'//'
         |
       提取相对路径
       /^[\/>]\s*([a-z0-9*-]+)/i.exec(target)
           |--->提取相对路径是为了可以直接通过DOM子节点(firstChild/nextSibling)的形式搜索字节点
      当搜索路径中不满足相对路径的表达形式时进行循环判断是否符合一些预定义的路径格式,这些格式的识别是通过正则表达式来进行检查的,而且这些正则信息保存在jQuery的属性jQuery.token中,它包括:'..'、'+'、'~'、'>'这几种路进表达方式(jQuery的路径是以section为解析单位的,不同的Section中通过空格来分割,当前Section的解析结果会作为下一个section的上下文context,也就是说开发者可以自由地组合路径), 同时jQuery.token中还保存了对应路径形式的处理函数
      r = ret = jQuery.map( ret, jQuery.isFunction( jQuery.token[i+1] ) ?
       jQuery.token[i+1] :
       function(a){ return eval(jQuery.token[i+1]); });
   这里有个有趣的事情:如果jQuery.token[i+1]是一个字符串的话,它运行的环境是以a为参数的函数,也就是说这个可运行的string是以'a.XXX'的形式来调用的,从jQuery.token的结构我们也可以看到这个特点。当然,在处理完成后就会清除路径中相应的section信息
   f_a = jQuery.trim( t.replace( re, "" ) );
         |
    jQuery前面对于路径的分析主要是兼容XPath部分的,后面的分析则主要是针对CSS的路径解析方式提取DOM
    对象的
         |
    检查路径首字母是否为',', 逗号表示后面的路进信息与前面的路径是并列的关系,而不是"子路径" 的关系, 如果是,那么把当前搜索到的节点合并到搜索结果中,这里有两个细节需要注意的:
    1、将搜索上下文中删除find调用传入的参数context(或者是调整的context,这是为了避免无限递归)
       if ( ret[0] == context ) ret.shift();
    2、在调整路径的时候,特意在f_a中增加了一个前导空格,f_a = " " + f_a.substr(1,f_a.length), 这是为了
       在后面的过滤路径的调用中跳过路径的检查(/^[a-z[({<*:.#]/i.test(t))
        |
    如果路径的首字母不是逗号,那么继续进行分析
    检查是否满足'TagName#elementid'的形式,事实上这里是判断是为了加速常用的寻路表达方式
    /^([a-z0-9_-]+)(#)([a-z0-9\\*_-]*)/i.exec(t)
    对于其他的路径的表达方式,就通过更通用的方式匹配/^([#.]?)([a-z0-9\\*_-]*)/i.exec(t)
    不管是那一种方式匹配了,都会将匹配结果整理成[XXX, '#'/'.', tagid, (tagname)]
       |
    对于常用的以document为context,并以id作为查询的方式进行优化
    var oid = ret[ret.length-1].getElementById(m[2])
    对查询结果进行检查,主要是针对IE进行的,因为在IE中getElementById也可以得到name为ID的DOM元素
    |    if ( jQuery.browser.msie && oid && oid.id != m[2] )
    |    oid = jQuery('[@id="'+m[2]+'"]', ret[ret.length-1])[0];
    |    |              |-->这里需要注意的是,jQuery以ret[ret.length-1]为context,'[@id=tagid]'为查询字符
    |    |                 串,同时它只取了查询结果中的第一个结果。对'[@id=tagid]'的查询分析我们后面在详细看^_^。
    |    |
    |   进一步比较查询得到的DOM元素的标签类型是否和查询字符串一致
    |   ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];   
    |
 当搜索路径不是以#tagid的形式提供时,进行常规的搜索
 当路径以'.'开始时,说明是以className来搜索的,那么生成提取class的正则表达式
 var rec = new RegExp("(^|\\s)" + m[2] + "(\\s|$)");
    |
 提取满足条件的DOM元素
 jQuery.each( ret, function(){
  var tag = m[1] != "" || m[0] == "" ? "*" : m[2];
  if ( jQuery.nodeName(this, "object") && tag == "*" )
   tag = "param";
  jQuery.merge( r, m[1] != "" && ret.length != 1 ?
    jQuery.getAll( this, [], m[1], m[2], rec ) :
    this.getElementsByTagName( tag ));});
 这里需要注意的是搜索算法是,对于不满足路径正则或者是提供了'./#'的路径都是通过全搜索('*'标签)
 的形式来进行的,而且这里还特别对IE7进行修正(IE会把一些DOM对像解析为object,这时只能通过搜索'param'标签的形式获得它所有的下属节点)。同时在搜索下级节点的时候还进行了简单的判断,对于只有一个元素的上下文context中,只是简单地调用getElementsByTagName,这里其实有些取巧的,因为在ret只有一个上下文的情况下,即使开发者希望根据'.class'(如:$('//div .test')-->这里只有一个div) 的形式来搜索路径,但是jQuery在这里还是会调用getElementsByTagName(*)来获得所有元素的,可以看到,后面的程序专门针对ret.length == 1的情况进行了特别的处理,这个也许是出于效率的考虑,因为常规的jQuery.getAll也是可以完成这个功能的)
 if ( m[1] == "." && ret.length == 1 ){...} -->通过jQuery.grep来循环检测是否具有匹配的css类名
 if ( m[1] == "#" && ret.length == 1 ){...} -->通过jQuery.each来检测是否具有搜索的ID的DOM元素,当搜索到一个匹配元素时,马上终止搜索
    |
 清除已经处理的路径信息
 t = t.replace( re2, "" );
    |
 进行属性的过滤提取
 if ( t ) {
  var val = jQuery.filter(t,r);---|
  ret = r = val.r;                      |
  t = jQuery.trim(val.t);           |
 }                                           |
 |                |---------------------|
 |   filter函数首先通过正则表达式检查路径t是否满足合法的路径选择
 |   while( t && /^[a-z[({<*:.#]/i.test(t))
 |      |
 |     通过预定义的正则表达式进一步检查路径的结构,这里主要是重新组织搜索路径的信息
 |     jQuery.each( jQuery.parse,--> 这是路进的提取正则表达式,主要包括:
 |       function(i,re){                                       [@value='test'], [@foo]
 |     m = re.exec( t );                                    :contains('foo')
 |     if ( m ) {                                                 [div], [div p]
 |      t = t.substring( m[0].length );                :even, :last-chlid
 |      if ( jQuery.expr[ m[1] ]._resort )                 
 |       m = jQuery.expr[ m[1] ]._resort( m );
 |      return false;
 |     }});
 |     |
 |   经过正则处理后的结果含义是[XX, 路径的属性分隔符号(:/[等), 真正的属性值, 对于不同的路径特点附加的值... ]
 |   在上面处理可以看到为了处理的统一性,有些路径的参数可能需要重新排列,所以这里检查并调用了jQuery.expr

 |  [m[1]]._resort
 |      |                                                               |-->这个表示取反
 |   对于':not'-->重新调用jQuery.filter(m[3], r, true)
 |       '.classname'-->直接通过jQuery.grep来提取相应的DOM节点
 |   进行单独的处理
 |     |
 |   对于其它的路径结构从jQuery.expr中提取相应的字符串并生成对应的grep过滤函数
 |   eval("f = function(a,i){" +
 |      ( jQuery.expr[ m[1] ]._prefix || "" ) +
 |      "return " + f + "}");
 |   (这里都是通过'a'来划分名空间的)
 |   调用过滤函数
 |   jQuery.grep( r, f, not )
 |     |
 |     filter是以字典的方式返回结果的return { r: r, t: t },所以在调用的地方可以看到会将搜索路径重新赋值为val.t
 |     而结果就保存为val.r
 |

返回所有的搜索对象 return done
   
   好了,现在我们已经清楚了jQuery关于DOM的整个搜索流程了^_^,jQuery使用了强烈的泛式编程的思想,抽离了一些算法(如:grep/each/filter等),这个我比较喜欢,我总是觉得想法比实现总要一些^_^。
   
   下面我们看看jQuery中关于DOM属性访问的几个比较复杂的函数。
   jQuery对象原型函数空间:
   jQuery是通过修改jQuery的原型来增加功能函数的:

jQuery结构分析 - happyiww.popo - Happy I.W.W
   jQuery结构分析 - happyiww.popo - Happy I.W.W
    
   查询css属性函数
   css:function(element, property, force){
     如果查询的是高度height或宽度width
           |
     将element的padding/border的宽度都设置为0,并动态计算element的高和宽
        |      |-->如果当前的元素显示模式不为none(display != none),那么直接取元素的offsetHeight合offsetWidth
        |          如果当前的元素显示模式为none,那么动态创建一个visibility == hidden的元素
        |          (visibility: "hidden", position: "absolute", display: "block", right: "0", left: "0")
        |          加在element的父节点中,取clientHeight和clientWidth作为返回值
        |
  当查询其他属性时,调用jQuery.curCSS函数
                                   |-->如果查询的是透明opacity属性,那么直接返回jQuery.attr(elem.style, "opacity");
                                         |
                                       (如果指定force,那么直接中element.style中提取属性)
                                       检查document.defaultView,如果存在改对象,那么
                                       document.defaultView.getComputedStyle(elem, null);
                                       cur.getPropertyValue(prop.replace(/([A-Z])/g,"-$1").toLowerCase());
                                         |
                            如果document.defaultView.getComputedStyle失败(对于display为none的话,可能会返回失败)
                            通过jQuery.swap函数暂时将display设为block,然后从新计算(如果查询的是display本身,那么
                            直接返回none即可)
                                         |
                            否则就从elem.currentStyle中提取属性elem.currentStyle[prog]
   }
   
   clean:function(a){
     对于a的每个元素(html_str或者form 对象)提取对应的html元素
           |
     如果是form对象,那么直接将form对象直接加入到返回值中r.push( arg );
           |
     如果是html_str,那么创建一个div和html wrapper(如果html_str是options,那么创建select标签,thead/tbody/tfoot,创建table标签,td,创建table/tbody/tr, tr,创建table/tbody/), 并响应的string付给div.innerHTML, 从而创建标签,但是并没有增加到显示的dom树中。
     (修正IE中自动创建tbody标签的情况)
           |
     通过dom的访问方式将新创建的元素加入到返回的结果数组中
     for (var i=0, l=div.childNodes.length; i<l; i++)
   arg.push(div.childNodes[i]);
     |
  将arg的结果合并到返回值中r = jQuery.merge( r, arg );     
   }
   
   attr: function(elem, name, value){
     如果查询/设置的是透明属性opacity,那么:
     浏览器是ie-->设置zoom属性,并将alpha(XX)的形式删除,并且增加alpha(opacity=XX这里XX是一个0~100的值)
           mozilla-->如果value==1, 将它修正为0.9999(mozilla对opacity=1工作不好)
                 |
         (对于某些属性必须按照DOM1的命名来访问,这些信息保存在fix对象中)
         对于fix中的属性,直接用elem[fix[name]]的形式访问/修改
                 |
         对于ie且查询的是form的action/method属性,那么通过elem.getAttributeNode(name).nodeValue的形式获取
         如果查询的是普通的属性,或者是用户定义的属性,那么使用elem.getAttribute( name )的方式查询属性
                 |
         如果elem不是一个DOM节点,而是DOM.style属性,那么以elem[name]的方式获得对应的属性
         (可以看到attr支持从元素和style属性中获得属性的值)
   }
  jQuery结构分析 - happyiww.popo - Happy I.W.W 
  
2.jQuery的事件模型

   javascript的事件传递模型分为两个过程:自上而下的捕捉过程-->目标元素事件的触发-->自下而上的起泡过程.
   1.捕捉过程:事件从Document节点开始传递到目标节点,如果路径上的节点注册了捕捉该类型事件,那么相应的回调函数就会被执行
   2.目标元素对应事件的执行.
   3.事件起泡过程:事件从目标节点传递到Document节点,如果路径上的节点注册了该类型的事件,那么回调函数就会被执行.
   
  2.1. IE的事件 
   IE没有事件的捕捉过程,除了直接通过元素的属性来注册相应的事件回调函数外,还可以通过attachEvent函数来绑定相应的回调函数.
   事件对象是通过window.event这个全局变量来访问的,event的常用属性包括:

jQuery结构分析 - happyiww.popo - Happy I.W.W
   
  2.2.DOM2事件模型

jQuery结构分析 - happyiww.popo - Happy I.W.W
  jQuery结构分析 - happyiww.popo - Happy I.W.W   
   
   事件触发函数
   trigger: function(type, data, element) {
       修正data,data = jQuery.makeArray(data || []);
          |
       如果没有指定触发的元素,那么对于每个注册了该事件的元素触发对应的事件
       jQuery.each( this.global[type] || [], function(){
    jQuery.event.trigger( type, data, this ); });
       |
    生成一个简化的事件对象data.unshift( this.fix({ type: type, target: element }) );
    调用元素的事件处理函数element["on" + type ].apply( element, data )
       |
    调用被触发的函数element[ type ]();-->函数是从元素的type属性中获得的,它不是一个标准的HTML属性,
    所以在注册的时候需要jQuery自己生成并设置这个属性
   }
   
   在jQuery中经常要判断DOM对象是否已经加载完成, 通常可以使用window.onload属性来实现DOM加载后的回调,
   但是window.onload事件是在所有的元素都加载完后才触发的(如果页面上有很多图片,那么就需要等待所有的图片都下载完成后才执行).现在我们看看jQuery判断DOM加载完成的实现:
   对于mozilla和opera
   注册DOMContentLoaded事件函数,document.addEventListener( "DOMContentLoaded", jQuery.ready, false)
        |
   对于IE,那么动态创建脚本节点:
   document.write("<scr" + "ipt id=__ie_init defer=true " + 
   "src=//:><\/script>");
  需要注意的是这里使用了延迟执行属性"defer=true", 这个属性只对IE和外部的script有效,所以这里使用document.write方式生成script节点,等待DOM加载完成
  script = document.getElementById("__ie_init");
  script.onreadystatechange = function() {
    if ( this.readyState != "complete" ) return;
    this.parentNode.removeChild( this );
    jQuery.ready();
   };
    |
 对于safari,那么通过setInterval来检测DOM是否加载完成
 document.readyState == "loaded" || document.readyState == "complete"
     |
 最后这里还会注册window.onload事件(jQuery.ready)-->jQuery.ready会检查初始化是否已经被执行过
                                            |-->循环执行注册的初时化函数
                     jQuery.each( jQuery.readyList, function(){ this.apply( document );});
   
   和事件相关的还有bind/unbind/toggole/hover/ready等函数, 这些函数比较实现比较容易理解,这里就不展开了^_^.
   
   现在对于jQuery的事件处理模式我们比较清楚了^_^.
   
3.jQuery的ajax通信实现
  
  对于IE等浏览器(没有window.XMLHttpRequest对象),修正对应的属性XMLHttpRequest = function(){ return new ActiveXObject("Microsoft.XMLHTTP");}
  生成异步通信函数,这个函数是所有ajax的核心
  ajax: function( s ) {
           |
     生成ajax通信的属性控制对象
     s = jQuery.extend({}, jQuery.ajaxSettings, s);
           |
     如果属性对象中提供了data,并且启动了自动转换功能processData
     那么调用对象到字符串的转换函数
     s.data = jQuery.param(s.data);
           |
     对于"GET"请求,修正url-->s.url += ((s.url.indexOf("?") > -1) ? "&" : "?") + s.data;
           |
     如果设置了global属性,那么触发所有元素的"ajaxStart"事件
     jQuery.event.trigger( "ajaxStart" );
           |
     生成XMLHttpRequest对象并打开socket
     xml = new XMLHttpRequest();
     xml.open(s.type, s.url, s.async-->表示是否使用异步模式);
           |
     设置请求的HTTP头信息
     xml.setRequestHeader("Content-Type", s.contentType);-->提交的内容类型,最常用于设置内容编码(utf8)
     设置请求的时间差
     xml.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
     设置请求的类型
     xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
     设置连接的关闭属性
     xml.setRequestHeader("Connection", "close");
           |
     调用请求发送前的钩子函数s.beforeSend(xml);
           |
     触发全局的ajax回调函数jQuery.event.trigger("ajaxSend", [xml, s]);
           |
     以定时查询的方式检测请求是否成功(方便进行操时处理)
     setInterval(onreadystatechange, 13)
           |             |-->如果请求xml对象有效(xml.readyState == 4)
           |             |    删除定时器并检查请求的状态
           |             |    jQuery.httpSuccess( xml )-->检查xml.status/location.protocol/对于safari则检查status

           |             |    是否为undefined&& isTimeout != "timeout" ?
           |             |           s.ifModified && jQuery.httpNotModified( xml, s.url ) ? "notmodified" : "success" : "error";
           |             |                            |-->检查status是否为304,或者xml.getResponseHeader("Last-Modified")和保存

           |             |                                 的值一样,而对于safari,则是通过判断status是否为undefined
           |             | 
           |   提取请求返回的数据
           |   var data = jQuery.httpData( xml, s.dataType );
          |             |               |-->对于xml数据,返回r.responseXML,对于text数据返回r.responseText
          |             |                   对于json数据,返回eval( "data = " + data ),对于script,直接运行
          |             |                   jQuery.globalEval( data );
          |             |                            |-->如果存在window.execScript对象,那么window.execScript( data );
          |             |                                对于safari是不能马上执行script的,需要window.setTimeout( data, 0 );
          |             |                                对于其他情况直接使用eval的原型eval.call( window, data );
          |             |               对于html数据,那么直接生成html元素并执行html里的script(对于以src指定的script会间接

          |             |             再调用ajax函数处理) jQuery.globalEval( this.text || this.textContent || this.innerHTML || "" );
          |             |
          |   回调请求的成功回调钩子
          |   s.success( data, status );
          |       |
          |   触发全局的ajax回调函数
          |   jQuery.event.trigger( "ajaxSuccess", [xml, s] );
          |       |
          |   如果发生错误,那么调用错误处理函数jQuery.handleError(s, xml, status, e-->异常对象);
          |       |
          |   触发全局的ajax完成函数jQuery.event.trigger( "ajaxComplete", [xml, s] );
          |   当全部的ajax请求都完成后,触发ajax停止函数jQuery.event.trigger( "ajaxStop回调请求的完成处理钩子   |
          |   回调请求的完成处理钩子
          |   s.complete(xml, status);
          |
     对于设置了超时的请求,注册超时回调函数
     setTimeout(function(){
        |       if ( xml ) {
        |               xml.abort();-->放弃请求
        |             if( !requestDone )
        |             onreadystatechange( "timeout" );}}, s.timeout);
        |
     对于同步的请求模式,直接调用onreadystatechange()-->firefox1.5不修改readyState
  
    jQuery中ajax处理流程我们已经清楚了,ajax模块中getXXX的函数最后调用的都是ajax函数,这里就不展开了。与ajax相关的很重要的东西就是字符的编码,通过ajax传送数据有时会遇到乱码问题,这里提供几个函数进行转换:
    可解码utf8字符单元-->unicode(例如:"%E5%A5%BD"--> "好")
    function   utf8CodeToChineseChar(strUtf8)   
    {   
      var   iCode, iCode1, iCode2;   
      iCode = parseInt("0x" + strUtf8.substr(1, 2));   
      iCode1 = parseInt("0x" + strUtf8.substr(4, 2));   
      iCode2 = parseInt("0x" + strUtf8.substr(7, 2));   
        
      return   String.fromCharCode(((iCode & 0x0F) << 12)|     
             ((iCode1 & 0x3F) << 6)|   
              (iCode2 & 0x3F));   
    }
    
    utf8字符串-->unicode(例如:"%E5%A5%BD%E5%A5%BD"--> "好好")
    function   unicodeFromUtf8(strUtf8)     
   {   
    var bstr = "";   
    var nTotalChars = strUtf8.length;
    var nOffset = 0;
    var nRemainingBytes = nTotalChars;
    var nOutputPosition = 0;   
    var iCode, iCode1, iCode2;
      
    while(nOffset < nTotalChars)   
    {   
     iCode = strUtf8.charCodeAt(nOffset);   
     if((iCode & 0x80) == 0)
     {   
      if(nRemainingBytes < 1) 
       break;   
        
      bstr += String.fromCharCode(iCode & 0x7F);   
      nOffset++;   
      nRemainingBytes -= 1;   
     }   
     else if((iCode & 0xE0) == 0xC0)
     {   
      iCode1 = strUtf8.charCodeAt(nOffset   +   1);   
      if(nRemainingBytes < 2 ||
        (iCode1 & 0xC0)!= 0x80)
      {   
         break;   
        }      
      bstr += String.fromCharCode(((iCode & 0x3F) << 6) | ( iCode1 & 0x3F));   
      nOffset += 2;   
      nRemainingBytes -= 2;   
     }   
     else if((iCode & 0xF0) == 0xE0)
     {   
      iCode1 = strUtf8.charCodeAt(nOffset + 1);   
      iCode2 = strUtf8.charCodeAt(nOffset + 2);   
      if( nRemainingBytes < 3 || 
        (iCode1 & 0xC0)!= 0x80 ||
        (iCode2 & 0xC0)!= 0x80 )   
      {   
         break;   
      }   
        
      bstr += String.fromCharCode(((iCode & 0x0F) << 12)|     
               ((iCode1 & 0x3F) << 6)|   
                (iCode2 & 0x3F));   
      nOffset += 3;   
      nRemainingBytes -= 3;   
     }   
     else 
        break;   
    }   
      
    if(nRemainingBytes != 0) {   
     return   "";   
    }     
    return  bstr;   
  }
  
  一般来说使用ajax最好使用utf8编码,如果XMLHttpRequest需要处理gbk数据,对于firefox等可以设置
  req.overrideMimeType('text/html;charset=gb2312')完成(服务器返回数据时也要增加表明编码的头部),而IE中对于HTTP返回的gbk数据需要转变成utf8数据,不然,对于"$('#test').innerHTML = result"这样的语句就很可能出现乱码. 还有一点需要注意的是,utf8_data是全局变量,因为execScript是在全局环境运行的.
  function gbk2utf8(data)
  {
  var glbEncode = [], t, i, j, len;
  utf8_data = data;
  execScript("utf8_data = MidB(utf8_data, 1)+' '", "vbscript");
  t = escape(utf8_data).replace(/%u/g,"").replace(/(.{2})(.{2})/g,"%$2%$1").replace(/%([A-Z].)%(.{2})/g,"@$1$2");
  t = t.split("@");
  i = 0;
  len = t.length;
  while(++i < len){
   j = t[i].substring(0,4);
   if(!glbEncode[j]) {
    utf8_char = eval("0x"+j);
    execScript("utf8_char=Chr(utf8_char)","vbscript");
    glbEncode[j] = escape(utf8_char).substring(1,6);

   }
   t[i] = glbEncode[j] + t[i].substring(4);

  }
  utf8_data = utf8_char = null;
  return unescape(t.join("%")).slice(0,-1);
 };
 
4.jQuery的动画效果的实现
   
   jQuery的动画效果比较强(基本上它是参考moo.fx模块实现的),现在我们来看看它的实现.
   
   jQuery的动画效果都是通过animate函数实现的
   animate: function( prop, speed, easing, callback ) {
  return this.queue(function(){-->将动画控制函数加入到执行队列中  
    记录需要动态变化的属性
   this.curAnim = jQuery.extend({}, prop);
   
   创建动画属性保存对象
   var opt = jQuery.speed(speed, easing, callback);
                      |-->这里设置了几个属性,需要注意的是
                         complete,它是动画结束时的回调钩子,jQuery对这个函数设置了一个wrap
                         ,除了调用用户提供的钩子外还启动下一个动画的处理函数
   for ( var p in prop ) {
       创建动画效果(动画是通过定时修改DOM元素的css来实现的)的控制对象
    var e = new jQuery.fx( this, opt, p );
                   |-->记录DOM元素的显示属性,当动画结束后要恢复到原来的显示状态
                       oldDisplay = jQuery.css(elem, "display");
                          |
                       设置元素的overflow属性为hidden,从而达到移出移进的效果
                          |
                       绑定jQuery.fx对象的控制函数
                       设置属性函数fx.set-->对于opacity属性,调用jQuery.attr(element.style, "opacity", element.now)
                          |           对于其他属性,直接设置对应的属性element.style[prop] = parseInt(element.now) + "px";
                          |
                       获得属性最大值函数fx.max-->parseFloat( jQuery.css(elem,prop) )
                          |
                       获得当前属性函数fx.cur-->parseFloat( jQuery.curCSS(elem, prop) );
                          |
                       启动动画函数fx.custom-->获取当前的时间element.startTime.startTime(new Date()).getTime()
                          |                       |
                          |            初始化当前的属性值的大小element.now = from;
                          |                       |
                          |            注册定时函数更新属性值的大小
                          |            setInterval(function(){fx.step(from, to);}, 13);
                          |
                       修改属性步长函数fx.step-->计算当前时间t = (new Date()).getTime()
                                                  |
                                       如果当前时间大于开始时间加动画持续时间
                                       t > opt.duration + element.startTime,取消定时器
                                       clearInterval(z.timer);
                                                  |
                                       更新对应的属性fx.set,并将对应的动态属性标记为处理完成
                                       elem.curAnim[ prop ] = true;
                                                  |
                                       当所有的动态属性都修改完成后,设置对应属性的默认值(对于opacity属性,
                                       调用jQuery.attr设置,对于其他的属性就用element.style[property]="")
                                       调用option的complete回调函数
                                       (这里有个需要注意的地方:如果display为none, 把它设置为block-->如果当前
                                       是hide设置,那么就把display设为none,这个顺序就保证了toggle函数的正常运作)
                                                  |
                                       如果当前时间没有超过界限,那么调整属性值的大小,
                                       element.now = options.easing && jQuery.easing[options.easing] ?
                      jQuery.easing[options.easing](p, n,  firstNum, (lastNum-firstNum), options.duration) :
                      ((-Math.cos(p*Math.PI)/2) + 0.5) * (lastNum-firstNum) + firstNum;
                                 |
                                           更新对应的属性值
    if ( prop[p].constructor == Number )
        启动动画
     e.custom( e.cur(), prop[p] );
    else
     e[ prop[p] ]( prop );
   }
   });
 }   
  现在我们对于jQuery的动画效果的运作模式已经比较清楚了^_^. 
  还有一些函数与动态效果相关,它们都是通过调用animate函数实现的.
  
5.整体感觉
  1.jQuery中使用了很多技巧兼容了多个浏览器的差异,从中可以了解很多关于javascript的细节应用和标准
  2.jQuery兼容了css/dom/xpath元素的选取模式,同时还扩展了部分的语法功能,使用方便
  3.jQuery中实现了多个通用的算法,并通过this变量巧妙地完成名空间的转换

posted @ 2012-12-04 11:34  cyb  阅读(1673)  评论(0编辑  收藏  举报