第一天:~。~ jQuery源码的整体架构、规定一些变量与函数 以及 对jQuery对象添加一些属性和方法

 

源码含注释共8830行,以下为整体架构



//整体架构(左侧为行数)
(function(window,undefined){ //闭包

(传入window的意义在于:1.加快执行速度(不必靠作用域链查找window了) 2.便于压缩(在内部可以简写);)
(传入undefined的意义在于防止某些浏览器 (IE7 IE8等) 可以人为修改 undefined的情况出现;)
(21,94)  定义一些变量和函数jQuery = function(){}
  
(96,283)  给JQ对象添加一些属性和方法

(285,347)  extend:JQ的继承方法

(349,817)  jQuery.extend():扩展一些工具方法(静态方法)  如$.trim()等

(877,2856)  Sizzle:复杂选择器的实现 (略过)

(2880,3042)  callbacks:回调函数:对函数的统一管理

(3043,3183)  Deferred:延迟对象 : 对异步的统一管理

(3184,3295) :support功能检测

(3308,3652)  data():数据缓存

(3653,3797)  queue :队列管理

(3803,4299)  attr() prop() val() addClass()等对元素属性的操作

(4300,5128)  on() trigger() 事件操作的相关方法

(5140,6057)  DOM操作: 添加 删除 获取 包装 DOM筛选

(6058,6620)  css():样式的操作

(6621,7854)  提交的数据和ajax()

(7855,8584)  animate():运动的方法

(8585,8792)  offset():位置与尺寸的方法

(8804,8821)  JQ支持模块化的模式

(8826)  window.jQuery = window.$ = jQuery 对外提供接口
 }
)(window)



 

在了解了整体架构后,我们来看一下:

第一部分 .(21,94)

变量:

rootJquery

readyList

core_strundefined

location

document

docElem

以上3个变量也是方便压缩使用

_jQuery

class2type

core_deletedIds

core_version

core_concat

core_push

core_slice

core_indexOf

core_toString

core_hasOwn

core_trim

方法:



jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context, rootjQuery );//直接初始化
    },



在函数内部返回了一个实例对象,可以看出jQuery是一个面向对象的程序。那他是如何设计的?其实在jQuery源码中有一句

jQuery.fn = jQuery.prototype 即jQuery.fn就是jQuery的原型,而上述代码中jQuery.fn.init()才是构造函数,那不是乱套了?

其实不然,jQuery的设计是通过jQuery.prototype.init.prototype = jQuery.prototype将jQuery的原型赋给jQuery.fn.init的原型。

如下(模拟):



function jQuery(){
   return new jQuery.prototype.init();
}
jQuery.prototype.init = function(){};
jQuery.prototype.css = function(){};
jQuery.prototype.init.prototype = jQuery.prototype;
//此处将 jQuery.prototype赋给jQuery.prototype.init.prototype形成引用关系
jQuery().css()//可以正常使用



一些正则:

core_pnum:匹配数字,在后面的css方法中用到

core_rnotwhite:匹配非空

rquickExpr: 两个子项 匹配<p>hello 或者#div1等

rsingleTag: 匹配单标签 <li>  <li></li>等

rmsPrefix : 匹配 -ms- (ie)

rdashAlpha : 匹配 -webkit等  ,后面用于更换大小写;

2个方法:

fcamelCase : 转大写;

completed : DOM加载成功后的回调(后面用到);

 

第二部分.(96,283):jQuery.fn = jQuery.prototype = {} 

jquery : core_version    版本字符串'2.0.3'

constructor : jQuery      将constructor重指向至jQuery  (修正指向)

先来看一个小例子: 当我们使用jQuery的css方法时 如$('li').css('background','red')时, jQuery内部是如何处理的呢?



$('li').css('background',red)  在jQuery中的简易处理
//$('li'): 在面向对象中 this可以共享,所以将jQuery对象存成以下形式;
this={
  0:'li',
  1:'li',
  2:'li',
  length:3
}
//.css('background',red) : 对于上面特殊的对象 (012下标,有length属性),可以进行for循环处理 
for(let i=0;i<this.length;i++){
  this[i].style.background = 'red'
}



 

init方法:初始化参数管理:

当我们给jQuery传入参数的时候分为几种情况,比如:

1.$("") , $(null) , $(undefined) , $(false) : 直接return this ;

2.参数为字符串时 如$('#div1') , $('.box') , $('div') , $('#div div.box')  或者创建标签$('<li>') $('<li>1</li><li>2</li>')

3.参数为dom元素时  如$(this)  $(document);

4.参数为函数时 :$(function(){ })

jQuery对上面的情况先进行简易的分类处理。 利用名为match的数组分配各种情况。(详细见源码)

 

init方法中调用的工具方法(后面会有这些工具方法的实现):

  jQuery.parseHTML : 将字符串变成节点数组,如:'<li>1</li><li>2</li>'   to :   ['li','li'] 

  jQuery.merge    :  内部使用时可以将数组合并成类数组  ['li','li']     to :   this={0:'li',1:'li',length:2}

  jQuery.isPlainObject :   判断是否是对象字面量{}

(this)时,可以在内部转为类数组this

 

tips: match = rquickExpr.exec(selector)    :    .exec()返回一个数组,此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 正则第 1 个子表达式相匹配的文本,第 2 个元素是与正则的第 2 个子表达式相匹配的文本,没有则为null,以此类推。

selector : 存储选择的字符串

length : this对象的长度

toArray() : 转数组 :类似makeArray(工具方法)  利用Array.prototype.slice.call(obj)

get() : 转原生集合                  return num == null ?this.toArray() :( num < 0 ? this[ this.length + num ] : this[ num ] );

pushStack() : JQ对象的入栈处理。(先进后出),在源码中使用较多,例如$('div').pushStack('$('span')).css()操作的是span 而$('div').pushStack('$('span')).end().css()操作的是div



pushStack: function( elems ) {
              var ret = jQuery.merge( this.constructor(), elems );
              ret.prevObject = this;  //将栈更深的一级存起来 可以使用end()调用
              ret.context = this.context;
              return ret;
           },

  end:     function() {
              return this.prevObject || this.constructor(null);
           },



 

each() : 遍历集合,内部调用工具方法$.each()

ready() : DOM加载的接口

slice() : 集合的截取:利用pushStack



return this.pushStack( core_slice.apply( this, arguments ) );



first() : 集合的第一项 ,即eq(0)

last() : 集合的最后一项, 即eq(-1)

eq() : 集合某一项,内部调用pushStack

map() :映射,内部调用$.map()工具方法和pushStack

end() : pushStack的回溯方法;

push 、 sort 、  splice为jQ内部使用的方法。

---------------------------------------------------------------------------------------------------------------------------------------------------

第二天:~。~ jQuery的继承方法 以及 对jQuery添加一些工具方法

(285,347)

先来说说jQuery的extend的使用方式:

当参数只有一个对象字面量时,这时是扩展插件的情况,比如:



$.extend({
   aaa:function(){
      alert(1);
  }
})           此时为扩展工具方法,由$.aaa()调用;若是$.fn.extend则为扩展实例方法,由$().aaa调用



 

而当参数为多个时,这时是后面的对象扩展到第一个对象的方式,比如:



var a = {};
$.extend(a, { name:'hello'},{age:30})



也可以做浅拷贝和深拷贝(jQuery默认浅拷贝),比如:



var a = {};
var b ={name:{age:18}};
$.extend(a,b)
a.name.age = 28;//此时默认浅拷贝模式,对象之间引用关系,导致修改a.name影响到b.name
alert(b.name)   //28



var a = {};
var b ={name:{age:18}};
$.extend(true,a,b)  //增加参数true
a.name.age = 28;//此时为深拷贝模式,修改a.name不影响b.name
alert(b.name)   //18



JQ使用拷贝继承:在深拷贝时使用了递归,下面看下源码大概做了哪些事:如下:



先定义一些变量
 var options, name, src, copy, copyIsArray, clone,
           target = arguments[0] || {},
           i = 1,
           length = arguments.length,
           deep = false;
