入門前端也一年了,從來沒有仔細(xì)看過jquery的源碼,最近一直在搞angular4,抽點(diǎn)時(shí)間寫下這個(gè)
(function (global,factory){
})(typeof window != 'undefined' ? window : this , function(window,noGlobal){
})
這是jquery的入口,一個(gè)自執(zhí)行函數(shù),相信有基礎(chǔ)的都懂。
(function (global,factory){
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
})(typeof window != 'undefined' ? window : this , function(window,noGlobal){
})
// For environments that do not have a window with a document
// (such as Node.js), expose a factory as module.exports.
解釋一下,如果是node環(huán)境,沒有window對(duì)象和document就用commonjs,利用module.exports導(dǎo)出模塊
接下來大家比較關(guān)注應(yīng)該是這個(gè)factory工廠函數(shù),我們來看一下里面的內(nèi)容
function(window,noGlobal){
var version = "3.2.1",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
},
}
裝個(gè)逼,我用的是最新的Jq(3.2.1)
看到了關(guān)鍵字Jquery,這個(gè)方法一看就是獲取dom節(jié)點(diǎn)的,有2個(gè)參數(shù):
1.selector:選擇器 可以是字符串 正則 等一系列的東東
2.context:上下文 這個(gè)選擇器是在那個(gè)節(jié)點(diǎn)下面找到的,默認(rèn)是body
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
},
var init = jQuery.fn.init = function( selector, context, root ) {
};
init.prototype = jQuery.fn;
jQuery.fn = jQuery.prototype = {
}
這段代碼如果原型鏈沒有過關(guān)的同學(xué)看起來比較費(fèi)勁,簡(jiǎn)單解釋一下吧
init.prototype = jQuery.fn = jQuery.prototype 這個(gè)連等都看的懂吧
然后 init = jQuery.fn.init 那么
new jQuery.fn.init() == new init() == new Jquery()
其實(shí)這里大費(fèi)周章 new jQuery.fn.init( selector, context ) 就是返回Jquery一個(gè)實(shí)例而已
接下來我們比較關(guān)心的是 jQuery.fn.init = function( selector, context, root ) {
};里面的代碼
var rootjQuery,
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
if ( !selector ) {
return this;
}
root = root || rootjQuery;
// Handle HTML strings
if ( typeof selector === "string" ) {
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
this[ 0 ] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
} else if ( jQuery.isFunction( selector ) ) {
return root.ready !== undefined ?
root.ready( selector ) :
selector( jQuery );
}
return jQuery.makeArray( selector, this ); //把獲得的dom節(jié)點(diǎn)轉(zhuǎn)成數(shù)組
};
可以看得出來源碼里面獲得節(jié)點(diǎn)分3種:
1.selector 是字符串類型的
2.selector 是dom節(jié)點(diǎn)類型
3.selector 是函數(shù)類型
這里重點(diǎn)講解一下字符串是如何處理的
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ),
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;
if ( typeof selector === "string" ) {
if ( selector[ 0 ] === "<" &&
selector[ selector.length - 1 ] === ">" &&
selector.length >= 3 ) {
// 以html標(biāo)簽的形式
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if ( match && ( match[ 1 ] || !context ) ) {
// HANDLE: $(html) -> $(array)
if ( match[ 1 ] ) {
context = context instanceof jQuery ? context[ 0 ] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge( this, jQuery.parseHTML(
match[ 1 ],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
return this;
// HANDLE: $(#id) 處理id選擇器
} else {
elem = document.getElementById( match[ 2 ] );
if ( elem ) {
// Inject the element directly into the jQuery object
this[ 0 ] = elem;
this.length = 1;
}
return this;
}
// HANDLE: $(expr, $(...)) 利用內(nèi)部的find方法尋找節(jié)點(diǎn)
} else if ( !context || context.jquery ) {
return ( context || root ).find( selector );
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
}
這一段看起來確實(shí)費(fèi)勁,關(guān)于js里面強(qiáng)大的exec正則用法,大家自己可以研究
context = context instanceof jQuery ? context[ 0 ] : context; 判斷context是否是jquery實(shí)例
下面來看jquery的原型里面有哪些代碼
var version = "3.2.1",
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
length: 0,
toArray: function() {
return slice.call( this );
},
get: function( num ){
if ( num == null ) {
return slice.call( this );
}
return num < 0 ? this[ num + this.length ] : this[ num ];
},
pushStack: function( elems ) {
var ret = jQuery.merge( this.constructor(), elems );
ret.prevObject = this;
return ret;
},
each: function( callback ) {
return jQuery.each( this, callback );
},
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 ] ] : [] );
},
end: function() {
return this.prevObject || this.constructor();
},
push: push,
sort: arr.sort,
splice: arr.splice
// 這里的push sort splice就是數(shù)組的方法
};
看到了吧 這里涵蓋了所有jquery對(duì)象的方法
1.我們要把一個(gè)jq Dom對(duì)象轉(zhuǎn)成數(shù)組,方便遍歷,可以 $('').toArray()
2.獲取節(jié)點(diǎn)中某一個(gè)可以用 $('****').get(index)
3.注意pushStack是一個(gè)很重要的函數(shù),slice函數(shù)就用到了他
4.可以通過$('').slice(index) 截取部分dom元素
5.each map等函數(shù)在這里也能看到。
難道強(qiáng)大的Jquery原型上就這些方法,當(dāng)然不是,我們接著看
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
//大家記得這個(gè)函數(shù)嗎 $.extend(true,obj1,obj2,obj3) 第一個(gè)參數(shù)為true表示深克隆
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[ i ] || {};
i++;
}
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
// 當(dāng)沒有傳遞bool值時(shí) 參數(shù)只有一個(gè)時(shí) 也就是$.fn.extend({})
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
if ( ( options = arguments[ i ] ) != null ) {
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
if ( target === copy ) {
continue;
}
// 這就是深克隆的情況
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && Array.isArray( src ) ? src : [];
} else {
clone = src && jQuery.isPlainObject( src ) ? src : {};
}
target[ name ] = jQuery.extend( deep, clone, copy );
// 這種情況就實(shí)現(xiàn)了jquery原型方法的拓展
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
return target;
};
有了以上代碼,我們這樣玩一哈
$.fn.extend({
say:function(){
console.log(this.nodeName)
},
hasChild:function(ele){
return $('ele this').length > 0
}
})
上面2個(gè)方法適用于任何jq對(duì)象,每個(gè)jq對(duì)象都有say方法,打印自己的nodeType,還有個(gè)hasChild方法判斷是否有某個(gè)子節(jié)點(diǎn)。
那我們來看看jquery源碼里面對(duì)原型上作了哪些擴(kuò)展
jQuery.fn.extend( {
find: function( selector ) {
var i, ret,
len = this.length,
self = this;
if ( typeof selector !== "string" ) {
return this.pushStack( jQuery( selector ).filter( function() {
for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( self[ i ], this ) ) {
return true;
}
}
} ) );
}
ret = this.pushStack( [] );
for ( i = 0; i < len; i++ ) {
jQuery.find( selector, self[ i ], ret );
}
return len > 1 ? jQuery.uniqueSort( ret ) : ret;
},
filter: function( selector ) {
return this.pushStack( winnow( this, selector || [], false ) );
},
not: function( selector ) {
return this.pushStack( winnow( this, selector || [], true ) );
},
is: function( selector ) {
return !!winnow(
this,
typeof selector === "string" && rneedsContext.test( selector ) ?
jQuery( selector ) :
selector || [],
false
).length;
},
addBack: function( selector ) {
return this.add( selector == null ?
this.prevObject : this.prevObject.filter( selector )
);
}
} );
是不是看到了很多熟悉的jq方法,然而這些方法是被擴(kuò)展進(jìn)去的
注意find()方法是遞歸查找,會(huì)一直找下去,效率并不高。
addBack()方法的源碼就在這
<ul>
<li>list item 1</li>
<li>list item 2</li>
<li class="third-item">list item 3</li>
<li>list item 4</li>
<li>list item 5</li>
</ul>
$( "li.third-item" ).nextAll().addBack()
.css( "background-color", "red" );
這里就不再贅述了,大家可以到源碼中自行查找。
下面說一個(gè)經(jīng)常見得工具方法 $.extend()
jQuery.extend( {
isFunction: function( obj ) {
return jQuery.type( obj ) === "function";
},
isWindow: function( obj ) {
return obj != null && obj === obj.window;
},
isNumeric: function( obj ) {
// As of jQuery 3.0, isNumeric is limited to
// strings and numbers (primitives or objects)
// that can be coerced to finite numbers (gh-2662)
var type = jQuery.type( obj );
return ( type === "number" || type === "string" ) &&
// parseFloat NaNs numeric-cast false positives ("")
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
// subtraction forces infinities to NaN
!isNaN( obj - parseFloat( obj ) );
},
isPlainObject: function( obj ) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
return false;
}
proto = getProto( obj );
// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
},
each: function( obj, callback ) {
var length, i = 0;
if ( isArrayLike( obj ) ) {
length = obj.length;
for ( ; i < length; i++ ) {
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
break;
}
}
} else {
for ( i in obj ) {
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
break;
}
}
}
return obj;
}
我們?cè)谌粘i_發(fā)中是不是會(huì)常用到上述工具函數(shù),判斷對(duì)象類型等
最常用的還是$.each(obj,function()) 這個(gè)方法了吧
也可以自定義一個(gè)工具函數(shù)來供自己使用
$.extend({
zhishu:function(num){
var count = 0,flag = true;
while(num--){
for(var i =0; i<Math.sqrt(num);i++){
if(num%i == 0) {
flag = !flag
break
}
count++
}
}
}
})
以上就寫了方法查找某個(gè)數(shù)范圍內(nèi)的質(zhì)數(shù)
有一點(diǎn)也許大家還在像jquery和$是怎么聯(lián)系到一起的。。。
//當(dāng)有一個(gè)也是以$開頭的庫(kù)與jquery沖突時(shí)
var _jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$;
//這是獲取之前定義的原始$
jQuery.noConflict = function( deep ) {
//如果現(xiàn)在定義的Jquery與之前不一樣,那么之前的就得寫成_$ _JQuery
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
};
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}
// 在非全局情況下 Jquery $ 都有jq原型
舉個(gè)栗子
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery.noConflict demo</title>
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<div id="log">
<h3>Before $.noConflict(true)</h3>
</div>
<script src="https://code.jquery.com/jquery-1.6.2.js"></script>
<script>
var $log = $( "#log" );
$log.append( "2nd loaded jQuery version ($): " + $.fn.jquery + "<br>" );
// Restore globally scoped jQuery variables to the first version loaded
// (the newer version)
jq162 = jQuery.noConflict( true );
$log.append( "<h3>After $.noConflict(true)</h3>" );
$log.append( "1st loaded jQuery version ($): " + $.fn.jquery + "<br>" );
$log.append( "2nd loaded jQuery version (jq162): " + jq162.fn.jquery + "<br>" );
</script>
</body>
</html>
Before $.noConflict(true)
2nd loaded jQuery version ($): 1.6.2
After $.noConflict(true)
1st loaded jQuery version ($): 1.10.2
2nd loaded jQuery version (jq162): 1.6.2
今天的內(nèi)容就到這里,下回我們講解