第一天:~。~ 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 : 就是jQuery(document) 用jquery选择到的document ,赋给这个变量方便压缩(简写)和维护(理解);
readyList : 与后面的DOM加载相关;
core_strundefined = typeof undefined : 字符串的undefined 处理一下IE6789使用xml的一些bug ,比较小众的问题;
location = window.location
document = window.document
docElem = document.documentElement //即html标签
以上3个变量也是方便压缩使用
_jQuery = window.jQuery ; _$=window.$ : 防与别的库中的$或者jQuery冲突时使用;
class2type = {} : 类型判断时使用。 如$.type() 内部存储的方式大致为{'[Object String]' : 'string',' [Object Array]' : 'array'}
core_deletedIds = [];
core_version = '2.0.3';
core_concat = core_deletedIds.concat;
core_push = core_deletedIds.push;
core_slice = core_deletedIds.slice;
core_indexOf = core_deletedIds.indexOf;
core_toString = class2type.toString;
core_hasOwn = class2type.hasOwnProperty;
core_trim =core_version.trim 以上7个变量便于压缩使用
方法:
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) 给JQ对象添加一些(实例)属性和方法: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 : 判断是否是对象字面量{}
jQuery.makeArray :[],传第二个参数(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 可以用end()回溯。
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) extend:JQ的继承方法:
先来说说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) jQuery.extend():扩展一些工具方法(静态方法):具体如下:
expando : 生成唯一的JQ字符串(内部),在后面数据缓存,事件操作,ajax等处使用;
noConflict() : 防止冲突;(先将别的库中的$=123 赋值给_$ 然后再将_$赋值给$ 舍弃$,使用新的变量名)
let $ = 123
<script src='jquery203.js'></script>
let new$ = $.noConflict(); new$(function(){ alert($) //123 })
isReady : DOM是否加载完毕(内部);
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触发;
先来集中说说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() : 是否是函数,通过$.type()实现,后面会说;
isArray() : 是否是数组,内部实现使用原生的Array.isArray();
isWindow() :是否是window;
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() : 解析JSON;
parseXML() : 解析XML;
noop() : 空函数(容错处理);
globalEval() : 全局解析JS代码 ,将局部变量解析成全局变量
camelCase() :转驼峰(内部);
nodeName(): 是否为指定节点名(内部);如$.nodeName(document.body,'body') 返回true
each() : 遍历,jQuery中的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() : 去前后空格,源码实现使用的原生es5的trim方法;
makeArray() :类数组转真数组;(传两个参数时内部使用,可以转成特殊的对象形式,第二个参数需要有length属性),
内部调用了$.merge();
inArray() :数组版indexOf(),内部调用原生indexOf;
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 : 1 唯一标识符(内部),可以累加,用于后面的事件操作;
proxy() :改this指向,类似es5的bind,可以传参,内部调用的是apply;
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() : 当前时间;Date.now()
swap() : css交换,使jQuery可以获得隐藏元素的值(display:none)
原理:
让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; },
remove 相对于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 :查看list中是否包含传入的fn
has: function( fn ) {//查看list中是否包含传入的fn return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); },
empty : list = [ ] firingLength = 0;
disable :禁止所有操作 list = stack = memory = undefined
disabled :return !list;
lock : 只锁住后续的fire, 只将stack = undefined
locked 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)
fired 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 :功能检测
jQuery解决兼容问题通过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():
事件源码较多较复杂,先整理一下思路:
从上图看出:平时我们使用的.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) }
下面来看看兼容处理的方法fix: 它又分为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(): 基本使用$('ul').find('li') :即找到ul下的li并对li进行后续操作,内部调用sizzle;
is(): 基本使用为$('div').is('.box') :即判断div中有没有.box的属性,有返回true,没有返回false;内部调用winnow;
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'} , function(a,b,c){})其中回调的参数里,第一个是返回的内容,第二个是状态,第三个是jQuery版的xhr(XMLHttpRequest) 。红色部分为传参;
$.get() :基本使用为$.get('1data.html' ,{} ,function(){},'json')
$.post():基本使用为$.post('1data.html' ,{} ,function(){},'json')
$.getJSON(): 基本使用为$.getJSON(‘1data.php’,function(){}),只会获取JSON类型数据,支持JSONP,如$.getJSON('2data.php?callback=?',function(a){console.log(a}) a即是返回的{}数据
$.getScirpt(): 基本使用为$.getScirpt(‘1data.php’,function(){}),只会获取scirpt类型数据,一般用于按需加载JS;
全局事件: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值