if(typeof target === "boolean){
           deep = target;
            target = arguments[1] || {};
            i = 2;
} // 看看是不是深拷贝的情况,即第一个参数是不是boolean

if( typeof target !== "object" && !jQuery.isFunction(target)){
        target = {};
}  //看参数正确不,若不正确变为空对象{}

if(length === i){
      target = this;
            --i;
}  //看是不是插件的情况,即只有一个对象字面量的参数

for(){//如果传入多个对象则开启for循环
   for(name in options){
        src = target[ name ];  //目标对象
        copy = options[ name ]; //拷贝对象
        if( target === copy ){// 防止循环引用,即$.extend({a,{name:a}})类似的情况
              continue;
        }if(deep &&  copy &©是对象或者数组){//深拷贝
//先做一下防止同名属性覆盖的处理 ( clone = src && jQuery.isPlainObject(src) ? src : {};)
          target[ name ] = jQuery.extend( deep, clone, copy );//使用递归分解执行
         }
         else if(){//浅拷贝
            target[ name ] = copy;直接赋值
        }
  }
    return target;
}



(349,817)

expando

noConflict() : 防止冲突;(先将别的库中的$=123 赋值给_$  然后再将_$赋值给$  舍弃$,使用新的变量名)



let $ = 123
<script src='jquery203.js'></script>
let new$ = $.noConflict();
new$(function(){
    alert($)  //123
})



isReady

readyWait

holdReady() : 推迟DOM触发; 比如:



$.holdReady(true);
$.getScript('a.js',function(){
   $.holdReady(false)
}
$(function(){
   alert(2)
});//这么操作就可以先加载完外部js文件再弹出2



内部实现为:



holdReady: function( hold ) {
            if ( hold ) {
                jQuery.readyWait++;
            } else {
                jQuery.ready( true );
            }
        }



ready()

先来集中说说DOM相关的方法:

window.onload()和$(function(){})的区别是什么?: 在于后者在dom加载完(不用文件加载完)即可执行(依赖DOMContentLoaded:即dom加载完触发的事件),而window.onload需要等文件图片等加载完毕才会执行;

而$(function(){})其实是$(document).ready(function)的一种简写方式,内部还是调用的后者;

通过源码可以看出$().ready()内部调用的是jQuery.ready.promise().done(fn) 延迟对象,而延迟对象内部无论是if还是else都调用了$.ready()这个工具方法,而$.ready()中有一句readyList.resolveWith(document,[jQuery])表示已完成,可以调用上述延迟对象的fn函数。下面详细看看这段比较绕人的异步操作源码:



jQuery.ready.promise = function( obj ) { //$().ready()中调用的方法
        if ( !readyList ) {//源码开头定义的一个变量,一开始为undefined

            readyList = jQuery.Deferred(); //延迟对象,后面会说
        
            if ( document.readyState === "complete" ) {
             
                setTimeout( jQuery.ready ); //setTimeout作用是防ie的一个bug

            } else {//若dom未加载完成,则监听事件
          
                document.addEventListener( "DOMContentLoaded", completed, false );//90行的回调
           
                window.addEventListener( "load", completed, false );//写2个防止缓存
            }
        }
        return readyList.promise( obj );
    };



下面看看completed回调的源码:(可以看出标红的地方无论dom有没有加载完毕,最终都是调用工具方法$.ready())



completed = function() {
            document.removeEventListener( "DOMContentLoaded", completed, false );//取消事件监听,所以之前的监听不会触发2次
            window.removeEventListener( "load", completed, false );//取消事件监听,所以之前的监听不会触发2次
            jQuery.ready();
        };



下面来看看重点的$.ready()工具方法的源码:



ready: function( wait ) {//传参时内部调用holdReady

            if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
                return;
            }

            jQuery.isReady = true;

            if ( wait !== true && --jQuery.readyWait > 0 ) {
                return;
            }//以上是处理holdReady的情景
   
            readyList.resolveWith( document, [ jQuery ] );
             //可以传参 如 $(function(arg){}) arg就是jQuery   document是指向

              if ( jQuery.fn.trigger ) {   
            // 第三种页面加载的书写方式(主动触发)如:$(document).on('ready',function(){})
                jQuery( document ).trigger("ready").off("ready");
            }
        }



isFunction()

isArray()

isWindow()

isNumeric() : 是否是数字,对NaN进行处理,判断为false,原生typeof判断 NaN时为true;

type() : 判断数据类型;( 利用  { }.toString.call([]) == '[object Array]'  )



jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
        class2type[ "[object " + name + "]" ] = name.toLowerCase();
    });



isPlainObject() :  是否为对象自面量;{ }或者new Object()  注意:window和DOM节点在$.type()判断时返回object但他们不是对象字面量。

isEmptyObject() : 是否为空的对象;{ }  ,  [ ]  , new Aaa()



isEmptyObject: function( obj ) {
            var name;
            for ( name in obj ) {
                return false;
            }
            return true;
        }//利用for-in 判断



error()

parseHTML(str,document,true) : 解析节点;将字符串变成节点数组,如:'<li>1</li><li>2</li>'   to :   ['li','li'] ,第三个参数为是否解析script标签。

parseJSON()

parseXML()

noop()

globalEval() : 全局解析JS代码 ,将局部变量解析成全局变量

camelCase()

nodeName(): 是否为指定节点名(内部);如$.nodeName(document.body,'body')  返回true

each()



each: function( obj, callback, args ) {
            var value,
                i = 0,
                length = obj.length,
                isArray = isArraylike( obj );

            if ( args ) {//内部使用时传入第三个参数args
                 //和外部使用的区别是 value = callback.apply( obj[ i ], args );因为args是不定参
                }
            } else {//正常使用时
                if ( isArray ) {
                    for ( ; i < length; i++ ) {
                        value = callback.call( obj[ i ], i, obj[ i ] );//第2,3个参数就是回调里的第1 2 个参数
                        if ( value === false ) {
                            break;
                        }
                    }
                } else {
                    for ( i in obj ) {
                        value = callback.call( obj[ i ], i, obj[ i ] );
                        if ( value === false ) {
                            break;
                        }
                    }
                }
            }

            return obj;
        },



trim()

makeArray()

内部调用了$.merge();

inArray()

merge()



merge: function( first, second ) {
            var l = second.length,
                i = first.length,
                j = 0;

            if ( typeof l === "number" ) {//第二个参数有length属性时
                for ( ; j < l; j++ ) {
                    first[ i++ ] = second[ j ];
                }
            } else {//第二个参数没有length,比如{0:'a',1:'b'}
                while ( second[j] !== undefined ) {
                    first[ i++ ] = second[ j++ ];
                }
            }

            first.length = i;//重新设置length

            return first;
        },



grep(): 过滤新数组;类似es5的filter          使用方法:arr= $.grep(arr,function(n,i){ return n>2})



grep: function( elems, callback, inv ) {//inv为true时取相反的情况
            var retVal,
                ret = [],
                i = 0,
                length = elems.length;
                inv = !!inv;   //转换类型

            for ( ; i < length; i++ ) {
                retVal = !!callback( elems[ i ], i );
                if ( inv !== retVal ) {
                    ret.push( elems[ i ] );//若符合条件就push到结果数组里
                }
            }
            return ret;
        },



map()



map: function( elems, callback, arg ) {
            var value,
                i = 0,
                length = elems.length,
                isArray = isArraylike( elems ),
                ret = [];
 
            if ( isArray ) {
                for ( ; i < length; i++ ) {
                    value = callback( elems[ i ], i, arg );

                    if ( value != null ) {//将回调处理的结果添加至空数组中
                        ret[ ret.length ] = value;
                    }
                }

            } else {
                for ( i in elems ) {
                    value = callback( elems[ i ], i, arg );

                    if ( value != null ) {
                        ret[ ret.length ] = value;
                    }
                }
            }

            // 防止嵌套数组
            return core_concat.apply( [], ret );
        },



guid

proxy()



proxy.guid = fn.guid = fn.guid || jQuery.guid++;  //最后给事件绑定设置唯一标识符,可以准确移除事件。



