前言
分析源码的过程总是成就感与挫败感相伴的,尤其是jquery
这样庞大且晦涩难懂的源码,本文承接上一篇:浅析jQuery整体框架与实现(上),继续做更细致些的分析,上篇文章距离现在已经大半年了,本来是只打算写一篇,做个样子的,但看到那么多点赞和收藏的,于是架不住大家的热情,就偷偷把标题改了下,预示着还有下文。上篇文章分析的是jquery-1.7.1
,这次分析下最新版的jquery-2.1.4吧
(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
var arr = [];//
var slice = arr.slice;
var concat = arr.concat;
var push = arr.push;
var indexOf = arr.indexOf;
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var support = {};
})
以上代码主要是让后续代码变得更加简洁,主要是数组和对象的一些原生方法。
class2type
初始化后的结构如下:
{
"[object Array]": "array"
"[object Boolean]": "boolean"
"[object Date]": "date"
"[object Function]": "function"
"[object Number]": "number"
"[object Object]": "object"
"[object RegExp]": "regexp"
"[object String]": "string"
}
构建一个空数组,该数组具有length
,prototype
和constructor
属性:
<!--length属性、prototype属性、constructor属性-->
<script type="text/javascript">
var arr = [];//字面量不会调用Array构造函数
alert(arr.length);//o
alert(Array.prototype.push(1));//1
alert(arr.constructor);//function Array() {[native code]}
</script>
73行开始:
var jQuery = function( selector, context ) { //定义类
return new jQuery.fn.init( selector, context );//返回选择器的实例
},
当我们调用jQuery
的时候会返回new init()
的结果而不是直接new jQuery()
原型属性和方法
原型属性和方法从92行开始源码如下:
// 原型属性和方法
jQuery.fn = jQuery.prototype = {
jquery: version,//版本号
constructor: jQuery,//指向构造函数jQuery
selector: "",//从一个空的选择器开始
length: 0,//指定默认的jQuery对象的长度为0
toArray: function() {
return slice.call( this );
},
110 get: function( num ) {
111 return num != null ?
112
113
114 ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
115
116
117 slice.call( this );
118 },
122 pushStack: function( elems ) {
123
124
125 var ret = jQuery.merge( this.constructor(), elems );
126
127
128 ret.prevObject = this;
129 ret.context = this.context;
130
131 return ret;
},
each: function( callback, args ) {
return jQuery.each( this, callback, args );
},
map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
},
slice: function() {
return this.pushStack( slice.apply( this, arguments ) );
},
first: function() {
return this.eq( 0 );
},
last: function() {
return this.eq( -1 );
},
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},
push: push,
sort: arr.sort,
splice: arr.splice
};
.toArray()
.toArray()
将当前jQuery
对象转换成真正的数组,执行时通过方法call()
和apply()
指定方法执行的环境,即关键字this
所引用的对象.
slice.call( this ) == [].slice.call(this);
也就是说该方法在执行时,会将原本属于数组对象的slice
方法交给当前执行环境(this
所引用的对象)的对象去处理。
看下面一个实例:
function add() {
// this.name = "math";
alert(this.name);
}
//使用new function 定义JavaScript中的用户自定义对象
var sub = new function (){
this.name = "English";
alert(this.name);//English
};
add.call(sub);//English,sub调用了add方法,并将sub对象替换为add对象
.get
.get([index]):
中括号表示可选。该方法返回当前jQuery
对象中指定位置的元素或包含了全部元素的数组。如果指定参数index
,则返回一个单独的元素;参数index
从0开始,并且支持负数,负数表示从末尾开始算起。
114
行首先判断num
是否小于0,则用length+num
重新计算下标,然后用[]
获取指定位置的元素。如果num
大于等于0,则直接返回指定位置的元素。否则调用[].slice.call(this)
返回所有元素,存入空数组里。
pushStack
pushStack(elems)
:该原型方法创建一个空的jQuery
对象,然后把DOM元素集合放入这个jQuery
对象中,并保留对当前jQuery
对象的引用。
125行:首先构造一个新的空jQuery
对象ret,this.constructor
指向构造函数jQuery()
,然后把参数elems
合并到this.constructor
并赋给新的jQuery
对象ret
131行:最后返回新的jQuery
对象ret
.
关于merge
方法:
merge: function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
for ( ; j < len; j++ ) {
first[ i++ ] = second[ j ];
}
first.length = i;
return first;
},
.end()
.end():
该方法结束当前链条中最近的筛选操作,并将匹配元素集合还原为之前的状态。相关代码如下:
166 end: function() {
167 return this.prevObject || this.constructor(null);
168 },
167行:返回前一个jQuery
对象,如果属性prevObject
不存在,则构建一个空的jQuery
对象返回。
.pushStack()
方法用于入栈,.end()
方法用于出栈。
.slice()
.slice():
该方法先使用[].slice
从当前jQuery
对象中获取指定范围的子集,再调用方法.pushStack()
把子集转换成jQuery
对象
调用关系
在javascript
的世界中一共有四种上下文调用方式:方法调用模式
、函数调用模式
、构造器调用模式
、apply调用模式
:
☑ jQuery.extend调用的时候上下文指向的是jQuery构造器
☑ jQuery.fn.extend调用的时候上下文指向的是jQuery构造器的实例对象了
DOM遍历 Traversing
DOM
遍历有如下3个核心函数:
| jQuery.dir( elem, dir, until ) | 从一个元素出发,迭代检索某个方向上的所有元素并记录,直到遇到document对象或遇到until匹配的元素 |
| ------------- |:-------------:|
| jQuery.nth( cur, result, dir, elem ) | 从一个元素出发,迭代检索某个方向上的第N个元素|
| jQuery.sibling( n, elem ) |元素n的所有后续兄弟元素,包含n,不包含elem
其中elem
是dom
对象, dir
是迭代方向,可选值:parentNode 、nextSibling、 previousSibling
,until
是截至条件
jQuery.dir
的整个运行过程是循环查找elem
的dir
的属性, 直到没有后续元素 或者找到了document
根节点(elem.nodeType !== 9
) , 最后再将所有查找到的元素放到数组中返回。
jQuery.each({
//父元素
parent: function( elem ) {
var parent = elem.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
//祖先元素
parents: function( elem ) {
return jQuery.dir( elem, "parentNode" );//检索所有父元素,直至document
},
parentsUntil: function( elem, i, until ) {
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 ) {
return jQuery.dir( elem, "nextSibling", until );
},
prevUntil: function( elem, i, until ) {
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 ) {
return elem.contentDocument || jQuery.merge( [], elem.childNodes );
}
},function( name, fn ) {
// 公开方法,模板函数
jQuery.fn[ name ] = function( until, selector ) { ... };
});
jQuery.each( object, callback ) ;//callback(键,值)
$.each(array,callback);//callback(索引,索引值)
jquery.extend()
jQuery.extend({
dir: function( elem, dir, until ) {
var matched = [],
truncate = until !== undefined;
while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
if ( elem.nodeType === 1 ) {
if ( truncate && jQuery( elem ).is( until ) ) {
break;
}
matched.push( elem );
}
}
return matched;
},
////返回n的所有兄弟节点,不包括elem
sibling: function( n, elem ) {
var matched = [];//定义空数组,用来保存查找到的元素
for ( ; n; n = n.nextSibling ) {//语句1可为空
if ( n.nodeType === 1 && n !== elem ) {
matched.push( n );
}
}
return matched;
}
});
API的解释
parent API
返回父节点并且当父节点的节点类型不为DocumentFragment
(不属于文档树,继承的 parentNode
属性总是 null
) 节点的话,就直接返回父节点,否则就返回null
。
parents API
通过jQuery.dir
循环查找elem
元素的所有父节点(parentNode
),直至document,然后将其返回。
sibling
(不同于siblings API
)有两个参数, n
是起始dom
对象, elem
是结束dom
对象。它通过不断寻找nextSibling
, 直到找到非element
的对象(n.nodeType === 1)
或者找到了elem
为止,,然后将所有查找到的兄弟元素放到数组中返回。值得注意的是,sibling
并不是jquery
的一个对外提供的API
,而是内部使用的,siblings
才是对外提供的API
next API
通过sibling
方法来获取当前节点的下个元素节点,同理prev API
。
siblings API
利用sibling
方法,先通过父元素的第一个子元素,然后不断往下找下一个紧邻元素,判断剔除自己。
children API
也是利用sibling
方法,来引用所有子元素。
DOM操作
jQuery
针对DOM
操作的插入方法有如下10种:
append、prepend、before、after、replaceWith
appendTo、prependTo、insertBefore、insertAfter、replaceAll
核心函数domManip
.domManip():第一个参数是arguments,第二个参数是回调函数
args
:待插入的DOM
元素或HTML
代码callback
回调函数,执行格式为callback.call
(目标元素即上下文, 待插入文档碎片/单个DOM元素 )
将html转化成dom:
if ( l ) {
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
first = fragment.firstChild;
if ( fragment.childNodes.length === 1 ) {
fragment = first;
}
}
append和prepend
相关源码(5204行开始)如下:
jQuery.fn.extend({
append: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
});
},
prepend: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.insertBefore( elem, target.firstChild );
}
});
},
})
jQuery.fn.extend()
函数用于为jQuery
扩展一个或多个实例属性和方法(主要用于扩展方法)。
append API
是被选元素的结尾(仍然在内部)插入指定内容。
text()和html()
text: function( value ) {
return access( this, function( value ) {
return value === undefined ?
jQuery.text( this ) :
this.empty().each(function() {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
this.textContent = value;
}
});
}, null, value, arguments.length );
},
html API
有点长,这里就不列出来了。access()
方法用来判断key
和value
值的不同类型。并为.attr(),.prop,.css()
提供支持
队列 Queue
我们知道,队列的特点是先进先出,即最先插入的元素最先被删除,有别于栈的先进后出。队列在jQuery
源码中,主要用于动画队列中。从6713行处开始到6730行结束的源码如下:
animate: function( prop, speed, easing, callback ) {
var empty = jQuery.isEmptyObject( prop ),//empty是布尔值
optall = jQuery.speed( speed, easing, callback ),//修正参数
doAnimation = function() {
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
if ( empty || data_priv.get( this, "finish" ) ) {
anim.stop( true );
}
};
doAnimation.finish = doAnimation;
return empty || optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
},
其中isEmptyObject()
方法的源码如下:
isEmptyObject: function( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
},
从源码中可见,通过for…in
语句遍历对象属性并返回布尔值。
console.log(jQuery.isEmptyObject({}));//true
为什么要引入队列?
我们可能已习惯于线性地编写代码,而事实上,对于js编程,比如setTimeout,CSS3 Transition/Animation,ajax,dom
的绘制,postmessage,Web Database
等等,大量异步操作所带来的回调函数会把我们的代码逻辑弄得支离破碎的。
所以,引入队列可以被认为是允许一系列函数被异步地调用而不会阻塞程序
类型检测
jQuery
类型检测的方法主要是如下几个:
jQuery.isFunction( obj )
jQuery.isArray( obj )
jQuery.isWindow( obj )
jQuery.isNumeric( value )
jQuery.type( obj )
jQuery.isPlainObject( object )
jQuery.isEmptyObject( object )
jQuery.type
type: function( obj ) {
if ( obj == null ) {
return obj + "";
}
305 return typeof obj === "object" || typeof obj === "function" ?
306 class2type[ toString.call(obj) ] || "object" :
307 typeof obj;//
308 },
jQuery.type( obj )
判断参数类型,如果参数是undefined
祸null
,则返回"undefined
"或"null
";如果参数是js
内部对象,则返回对应的字符串名称。
305~307行:首先用typeof
检测参数数据类型,如果是Object
或function
类型,则利用Object
的原型方法toString()
获取参数obj
的字符串表示,或直接一律返回“Object”,否则直接typeof
参数的数据类型返回
console.log(typeof {});//"object",带双引号
Object.prototype.toString.call(true);//"[object Boolean]"
{}.toString === Object.prototype.toString;//true
其中,jQuery.isFunction(obj)
用于判断传入的参数是否是函数 ,该方法依赖于jQuery.type(obj)
,该方法通过返回是否是 "function
"来实现的
jQuery.isWindow
261~263行源码如下:
isWindow: function( obj ) {
return obj != null && obj === obj.window;
},
isWindow(obj)
是用来判断传入参数是不是window
对象,首先判断参数是否不为null
然后利用等性运算符判断参数对其自身的引用
$.isWindow(window);//true
$.isWindow(document);//false
$.isWindow(iframe);//true
jQuery.isNumeric
265~271行源码如下:
isNumeric: function( obj ) {
return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
},
isNumeric(obj)
用来判断参数是否是数字,该函数和isWindow()
一样属于全局jQuery
对象。
jQuery.isFunction
255~257行源码如下:
isFunction: function( obj ) {
return jQuery.type(obj) === "function";
},
jQuery事件
主要源码结构如下:
jQuery.event = {
global : {},
//绑定事件句柄
add : function(){},
//移除
remove : function(){},
// 触发
trigger : function(){},
//分派(执行)事件处理函数
dispatch : function(){}
// 执行
handlers : function(){},
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
fixHooks: {},
keyHooks: {},
mouseHooks: {},
//fix修正event对象
fix : {},
special: {
load: {},
focus: {},
blur: {},
click: {},
beforeunload :{},
}
simulate: {}
}
jQuery事件对象原型
jQuery.Event.prototype = {
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse,
//阻止默认浏览器默认行为
preventDefault: function() {
var e = this.originalEvent;
this.isDefaultPrevented = returnTrue;
if ( e && e.preventDefault ) {
e.preventDefault();
}
},
//阻止事件传播
stopPropagation: function() {
var e = this.originalEvent;
this.isPropagationStopped = returnTrue;
if ( e && e.stopPropagation ) {
e.stopPropagation();
}
},
//立即停止事件传播
stopImmediatePropagation: function() {
var e = this.originalEvent;
this.isImmediatePropagationStopped = returnTrue;
if ( e && e.stopImmediatePropagation ) {
e.stopImmediatePropagation();
}
this.stopPropagation();
}
};
其他事件
jQuery.fn.extend({
hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
},
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
return this.off( types, null, fn );
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
}
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。