第一天:~。~ 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():
事件源码较多较复杂,先整理一下思路:
从上图看出:平时我们使用的.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值