access(): 多功能值操作(内部);供attr)  css()等使用。举例,当我们使用css方法时:



$('div1').css('width')  //获取宽度
$('div1').css('width','100px')  //设置宽度
$('div1').css({width:'100px',height:'100px'})  //同时设置高度和宽度



像这样通过参数个数或形式的不同,进行不同的操作。除了css() 还有attr() prop() val() width()等,为了简化代码,将处理不同参数的功能统一的提取出来,生成这么一个工具方法,供后面使用。我们来看看它的实现:(比较绕)



access: function( elems, fn, key, value, chainable, emptyGet, raw ) {//参数fn用于区分不同的功能如attr css
           var i = 0,
               length = elems.length,
               bulk = key == null;       //bulk为false时表示有key值(width)
            // 设置多组值比如$('div1').css({width:'100px',height:'100px'})
           if ( jQuery.type( key ) === "object" ) {
               chainable = true;      // true为set  false为get
               for ( i in key ) {//递归分解
                   jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
               }

               // 设置一个值时如$('div1').css('width','100px')
            } else if ( value !== undefined ) {
               chainable = true;
               if ( !jQuery.isFunction( value ) ) {
                   raw = true;     //value不是函数时为true
               }
               if ( bulk ) {//没有key值时,用的不多
                  ...            
               }
               if ( fn ) {//有key值时,走回调
                  for ( ; i < length; i++ ) {
                    fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
                  }
                }
            }

            return chainable ?
                elems :
                // 获取值时
                bulk ? 
                    fn.call( elems ) :           // 没有key,走回调
                    length ? fn( elems[0], key ) : emptyGet;  // 有key
        },



now()

swap()



原理:
让display:none变为
display:block visibility:none  position:absolute 
取得值后变回display:none



jQuery.ready.promise():延迟对象

 isArraylike():是否是数组/类数组/带length的对象



function isArraylike( obj ) {
        var length = obj.length,
            type = jQuery.type( obj );

        if ( jQuery.isWindow( obj ) ) {//不是window,防止window下添加属性的情况
            return false;
        }

        if ( obj.nodeType === 1 && length ) {//元素节点类数组
            return true;
        }

        return type === "array" || type !== "function" &&
            ( length === 0 ||     //arguments的情况
            typeof length === "number" && length > 0 && ( length - 1 ) in obj );
    }



 

-------------------------------------------------------------------------------------------------------------------------------------------

第三天:~。~ Callbacks回调函数和Deferred异步统一管理 

(2880,3042) Callbacks:回调函数:对函数(可以是不同作用域的)的统一管理,它的基本使用情况如下:



function aaa(){
     alert(1)
}
function bbb(){
     alert(2)
}
let cb = $.Callbacks();
cb.add(aaa);   //  源码中通过将aaa,bbb 添加入一个名为list的数组统一管理
cb.add(bbb);
cb.fire()    //触发执行        源码中利用for循环触发



而它的作用主要用于对不同作用域的函数的统一管理:



let cb = $.Callbacks();
function aaa(){
     alert(1)
}
cb.add(aaa);  
(function (){    //aaa,和bbb的作用域不同
  function bbb(){
       alert(2)
  }
   cb.add(bbb);
})

cb.fire()  //不同作用域的函数也可以统一管理



Callbacks拥有4个配置选项(options) 如 let cb = $.Callbacks('once'),可以组合使用 用空格隔开 如('once memory')

once:(fire只会触发一次,源码中让for循环只执行一次) 

memory:(fire之后的add添加的函数也可以执行,源码中作用在add上)

unique : (去重,同名函数不能添加至list,源码中作用在add上)

stopOnFalse :(如果函数中return  false  则不添加至list ,源码中作用于for循环)

配置相关的源码如下:



//先设置一个配置缓存
  var optionsCache = {};//内容如{'once memory': {once:true,memory:true} }

// 设置一个开启配置的全局方法,后面用
    function createOptions( options ) {
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true; //将配置加入空对象中
        });
        return object;//{once:true,memory:true}
    }

 jQuery.Callbacks = function( options ) {
       //判断有没有传入options
        options = typeof options === "string" ?  //{once:true,memory:true}
            ( optionsCache[ options ] || createOptions( options ) ) ://从缓存拿或者用方法新建
            jQuery.extend( {}, options );//防止options是undefined
...}



方法:

私有函数fire :cb.fire()底层的实现依赖于私有函数fire



fire = function( data ) {
        memory = options.memory && data;  // 给memory赋值,add方法中就可以取到memory
        fired = true;
        firingIndex = firingStart || 0;
        firingStart = 0;
        firingLength = list.length;
        firing = true;       //用于处理在回调中使用fire()的情况,for循环后就变为false
        for ( ; list && firingIndex < firingLength; firingIndex++ ) {
//data[0]是执行环境  data[1]是fireWith传过来的参数args
          if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                  memory = false; 
                  break;
              }
          }
          firing = false;
          if ( list ) {
              if ( stack ) {  //  stack = !options.once && [],
                  if ( stack.length ) {
                      fire( stack.shift() );
                  }
              } else if ( memory ) {
                  list = [];
              } else {
                  self.disable();
              }
          }
      },



add 将函数push到list中:



add: function() {
                    if ( list ) {
                        var start = list.length;
                        (function add( args ) {//主要针对cb.add(fn1,fn2)
                            jQuery.each( args, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    if ( !options.unique || !self.has( arg ) ) {
                                        list.push( arg );
                                    }
                                } else if ( arg && arg.length && type !== "string" ) {
                                    //  针对cb.add([fn1,fn2])
                                    add( arg );
                                }
                            });
                        })( arguments );
                        if ( firing ) {
                            firingLength = list.length;
                        } else if ( memory ) {
 // 当调用fire()后fire的工具方法源码中会给memory赋值,此时在fire()后面的add方法就会走这个else if,重新调用fire
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },



 

相对于add,从list数组中去除成员list.splice(index,1)

 



remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );       
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },



 

has

 



has: function( fn ) {//查看list中是否包含传入的fn
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },



 

empty :  list = [ ]    firingLength = 0;

disable :禁止所有操作   list = stack = memory = undefined

return !list;  

lock : 只锁住后续的fire, 只将stack = undefined

return !stack; 

fireWith  调用fire私有函数

 



fireWith: function( context, args ) {
                    if ( list && ( !fired || stack ) ) {
                        args = args || [];
                        args = [ context, args.slice ? args.slice() : args ];
                        if ( firing ) {
                            stack.push( args );
                        } else {
                            fire( args );//cb.fire('hello')hello可以传递给私有fire函数
                        }
                    }
                    return this;
                }



 

fire  调用self.fireWith(this,arguments)

return !!fired

 

(3043-3183)Deferred:对异步的统一管理,基于$.Callbacks()设计,可以看下两者简单的对比:



let cb = $.Callbacks()
setTimeout(function(){
   alert(1);
   cb.fire() 
},1000)

cb.add(function(){
  alert(2)
})
-------------------------------------------------------
let dfd= $.deferred()
setTimeout(function(){
   alert(1);
   dfd.resolve() 
},1000)

dfd.done(function(){
  alert(2)
})



dfd.resolve()    dfd.reject()   dfd.notify()  《==》 cb.fire()

dfd.done()   dfd.fail()  dfd.progress() 《==》 cd.add()

$.ajax()内置了延迟对象,如下:



//普通写法
$.ajax({
  url: 'xxx.php',
  success : function(){},
  error :  function(){}
})
//延迟对象写法  自动触发resolve/reject
$.ajax('xxx.php').done(function(){}).fail(function(){})



延迟对象的一个简单需求分析:加载网页时,导航运动到一半时,网页内容才开始运动;而网页加载完后,再返回导航,

这时网页内容立刻运动,而不是等待导航运动到一半再运动,这就比较适合使用延迟对象(resolve)原理是使用了$.Callbacks的memory配置

下面看看源码:

开始先定义一个映射数组:



var tuples = [ //映射数组 其中once表示状态只变化一次,memory表示记忆,只要resolve就立刻触发done的函数
                  [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                  [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                  [ "notify", "progress", jQuery.Callbacks("memory") ]
              ],



然后新建一个promise对象,(添加[ done | fail | progress]方法即$.Callbacks()里的 list.add )  并没有resolve reject notify的方法,所以外部无法通过resolve()  reject()修改状态;来看一眼promise对象



promise = {
                    state: function() {
                        return state;
                    },
                    always: function() {//无论resolve还是reject都会触发
                       deferred.done(arguments).fail(arguments);
                       return this;
                    },
                    then: function( /* fnDone, fnFail, fnProgress */ ) {
                        ...
                    },
                    promise: function( obj ) {
//当有参数时(deferred),将promise继承给deferred,没有参数时返回promise对象!
                      return obj != null ? jQuery.extend( obj, promise ) : promise;
                    }
                },



然后再新建一个deferred对象,添加[ resolve | reject | notify]方法,然后通过后面代码promise.promise(deferred)即调用上述红色代码,将promise继承给deferred,当我们平时使用延迟对象时,想要在外部不能随便修改状态。会调用dfd.promise(),由于没有参数,此时返回的就是promise对象,而promise对象中没有deferred对象中的[ resolve | reject | notify]方法,所以无法在外部修改状态。

延迟对象的辅助方法when(): 内部return deferred.promise()即外部不能更改状态,类似es6的promise.all,例如:



$.when(aaa(),bbb()).done(function(){ //aaa(),bbb()必须return dfd
    alert('成功')          //aaa,bbb都resolve才执行
}).fail(function(){
    alert('失败')          //aaa,bbb只要一个reject就执行
})



源码中通过计数器remaining(即arguments.length)做判断,来处理各种参数情况

 

-------------------------------------------------------------------------------------------------------------------------------------------

第四天:~。~ support功能检测和data数据缓存

(3184,3295)support :功能检测

通过support检测,通过hooks解决:

1.input.type='checkbox'时,老版本webkit中input.value= ' '  而其他为'on',

2.option 的下拉select的第一项的secleted(是否选中) IE中默认false 其他默认true;

3.当input.checked = true时, input.cloneNode(true).check在IE9 IE10中不能正确克隆到。

4.当设置input.value = 't'    input.type ='radio' 时判断input.value有没有被修改成't' (在IE9 10 11中没有变成't' 而是'on')

5.IE下的onfocusin 和onfocusout  具有冒泡操作,其他浏览器不支持

6. background相关样式操作克隆时,IE9 IE10  在修改克隆的背景样式时会影响到原来元素的背景样式

7.下列功能检测在dom加载完后执行:

a.box-sizing:   首先要了解下box-sizing :content-box  和box-sizing: border-box的区别:比如在设置width为100px时,

content-box模式时,这100px并不包含border padding margin。 而border-box模式时:100px是所有宽度的和。

例如,假如您需要并排放置两个带边框的框,可通过将 box-sizing 设置为 "border-box"。这可令浏览器呈现出带有指定宽度和高度的框,并把边框和内边距放入框中。

b.当top值设置为1%时,只有safari的getComputedStyle不会转为像素值。

c.在box-sizing:border-box中 ,只有IE在设置width后,getComputedStyle获得的width值需要减去padding

 

(3308,3652) data():数据缓存:

 我们先来看一下data() prop()  attr()有什么区别:

$('#div1').attr('name','hehe'):  内部实现为document.getElementById('div1').setAttribute('name','hehe')    获取是alert(document.getElementById('div1').getAttribute('name'));

$('#div1).prop('name','hehe') : 内部实现为document.getElementById('div1') ['name'] ='hehe' ,获取是alert(document.getElementById('div1') ['name']);

$('#div1').data('name','hehe') :更适合于处理大量数据,而且可以避免内存泄漏,内存泄漏的原因之一就是:

当DOM元素与对象互相引用时,大部分浏览器会出现内存泄漏,如:



let div1 =document.getElementById('div1');
let obj = {};
div1.name = obj;
obj.age = div1            //垃圾回收机制无法销毁变量,导致内存泄漏



而data()是如何做到避免内存泄漏的呢?原因是data()用了一个中介对象cache={ },简易说明如下:



$(#div1).data('name',obj);//相当于<div id ='div1' xxx='1'></div>
$('body').data('age',obj) //相当于<body xxx='2'></body>
var cache ={ 
//中介cache中存的是需要添加的数据,由于自定义属性xxx='1'不是属性只是一个索引,所以不会引起内存泄漏
  1:{name:obj},
  2:{age:obj}
}



下面来研究下源码:



function Data() {
//首先将cache的属性'0' 设置为只能get不能set,而且只返回一个空对象。属性'0'是公用的,
而后面的属性‘1’’2‘‘3’...等是私有的,而且可以set
        Object.defineProperty( this.cache = {}, 0, {
            get: function() {
                return {};
            }
        });
        //唯一的标识  就是上述的<div xxx='1'>中的xxx
        this.expando = jQuery.expando + Math.random();
    }



Data.uid = 1;//即cache中的1 2 3 4...可以累加,
    Data.accepts = function( owner ) {
        //节点中只接受元素节点和document节点(即不接受文本节点等),和其他类型,
        return owner.nodeType ?
            owner.nodeType === 1 || owner.nodeType === 9 : true;
    };



下面来看看构造函数data的原型上的方法:



key: function( owner ) { //用于分配属性名,让元素与cache关联起来
         //如果不是可接受的参数,则返回0作为cache的属性'0'
            if ( !Data.accepts( owner ) ) {
                return 0;
            }
            var descriptor = {},
                //相当于如unlock = div[xxx]即 1
                unlock = owner[ this.expando ];

            if ( !unlock ) {//若不存在,就创建一个
                unlock = Data.uid++;
                try {
                    descriptor[ this.expando ] = { value: unlock };
                    Object.defineProperties( owner, descriptor );
                } catch ( e ) {//安卓4以下的兼容写法
                    descriptor[ this.expando ] = unlock;
                    jQuery.extend( owner, descriptor );
                }
            }
            if ( !this.cache[ unlock ] ) {
                this.cache[ unlock ] = {};
            }
            return unlock;
        }



set: function( owner, data, value ) {
            var prop,
                unlock = this.key( owner ),
                cache = this.cache[ unlock ];
           // 处理: [ owner, key, value ] args
            if ( typeof data === "string" ) {
                cache[ data ] = value;
          //处理: [ owner, { properties } ] args
            } else {
                    for ( prop in data ) {
                        cache[ prop ] = data[ prop ];
                    }
                }
            }
            return cache;
        }



get: function( owner, key ) {
            var cache = this.cache[ this.key( owner ) ];
            return key === undefined ?
                cache : cache[ key ];
        }



access: function( owner, key, value ) {//set和get的整合,通过参数不同选择不同操作
            var stored;
            if ( key === undefined ||
                ((key && typeof key === "string") && value === undefined) ) {
                stored = this.get( owner, key );
                return stored !== undefined ?
                    stored : this.get( owner, jQuery.camelCase(key) );
            }
            this.set( owner, key, value );
            return value !== undefined ? value : key;
        }



remove: function( owner, key ) {
            var i, name, camel,
                unlock = this.key( owner ),
                cache = this.cache[ unlock ];
            if ( key === undefined ) {
                this.cache[ unlock ] = {};
            } else {
                if ( jQuery.isArray( key ) ) { 
                    name = key.concat( key.map( jQuery.camelCase ) );
                } else {
                    camel = jQuery.camelCase( key );
                    if ( key in cache ) {
                        name = [ key, camel ];
                    } else {
                        name = camel;
                        name = name in cache ?
                            [ name ] : ( name.match( core_rnotwhite ) || [] );
                    }
                }
                i = name.length;
                while ( i-- ) {
                    delete cache[ name[ i ] ];
                }
            }
        }



hasData: function( owner ) {
            return !jQuery.isEmptyObject(
                this.cache[ owner[ this.expando ] ] || {}
            );
        }



discard: function( owner ) {//删除cache中的1(2,3...)的整体
            if ( owner[ this.expando ] ) {
                delete this.cache[ owner[ this.expando ] ];
            }
        }



下面来看看data的2个实例方法data()和removeData(),看之前先了解下jQuery设计的一个小思想:

就是对一组元素进行设置时是设置每一个元素,而获取时只获取第一个元素。data也是如此:那么来看下源码:



data: function( key, value ) {
            var attrs, name,
                elem = this[ 0 ],//jq对象的第一项,用于get
                i = 0,
                data = null;
         //$('#div1') .data()不传参时获取所有属性
            if ( key === undefined ) {
                if ( this.length ) {
                    data = data_user.get( elem );
              ...//此处有段代码针对html5的data-自定义属性
                return data;
            }

            // Sets multiple values 针对$('#div1').data({'a':1,'b':2})
            if ( typeof key === "object" ) {
                return this.each(function() {
                    data_user.set( this, key );
                });
            }

            return jQuery.access( this, function( value ) {
                ...//此处有段代码针对html5的data-自定义属性
                
       this.each(function(){...})// set
}); }, null, value, arguments.length > 1, null, true ); }



removeData: function( key ) {//调用原型上的remove方法
            return this.each(function() {
                data_user.remove( this, key );
            });
        }



 

(3653,3797)queue队列管理:先进先出的顺序管理,它的基本使用方式为:



function aaa(){
  alert(1);
}
function bbb(){
  alert(2);
}
$.queue(document,'q1',aaa);
$.queue(document,'q1',bbb);       //将aaa  bbb添加到名字为q1的数组里
console.log($.queue(document, 'q1') //[aaa(),bbb()]
$.dequeue(document,'q1')//出队,弹出aaa   再调用一次才会弹出bbb
实例方法为$(document).queue('q1',aaa)



queue和deferred的区别是queue更适合处理多步的异步操作,主要用于animation(内部队列名为' fx' )方法,让原生方法中的setInterval可以按顺序执行;比如:



$('#div1').animate({width:300},2000).queue(function(next){//next就相当于dequeue
    let This = this;
    let timer = setInterval(function(){
      This.style.height = This.offsetHeight + 1 +'px';
      if(This.offsetHeight == 200){
          next()
          clearInterval(timer)
      }
  }
},30)



下面来看看queue的工具方法的源码



queue: function( elem, type, data ) {
            var queue;
            if ( elem ) {
                type = ( type || "fx" ) + "queue";
                queue = data_priv.get( elem, type );                if ( data ) {
                    if ( !queue || jQuery.isArray( data ) ) {//若不存在queue就设置一个
                        queue = data_priv.access( elem, type, jQuery.makeArray(data) );
                    } else {
                        queue.push( data );//如果存在就push
                    }
                }
                return queue || [];
            }
        },



dequeue: function( elem, type ) {
            type = type || "fx";
            var queue = jQuery.queue( elem, type ),
                startLength = queue.length,
                fn = queue.shift(),
                hooks = jQuery._queueHooks( elem, type ),
                next = function() {
                    jQuery.dequeue( elem, type );
                };

            // 处理animate的情况
            if ( fn === "inprogress" ) {
                fn = queue.shift();
                startLength--;
            }
            if ( fn ) {
                if ( type === "fx" ) {
                    queue.unshift( "inprogress" );
                }
                delete hooks.stop;
                fn.call( elem, next, hooks );//源码中就是$.dequeue(),作为参数传给fn
            }
            if ( !startLength && hooks ) {
                hooks.empty.fire();   //调用_queueHooks中的empty方法清除不需要的对列名
            }
        }



_queueHooks  :当dequeue后,清除data中留下的用不上的队列名。

 下面看下queue的实例方法源码:

queue: 入队

dequeue:出队 ,内部调用工具方法$.dequeue

delay: 给队列加延迟执行时间 比如: animate().delay(2000).animate()



delay: function( time, type ) {
            time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;//源码最后的字符的速度
            type = type || "fx";
            return this.queue( type, function( next, hooks ) {
                var timeout = setTimeout( next, time );//next就是dequeue
                hooks.stop = function() {
                    clearTimeout( timeout );
                };
            });
        }



clearQueue: return this.queue( type || ' fx ', [ ] ) 即把queue变为空数组。

promise: 当整个队列都结束后调用回调,一般不用。

 

 

-------------------------------------------------------------------------------------------------------------------------------------------

第五天:~。~attr prop val addClass等对元素的操作

 在上面我们说过 attr()和prop()内部的实现 一个是原生的setAttribute()/getAttrbute(),另一个是[ ] /.   那么两者在实用方面有什么区别呢?

1.attr()可以设置和获取自定义属性,而prop()不可以;如<div hehe='haha'>

2 .当获取a标签的href值时,attr()获取的是原来的值,而prop()获取的是带有本机地址的值;

3.对于元素自带的属性,比如id  用removeProp()无法删除,而用removeAttr()可以删除;

下面先来看下attr prop等的实例方法:

 



attr: function( name, value ) {//走$.attr()工具方法的回调,通过arguments.length判断获取还是设置
            return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
        },

        removeAttr: function( name ) {//走$.removeAttr()的工具方法
            return this.each(function() {
                jQuery.removeAttr( this, name );
            });
        },

        prop: function( name, value ) {/走$.prop()的工具方法
            return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
        },

        removeProp: function( name ) {//只有实例方法
            return this.each(function() {
                delete this[ jQuery.propFix[ name ] || name ];
            });
        }



 

下面来看下attr reoveAttr的工具方法:



attr: function( elem, name, value ) {
            var hooks, ret,
                nType = elem.nodeType;
            if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
                return;//节点类型为文本,注释,属性时返回
            }
            if ( typeof elem.getAttribute === core_strundefined ) {
                return jQuery.prop( elem, name, value );//若没有getAttribute则用prop兼容一下
            }
            if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
              ...处理一个attr('check',true)的兼容问题,处理后调用的是attr('check','check')
            }
            if ( value !== undefined ) {
                if ( value === null ) { //针对$('#div1').attr('hehe',null),把hehe这个属性删除
                    jQuery.removeAttr( elem, name );
                } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
                    return ret;  //hook的兼容写法
                } else {
                    elem.setAttribute( name, value + "" );
                    return value;
                }
            } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
                return ret;
            } else {
                ret = jQuery.find.attr( elem, name );//sizzle中的方法,调用的getAttribute
                return ret == null ?
                    undefined :
                    ret;
            }
        }



removeAttr: function( elem, value ) {
            var name, propName,
                i = 0,
                attrNames = value && value.match( core_rnotwhite );
//针对removeAttr('title id name'),match后返回一个分割后的数组['title','id','name']
            if ( attrNames && elem.nodeType === 1 ) {
                while ( (name = attrNames[i++]) ) {
                    propName = jQuery.propFix[ name ] || name;
//propFix用来处理class关键字,修正为className  for关键字修正为htmlFor
                    if ( jQuery.expr.match.bool.test( name ) ) {
                        elem[ propName ] = false;//用于处理checked被删除后,checked属性也变为false
                    }
                    elem.removeAttribute( name );//调用原生的removeAtrribute()
                }
            }
        }



prop的工具方法类似attr ,里面有一段propHooks,针对tabIndex在IE下的兼容,tableIndex的作用是改变光标切换的顺序。

比如:



<input type='text' tableIndex = '2'>
<input type='text' tableIndex = '1'>
//此时用tab键切换时先切换到第二个再第一个,在IE中很多其他元素也拥有tableIndex的属性,会造成一些问题,jQ做了处理。



来看下addClass的实现:



addClass: function( value ) {
            var classes, elem, cur, clazz, j,
                i = 0,
                len = this.length,
                proceed = typeof value === "string" && value;
            if ( jQuery.isFunction( value ) ) {//如果写回调的话,一般不用
                return this.each(function( j ) {
                    jQuery( this ).addClass( value.call( this, j, this.className ) );
                });
            }
            if ( proceed ) {
                classes = ( value || "" ).match( core_rnotwhite ) || [];//分割一下
                for ( ; i < len; i++ ) {
                    elem = this[ i ];
                    cur = elem.nodeType === 1 && ( elem.className ?
                                ( " " + elem.className + " " ).replace( rclass, " " ) :
                                " "  //rclass是/[\t\r\n\f]/g,将这些转为空格。
                        );
                    if ( cur ) {//如果元素中已经有className的时候,进行处理
                        j = 0;
                        while ( (clazz = classes[j++]) ) {
                            if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
                                cur += clazz + " ";
                            }
                        }
                        elem.className = jQuery.trim( cur );
                    }
                }
            }
            return this;
        }



removeClass:类似addClass,不同点在于



if ( cur ) {
                        j = 0;
                        while ( (clazz = classes[j++]) ) {
                            while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
                                cur = cur.replace( " " + clazz + " ", " " );
                            }
                        }
                        elem.className = value ? jQuery.trim( cur ) : "";
                    }



toggleClass:可以接受第二个参数boolean 如toggleClass('box1 box2',true)代表执行addClass, false代表执行removeClass.



//主要代码为  ...
            return this.each(function() {
                if ( type === "string" ) {
                    var className,
                        i = 0,
                        self = jQuery( this ),
                        classNames = value.match( core_rnotwhite ) || [];
                    while ( (className = classNames[ i++ ]) ) {
                        if ( self.hasClass( className ) ) {
                            self.removeClass( className );
                        } else {
                            self.addClass( className );
                        }
                    }
                } else if ( type === core_strundefined || type === "boolean" ) {
                   //一般不用即toggleClass(true)
            });



hasClass:



hasClass: function( selector ) {
            var className = " " + selector + " ",
                i = 0,
                l = this.length;
            for ( ; i < l; i++ ) {
                if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
                    return true;
                }
            }
            return false;
        }



val: 针对value的操作,源码中首先要通过工具方法valHooks做一下兼容处理,比如<select>标签里如果返回type值为

select-one 如果<select multiple>则type值返回select-multiple 处理方法通过如果找不到select则通过nodeName寻找select。

 

 

 

-------------------------------------------------------------------------------------------------------------------------------------------

第六天:~。~event事件操作  on() trigger():

事件源码较多较复杂,先整理一下思路:

jquery源代码分析 jquery源码解读_javascript

从上图看出:平时我们使用的.click()  .hover等内部调用的是实例方法on() trigger()  off() ,而这些实例方法内部调用的是更底层的add()绑定事件   remove()解绑事件   trigger()主动触发事件方法;

由于实例方法on()的特殊'地位',我们先来关心下on的使用和实现:



//平时使用的情况,两个参数:一个事件名,一个回调
$('#div1').on('click',function(){alert(1)});

on: function( types, selector, data, fn, /*INTERNAL*/ one ) {}//第5个参数内部使用
//on()接收5个参数,其中selector用于事件委托,data用于传参,例如:
$('#div').on('click',{name:'hehe'},function(ev){//{name:'hehe'}就是参数data
     alert(ev.data.name)   //'hehe'
}

$('ul').delegate('li','click',function(){
    $(this).css('background','red'); 
}//通过事件委托,在ul上绑定事件,通过冒泡机制点击li时会触发ul的事件,知道源码后用on完全可以完成delegate的职责,如下:
$('ul').on('click','li',function(){//参数顺序有些变化
 $(this).css('background','red'); 
}



 看下on在源码里做了什么:



on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
            var origFn, type;
            if ( typeof types === "object" ) {//处理第一个参数是{}的形式
                if ( typeof selector !== "string" ) {
                    // ( types-Object, data )
                    data = data || selector;
                    selector = undefined;
                }
                for ( type in types ) {//for in  分解递归
                    this.on( type, selector, data, types[ type ], one );
                }
                return this;
            }
            if ( data == null && fn == null ) {//对参数进行顺序的处理
                fn = selector;
                data = selector = undefined;
            } else if ( fn == null ) {
                if ( typeof selector === "string" ) {
                    fn = data;
                    data = undefined;
                } else {
                    fn = data;
                    data = selector;
                    selector = undefined;
                }
            }
            if ( fn === false ) {
                fn = returnFalse;
            } else if ( !fn ) {
                return this;
            }
            if ( one === 1 ) {//即$('div').one('click',function(){})只触发一次,下面有one的源码
                origFn = fn; //将fn 存一下
                fn = function( event ) {
                    jQuery().off( event ); //取消$(this)的事件
                    return origFn.apply( this, arguments ); //调用存起来的函数
                };
                // 分配一个特定的guid,普通函数和事件函数都可以识别到
                fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
            }
            return this.each( function() {//调用底层add方法
                jQuery.event.add( this, types, fn, data, selector );
            });
        }



one: function( types, selector, data, fn ) {
            return this.on( types, selector, data, fn, 1 );
        }



off: function( types, selector, fn ) {
          var handleObj, type;
          if ( types && types.preventDefault && types.handleObj ) {
              handleObj = types.handleObj;
              jQuery( types.delegateTarget ).off(
              handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
                  handleObj.selector,
                  handleObj.handler
              );
                return this;
            }
            if ( typeof types === "object" ) {//同on 处理第一个参数是{}
                for ( type in types ) {
                    this.off( type, selector, types[ type ] );
                }
                return this;
            }//同on 处理参数顺序
            if ( selector === false || typeof selector === "function" ) {
                fn = selector;
                selector = undefined;
            }
            if ( fn === false ) {
                fn = returnFalse;
            }
            return this.each(function() {//调用底层的remove方法
                jQuery.event.remove( this, types, fn, selector );
            });
        }



delegate和undelegate内部都是调用on()和off()

bind和 unbind内部也调用on()和off();

下面看下trigger和triggerHandler的区别:后者不会触发事件的默认行为。比如:



$('input').focus(function(){$(this).css('background,'red')});
$('input').trigger('focus)//光标会在input中闪烁(默认行为会触发),背景也变红
$('input').triggerHandle('focus) //不会触发事件默认行为,只将背景色变红



两者的实现很简单,都是调用了底层的trigger方法,通过第四个参数来判定行为:



trigger: function( type, data ) {
            return this.each(function() {//调用底层trigger
                jQuery.event.trigger( type, data, this );
            });
        },
  triggerHandler: function( type, data ) {
           var elem = this[0];
           if ( elem ) {
               return jQuery.event.trigger( type, data, elem, true );
           }
        }
    })



下面重点来看看底层的add()    remove()   trigger()方法:由于源码代码量较大,先看下非常简化的版本如(红色部分为trigger):



var div1 =document.getElementById('div1');
window.onload=function () {
    var aaa= function () {
        alert(111)
    };
   add(div1,'show',aaa);//自定义事件,通过trigger触发;
  // remove(div1,'show',aaa);
   trigger(div1,'show')
};
function add(obj,types,fn) {//在obj上挂载一个自定义属性,并添加数组如div1.listeners['show']形成映射关系
    obj.listeners =obj.listeners || {};
    obj.listeners[types] = obj.listeners[types]||[];
    obj.listeners[types].push(fn);//将函数aaa等push到数组中,在后面依次执行。
    obj.addEventListener(types,fn,false)
}
function remove(obj,types,fn) {
    obj.removeEventListener(types,fn,false);
    delete obj.listeners[types];//删除数组
}
function trigger(obj,types) {
    var arr = obj.listeners[types] ||[];
    for(var i=0;i<arr.length;i++){
        arr[i]();//执行函数
    }
}



 在jQuery源码中,并不是直接在obj上挂载自定义属性,因为可能会引起内存泄漏,而是使用了data数据缓存的方法

让我们在控制台打印一下elemData(data_priv.get( elem ))看一下里面大概的内容:



$(function(){
  $('#div1').on('click',function(a){alert(1)
  $('#div1').on('click',function(b){alert(2)}
  $('#div1').on('click','span',function(c){alert(3)}
  $('#div1').on('mouseover',function(d){alert(4)}
}
elemData={
    events:{
      'click':[   //还有delegateCount和length2个属性,第一个属性是委托的个数。
{},//委托的 排在前方,无论guid的值
{
        data:undefined,
        guid:3, //唯一标识符
         handler:function(c){},//自己绑定的函数
        namespace:'', //命名空间,比如click.aaa 方便分类操作;
         needContext:false,//委托相关,处理span:first这种伪类情况 如果没有委托就委undefined
        origType:'click',//原始的事件类型,如mouseenter
        selector:'span', //事件委托
        type:'click'//需要兼容后的事件类型,比如mouseenter=>mouseover
        },
        {},
      ],
      'mouseover':[
        {}
      ]
   }
   handle:function(e){}
}



现在理一下流程,当on()的时候调用的add() 其实add内部调用的是dispatch,dispatch做了三件事:



jQuery.event.fix    //处理event兼容
jQuery.event.special  //处理特殊事件的兼容
jQuery.event.handlers  //处理事件的顺序问题



现在来看看jQuery源码里jQuery.event里各个方法的功能:



jQuery.event={
    global:
    add:绑定事件
    remove:解绑事件
    trigger:主动触发
    dispatch:分发事件的具体操作
    handlers:函数执行顺序的操作
    props : JQ中共享原生JS的event属性
    fixHooks :收集event兼容的结合
    keyHooks:键盘的event兼容
    mouseHooks:鼠标的event兼容
    fix:event对象的兼容处理
    special:特殊事件的兼容处理
    simulate:focusin的模拟操作(利用trigger)
}



mouseHooks和keyHooks

先来看看鼠标兼容问题 :

1.clientY和pageY  : pageY是鼠标点到页面顶的距离,clientY是鼠标点到可视区的距离,而且pageY有浏览器兼容问题,

jQuery的做法是,把clientY+scrollY拼接成pageY解决兼容问题;

2 event.which 和event.button的兼容处理(兼容成 左键1中键2右键3)注意IE中最后用mousedown来获得event.which而不是用click,

如果使用click  IE可能会直接弹出右键菜单而非获得事件对象的值;

键盘兼容问题:



if ( event.which == null ) {//当keypress事件时,keycode可能取不到值
           event.which = original.charCode != null ? original.charCode : original.keyCode;
        }



阻止冒泡:ev.stopPropagation()  和ev.stopImmediatePropagation()的区别是后者自身相同事件也会被阻止;

 

下面看下jQuery.event.special(dispatch做的第二件事)



load       //noBubble 处理img等 onload会冒泡的问题
focus     //focus不应冒泡,而当委托时,变为focusin并做兼容
blur
click      //如果是checkbox的click则选中  如果是a标签则不跳转
beforeunload//火狐20+里兼容添加returnValue,否则关闭网页不弹框
mouseenter
mouseleave
focusin //内部用trigger模拟自定义事件,达到兼容目的
focusout



mouseover和mouseout兼容性好,但是有缺陷(比如嵌套元素,会反复触发事件);而mouseenter和mouseleave没缺陷,兼容性差些,jQuery利用mouseover和mouseout模拟mouseenter和mouseleave来解决问题;

下面看下jQuery.event.handle(dispatch第三件事):1委托优先  2委托层级越深的越优先

-------------------------------------------------------------------------------------------------------------------------------------------

第七天:~。~dom操作:

先来看下几个常用方法.filter()   .not()   .has()  :都是对自身进行操作,而find是对找到的子项进行操作;



$('div').filter('.box').css(...)  //过滤出自身有.box的元素
$('div').not('.box').css(...) //和filter相反
$('div').has('.box').css(...) //过滤出子项有.box的元素



看下filter和not的源码:可以看出调用的是以前说过的入栈pushStack操作,



not: function( selector ) {//通过winnow的第三个参数判断行为
            return this.pushStack( winnow(this, selector || [], true) );
      },
      filter: function( selector ) {
            return this.pushStack( winnow(this, selector || [], false) );
      }



而这个winnow是什么呢?它是一个过滤的方法,将过滤后的数组推入到pushStack的栈中



function winnow( elements, qualifier, not ) {
        if ( jQuery.isFunction( qualifier ) ) {
            return jQuery.grep( elements, function( elem, i ) {//回调的情况
                return !!qualifier.call( elem, i, elem ) !== not;
            });
        }
        if ( qualifier.nodeType ) {//getElementsByClassName()的情况
            return jQuery.grep( elements, function( elem ) {
                return ( elem === qualifier ) !== not;
            });
        }
        if ( typeof qualifier === "string" ) {//筛选条件如'.box'
            if ( isSimple.test( qualifier ) ) {
                return jQuery.filter( qualifier, elements, not );
            }
            qualifier = jQuery.filter( qualifier, elements );
        }
        return jQuery.grep( elements, function( elem ) {
            return ( core_indexOf.call( qualifier, elem ) >= 0 ) !== not;
        });
    }



find(): 

is():

index():基本使用为$('#div1').index() :即判断#div1在兄弟节点中的位置(0.1.2.3...),可以传递参数index('span')缩小查找范围;

closest():基本使用为$('#div1').closest('.box'):即对#div1的祖先节点(包含自身)中具有条件.box的最近的节点进行操作,可以传第二个参数限制查找范围;

add():基本使用为$('#div1').add('span')即将span添加到#div1的集合里形成一个整体,然后对整体进行操作

addBack():基本使用为$('#div1').find('span').css(...).addBack().css()即不仅可以回溯也对当前的严肃操作(同时操作),类比end()

下面集中看下系列源码:



jQuery.each({
        parent: function( elem ) {//父节点,传参限定条件
            var parent = elem.parentNode;
            return parent && parent.nodeType !== 11 ? parent : null;
        },
        parents: function( elem ) {//所有祖先节点,传参限定条件如'div'
            return jQuery.dir( elem, "parentNode" );
        },
        parentsUntil: function( elem, i, until ) {//parentsUntil('body')截止到body不包含body,第二个参数也可以限定条件
            return jQuery.dir( elem, "parentNode", until );
        },
        next: function( elem ) {//下一个兄弟节点
            return sibling( elem, "nextSibling" );
        },
        prev: function( elem ) {//上一个兄弟节点
            return sibling( elem, "previousSibling" );
        },
        nextAll: function( elem ) {//下面所有兄弟节点
            return jQuery.dir( elem, "nextSibling" );
        },
        prevAll: function( elem ) {//上面所有兄弟节点
            return jQuery.dir( elem, "previousSibling" );
        },
        nextUntil: function( elem, i, until ) {//截止到xxx
            return jQuery.dir( elem, "nextSibling", until );
        },
        prevUntil: function( elem, i, until ) {//截止到xxx
            return jQuery.dir( elem, "previousSibling", until );
        },
        siblings: function( elem ) {//选择所有兄弟节点(不包括自身)
            return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
        },
        children: function( elem ) {//选择所有子节点
            return jQuery.sibling( elem.firstChild );
        },
        contents: function( elem ) {//可以包含空白节点,文本节点等 原生childNodes就是获取所有节点
            return elem.contentDocument || jQuery.merge( [], elem.childNodes );
        }
    }, function( name, fn ) {
        jQuery.fn[ name ] = function( until, selector ) {
            var matched = jQuery.map( this, fn, until );
            if ( name.slice( -5 ) !== "Until" ) {
                selector = until;
            }
            if ( selector && typeof selector === "string" ) {
                matched = jQuery.filter( selector, matched );
            }
            if ( this.length > 1 ) {
                if ( !guaranteedUnique[ name ] ) {
                    jQuery.unique( matched );//去重
                }
                if ( rparentsprev.test( name ) ) {
                    matched.reverse();//修正排序
                }
            }
            return this.pushStack( matched );
        };
    })



jQuery.dir()出场较高:



dir: function( elem, dir, until ) {
            var matched = [],
                truncate = until !== undefined;
           while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { //节点不能为document
                if ( elem.nodeType === 1 ) {
                    if ( truncate && jQuery( elem ).is( until ) ) { //如果有until,则不push
                        break;
                    }
                    matched.push( elem );
                }
            }
            return matched;//将结果存入数组
        }



下面继续:

remove():基本使用为$('div').remove('.box') 参数可传 为筛选条件,返回值就是删除的元素,删的干净包括绑定的事件等;

detach():基本使用为$('div').detach('.box') 参数可传 为筛选条件,保留绑定事件等;内部就是调用的remove()第二个参数传true



remove: function( selector, keepData ) {
            var elem,
                elems = selector ? jQuery.filter( selector, this ) : this,
                i = 0;
            for ( ; (elem = elems[i]) != null; i++ ) {
                if ( !keepData && elem.nodeType === 1 ) {//若keepData存在则为detach
                    jQuery.cleanData( getAll( elem ) );//cleanData清空数据
                }
                if ( elem.parentNode ) {
                    if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
                        setGlobalEval( getAll( elem, "script" ) );
                    }
                    elem.parentNode.removeChild( elem );
                }
            }
            return this;
        }



empty:基本使用为$('div').empty() 相当于innerHTML='';

html(): 基本使用为$('div').html()  ,和原生innerHTML的区别是html('<script>alert(1)</script>')会执行js代码,而原生不执行;

text():基本使用为$('div').text(),前者解析htm标签;

clone():基本使用为$('div').clone(true) 第一个参数表示是否克隆绑定的事件等, 第二个参数表示子元素是否进行事件操作;

内部使用原生的cloneNode()实现,原生的cloneNode在IE9  IE10中并不能克隆checked的状态,jQuery兼容了这个bug

before():基本使用为$('div').before($('span')) 即把span插到div前面,类似功能insertBefore()

after():基本使用为$('div').after($('span')) 即把span插到div后面,与before()内部都是调用原生insertBefore();类似功能insertAfter()

append():基本使用为$('div').append($('span')) 即把span添加到div内部的最后,内部调用原生appendChild(),类似功能appendTo()

prepend():基本使用为$('div').prepend($('span'))即把span添加到div内部的最开始,内部调用原生insertBefore(),类似功能prependTo()

replaceWith  replaceAll :基本使用为$("p").replaceWith("<b>Hello world!</b>")即替换

wrap():基本使用为$('span').wrap('<div>')即把每个span外面都包上一个div

wrapAll():基本使用为$('span').wrapAll('<div>')即把span整体的外面包上一个div

wrapInner():基本使用为$('span').wrapAll('<div>')即把每个span内部子节点的外面包上一个div

unwrap():基本使用为$('span').unwrap(),即除了body外删除其父级(包装)

 

-------------------------------------------------------------------------------------------------------------------------------------------

第八天:~。~css相关操作与Ajax:

先来看下框架:



一些变量
function vendorPropName(){} //添加浏览器前缀,如o ms webkit moz
function isHidden(){}
function getStyles(){}
function showHide(){}
jQuery.fn.extend({
  css //获取时调用$.css() 设置时调用$.style()
  show  //display block
  hide  //display none
  toggle
})
jQuery.extend({
  cssHooks
  cssNumber
  cssProps //将float兼容成cssFloat
  style //内部调用原生style
  css  //内部调用curCss,原生getComputedStyle()
})
curCSS=function(){}//处理了一个IE9的filter获取不到的兼容问题等
function setPositiveNumber(){}
function argumentWidthOrHeight(){}
function getWidthOrHeight(){}
function css_defaultDisplay(){}//动态获取隐藏元素的display值(通过nodeName然后createElement得到后再remove)
function actualDisplay(){}
一些cssHooks



先来看看原生的获取样式方法:行间样式的获取可以用.style (可以获取复合样式如background)   而获取其他样式用window.getComputedStyle('元素','伪类')(不能获取复合样式)

 width():100 时,当padding为10px, border为1px,  margin为5px时,

innerWidth()  : 120    即width+padding

outerWidth() : 122  即width+padding+border         outerWidth(true) :   132 即width+padding+border+margin

当width(200)时:width=200

innerWidth(200):width = 200 - padding

outerWidth(200):width = 200 - padding-border

outerWidth(200,true):width = 200 - padding-border-margin

-----------------------------------------------------------------------------------------------------------------------------------

ajax():

$.param({'aaa':1,'bbb':2})   ==> 结果是aaa=1&bbb=2    ,如果有汉字或特殊符号则需要编码,

需要注意的是$.param( [ {'name':'1','value':'2' } ] ) ==>结果是1=2 ,针对的是form表单;

tips:当空格在表单中传到后台时会转为‘+’ 而不是encodeURIComponent转的%20

下面先看下ajax的源码框架:



function addToPrefiltersOrTransports(){}  
 function inspectPrefiltersOrTransports(){} //两个函数利用柯里化组成dataType:fn的形式并调用fn
 function ajaxExtend(){}
 jQuery.fn.load = function(){}
 jQuery.extend({
    ajaxSettings    //默认参数
    ajaxSetup       //配置操纵覆盖默认参数
    ajaxPrefilter   //网址url的预先处理
    ajaxTransport   //分发处理器(比如是否需要动态创建srcipt)
    ajax
    getJSON
    getScript
})
 jQuery.each( [ "get", "post" ], function(){}
 function ajaxHandleResponses(){}
function ajaxConvert(){}   //类型转换器,识别dataType
其他一些方法



5个接口$().load()    $.get()  $.post()  $.getJSON()   $.getScript()  内部调用的都是底层的$.ajax()方法

$().load():基本使用为$(‘div’).load(' 1data.html' , {'name':'hello'}

$.get() :基本使用为$.get('1data.html' ,{} ,function(){},'json')

$.post():基本使用为$.post('1data.html' ,{} ,function(){},'json')

$.getJSON():

$.getScirpt():

全局事件:ajax拥有 ajaxStart  ajaxStop  ajaxComplete  ajaxError  ajaxSuccess  ajaxSend这6个全局事件,每个ajax调用时都会触发这些事件。(内部调用on,其实是一种自定义事件,用trigger主动触发)

 

addToPrefiltersOrTransports()  inspectPrefiltersOrTransports(){}这2个函数利用了柯里化,那我们来看看什么是柯里化:



//一个简单的柯里化 分解参数
function show(n1){
  return function(n2){
    return n1+n2
  }
}
var num1=show(3)
var num2=show(4)
console.log(num1(5)) //8




------------------------------------------------------------------------------------------------------------------

animate: 先来看下整体架构



tweeners={} //css样式对应处理
function createFxNow(){}
function createTween(){}
function Animation(){} //核心方法
function propFilter(){} //属性过滤操作
jQuery.Animation = jQuery.extend(Animation,{
    tweener
    prefilter
})
function defaultPrefilter(){}
function Tween(){}
Tween.prototype={
  init
  cur
  run
}
Tween.propHooks={}
jQuery.each(['toggle','show','hide'],function(){})
jQuery.fn.extend({
  fadeTo
  animate
  stop
  finish
})
function genFx(){}
jQuery.each({
  slideDown
  slideUp
  slideToggle
  fadeIn
  fadeOut
  fadeToggle
},function(){})
jQuery.speed =function(){}
jQuery.easing={
  linear
  swing
}
jQuery.timers=[]
jQuery.fx.tick=function(){}
jQuery.fx.interval=13;
jQuery.fx.start =function(){}
jQuery.fx.stop =function(){}
jQuery.fx.speeds = {}



来看看一些api的简单实用

$(div).hide(1000)   //改变width height opacity      show()和toggle()同

$(div).sildeUp(1000)  //向上卷曲   

$(div).sildeDown(1000)  //向下展开  

slideToggle()

$(div).fadeOut(1000)  //淡出  改变opacity      fadeIn()和fadeToggle()同

$(div).animate({width:400},1000,'swing',function(){ }) //比较底层的api

$('div').fadeTo(1000,0.5)

 

offset()    获取元素距离屏幕的距离 .top  .left  ,无视祖先节点的定位  如果传参就设置

position()   相对于父级的距离 且不计算margin值