1. 程式人生 > >第五節:JQuery框架源碼簡析(1)

第五節:JQuery框架源碼簡析(1)

err cal content browser active lac rda setting right

(轉自老惠的博客)

JQuery是一個應用廣泛、非常優秀的JavaScript框架,其代碼簡潔而優雅,有很多值得我們學習的地方。這裏僅僅對其代碼結構做一個簡單的分析,方便大家的理解和學習。

我們進行分析、分解的基準版本是jQuery1.7.1。

開始之前,請準備好以下素材和工具:

  1. jQuery源代碼:jquery-1.7.1.js

  2. 文本編輯器:EditPlus,或者你喜歡的

  3. 參考書:《jQuery高級編程》、《jQuery技術內幕:深入解析jQuery架構設計與實現原理》、《JavaScript框架設計》

1、主體結構

jQuery的所有代碼都在一個自調用匿名函數中,該函數構造一個jQuery對象,並初始化jQuery的各個模塊,最後創建了$和jQuery命名空間,實現了框架代碼和其他代碼的隔離。

創建文件jquery.main.js,代碼含義請看註釋。

// 整體結構

(function(window,undefined){

var jQuery = (function(){

var jQuery = function(selector, context){

// 調用init方法創建jQuery對象

return new jQuery.prototype.init(selector, context,rootjQuery);

};

// 臨時變量

var _$ = window.$;

var _jQuery = window.jQuery;

var rootjQuery;

var toString = Object.prototype.toString;

// 初始化jQuery對象

// 。。。

return jQuery;

})();

// 初始化jQuery各個模塊

// 。。。

window.$ = window.jQuery = jQuery;

})(window);

創建測試文件jquery.test.html。

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<script src="jquery.main.js"></script>

<title>jquery demo</title>

</head>

<body>

<script>

alert($);

</script>

</body>

</html>

執行結果:

技術分享

說明jQuery的命名空間$,或者jQuery(alert(jQuery))是個函數,該函數執行後,返回一個jQuery對象。現在我們執行測試代碼$():

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<script src="jquery.main.js"></script>

<title>jquery demo</title>

</head>

<body>

<script>

$();

</script>

</body>

</html>

技術分享

產生錯誤的原因是jQuery.prototype.init(selector, context,rootjQuery)方法不存在。

2、jQuery對象初始化

初始化代碼定義了一些jQuery對象的原型屬性和方法 。我們創建文件jquery.init.js,將jQuery初始化的代碼放在這個文件中。

// 初始化

// 定義了一些jQuery對象的原型屬性和方法

function jQuery_init(jQuery){

jQuery.fn = jQuery.prototype;

jQuery.fn.constructor = jQuery;

// Start with an empty selector

jQuery.fn.selector = "";

// The current version of jQuery being used

jQuery.fn.jquery = "1.7.1";

// The default length of a jQuery object is 0

jQuery.fn.length = 0;

jQuery.fn.init = init;

jQuery.fn.size = size;

jQuery.fn.toArray = toArray;

jQuery.fn.get = get;

jQuery.fn.pushStack = pushStack;

jQuery.fn.each = each;

jQuery.fn.ready = ready;

jQuery.fn.eq = eq;

jQuery.fn.first = first;

jQuery.fn.last = last;

jQuery.fn.slice = slice;

jQuery.fn.map = map;

jQuery.fn.end = end;

// For internal use only.

// Behaves like an Array‘s method, not like a jQuery method.

jQuery.fn.push = Array.prototype.push;

jQuery.fn.sort = [].sort;

jQuery.fn.splice = [].splice;

// jQuery對象初始化,並返回init子對象

function init( selector, context, rootjQuery ) {

// 檢查selector是HTML字符串,還是#id的正則表達式

var quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;

// 檢測 HTML 代碼是否是單獨標簽

// 包含一個分組“ (\w+) ”,該分組中不包含左右尖括號、不能包含屬性、

// 可以自關閉或不關閉;

// “ \1 ”指向匹配的第一個分組“ (\w+) ”

var rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/;

var match, elem, ret, doc;

// selector為空,比如$()

// 0、""、null、false、undefined、NaN 都會判定為 false ,而其他都為 true

if ( !selector ) {

return this;

}

//selector是 DOM 元素,比如$(document.body)

//selector如果是DOM元素,則包含屬性nodeType

if ( selector.nodeType ) {

this.context = this[0] = selector;

this.length = 1;

return this;

}

// 文檔樹中只有一個body元素,所以要對body字符串查找優化

if ( selector === "body" && !context && document.body ) {

this.context = document;

this[0] = document.body;

this.selector = selector;

this.length = 1;

return this;

}

// selector是字符串

if ( typeof selector === "string" ) {

// selector是一個HTML字符串,還是#id

if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {

match = [null, selector, null];

}

else {

match = quickExpr.exec(selector);

// 正則 quickExpr 包含兩個分組,依次匹配HTML代碼和 id。

// 如果匹配成功,則數組match的第一個元素為參數selector,

// 第二個元素為匹配的 HTML代碼或undefined,

// 第三個元素為匹配的id或undefined

}

// 這裏面包含了對match[2]的判斷,完整的表達式應該是

// if ( match && (match[1] || match[2] && !context) )

// 但是,如果match[1]不成立,那麽match[2]必然成立,所以省略

if ( match && (match[1] || !context) ) {

// 處理參數 selector 是 HTML 代碼的情況,先修正 context、 doc,

// 然後用正則 rsingleTag 檢測 HTML 代碼是否是單獨標簽,

// 匹配結果存放在數組 ret 中

if ( match[1] ) {

context = context instanceof jQuery ? context[0] : context;

doc = ( context ? context.ownerDocument || context : document );

// If a single string is passed in and it‘s a single tag

// just do a createElement and skip the rest

ret = rsingleTag.exec( selector );

if ( ret ) {

if ( jQuery.isPlainObject( context ) ) {

selector = [ document.createElement( ret[1] ) ];

jQuery.fn.attr.call( selector, context, true );

} else {

selector = [ doc.createElement( ret[1] ) ];

}

} else {

ret = jQuery.buildFragment( [ match[1] ], [ doc ] );

selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;

}

return jQuery.merge( this, selector );

// HANDLE: $("#id")

} else {

elem = document.getElementById( match[2] );

// Check parentNode to catch when Blackberry 4.6 returns

// nodes that are no longer in the document #6963

if ( elem && elem.parentNode ) {

// Handle the case where IE and Opera return items

// by name instead of ID

if ( elem.id !== match[2] ) {

return rootjQuery.find( selector );

}

// Otherwise, we inject the element directly into the jQuery object

this.length = 1;

this[0] = elem;

}

this.context = document;

this.selector = selector;

return this;

}

// HANDLE: $(expr, $(...))

} else if ( !context || context.jquery ) {

return ( context || rootjQuery ).find( selector );

// HANDLE: $(expr, context)

// (which is just equivalent to: $(context).find(expr)

} else {

return this.constructor( context ).find( selector );

}

// HANDLE: $(function)

// Shortcut for document ready

} else if ( jQuery.isFunction( selector ) ) {

return rootjQuery.ready( selector );

}

if ( selector.selector !== undefined ) {

this.selector = selector.selector;

this.context = selector.context;

}

return jQuery.makeArray( selector, this );

}

// The number of elements contained in the matched element set

function size() {

return this.length;

}

function toArray() {

return slice.call( this, 0 );

}

// Get the Nth element in the matched element set OR

// Get the whole matched element set as a clean array

function get( num ) {

return num == null ?

// Return a ‘clean‘ array

this.toArray() :

// Return just the object

( num < 0 ? this[ this.length + num ] : this[ num ] );

}

// Take an array of elements and push it onto the stack

// (returning the new matched element set)

function pushStack( elems, name, selector ) {

// Build a new jQuery matched element set

var ret = this.constructor();

if ( jQuery.isArray( elems ) ) {

Array.prototype.push.apply( ret, elems );

} else {

jQuery.merge( ret, elems );

}

// Add the old object onto the stack (as a reference)

ret.prevObject = this;

ret.context = this.context;

if ( name === "find" ) {

ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;

} else if ( name ) {

ret.selector = this.selector + "." + name + "(" + selector + ")";

}

// Return the newly-formed element set

return ret;

}

// Execute a callback for every element in the matched set.

// (You can seed the arguments with an array of args, but this is

// only used internally.)

function each( callback, args ) {

return jQuery.each( this, callback, args );

}

function ready( fn ) {

// Attach the listeners

jQuery.bindReady();

// Add the callback

readyList.add( fn );

return this;

}

function eq( i ) {

i = +i;

return i === -1 ?

this.slice( i ) :

this.slice( i, i + 1 );

}

function first(){

return this.eq( 0 );

}

function last() {

return this.eq( -1 );

}

function slice() {

var slice = Array.prototype.slice;

return this.pushStack( slice.apply( this, arguments ),

"slice", slice.call(arguments).join(",") );

}

function map( callback ) {

return this.pushStack( jQuery.map(this, function( elem, i ) {

return callback.call( elem, i, elem );

}));

}

function end() {

return this.prevObject || this.constructor(null);

}

// 這句代碼很重要

// 讓init子對象集成jQuery對象的屬性和方法(通過原型鏈)

jQuery.fn.init.prototype = jQuery.fn;

}

我們發現init方法返回的是init子對象,所以要有這句代碼jQuery.fn.init.prototype = jQuery.fn,從而使init子對象和jQuery對象的屬性和方法集成在一起。

修改文件jquery.main.js,添加對方法jQuery_init的調用。

技術分享

修改jquery.test.html文件,添加對jquery.init.js的引用,並測試。

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<script src="jquery.init.js"></script>

<script src="jquery.main.js"></script>

<title>jquery demo</title>

</head>

<body>

<script>

var o = $();

alert(o.jquery);

</script>

</body>

</html>

測試結果說明,我們成功創建了jQuery對象,並顯示了它的版本號屬性:

技術分享

3、extend方法定義

extend方法實現jQuery的功能擴展,用於合並兩個或多個對象的靜態屬性和靜態方法到第一個對象。我們把extend方法定義的代碼放在文件jquery.extend.define.js中。

// extend方法定義,該方法實現功能擴展

// 用於合並兩個或多個對象的屬性和方法到第一個對象(靜態屬性和靜態方法)

// 參數 [deep], target, object1 [, objectN]

// deep 是可選的布爾值,表示是否進行深度合並(即遞歸合並)。默認不遞歸。

// 參數 target 是目標對象;參數 object1 和 objectN 是源對象,包含了待合並的屬性。如果

// 提供了兩個或更多的對象,所有源對象的屬性將會合並到目標對象;如果僅提供一個對象,

// 意味著參數 target 被忽略, jQuery 或 jQuery.fn 被當作目標對象,通過這種方式可以在 jQuery

// 或 jQuery.fn 上添加新的屬性和方法, jQuery 的其他模塊大都是這麽實現的

function jQuery_extend_defined(jQuery){

jQuery.extend = jQuery.fn.extend = function() {

var options, name, src, copy, copyIsArray, clone,

target = arguments[0] || {},

i = 1,

length = arguments.length,

deep = false;

// Handle a deep copy situation

if ( typeof target === "boolean" ) {

deep = target;

target = arguments[1] || {};

// skip the boolean and the target

i = 2;

}

// Handle case when target is a string or something (possible in deep copy)

if ( typeof target !== "object" && !jQuery.isFunction(target) ) {

target = {};

}

// extend jQuery itself if only one argument is passed

if ( length === i ) {

target = this;

--i;

}

for ( ; i < length; i++ ) {

// Only deal with non-null/undefined values

if ( (options = arguments[ i ]) != null ) {

// Extend the base object

for ( name in options ) {

src = target[ name ];

copy = options[ name ];

// Prevent never-ending loop

if ( target === copy ) {

continue;

}

// Recurse if we‘re merging plain objects or arrays

if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {

if ( copyIsArray ) {

copyIsArray = false;

clone = src && jQuery.isArray(src) ? src : [];

} else {

clone = src && jQuery.isPlainObject(src) ? src : {};

}

// Never move original objects, clone them

target[ name ] = jQuery.extend( deep, clone, copy );

// Don‘t bring in undefined values

} else if ( copy !== undefined ) {

target[ name ] = copy;

}

}

}

}

// Return the modified object

return target;

};

}

修改文件jquery.main.js,添加對方法jQuery_extend_defined的調用。

技術分享

修改jquery.test.html文件,添加對jquery.extend.define.js的引用,並測試。

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<script src="jquery.init.js"></script>

<script src="jquery.extend.define.js"></script>

<script src="jquery.main.js"></script>

<title>jquery demo</title>

</head>

<body>

<script>

$.extend({

test_func: function() {

alert("擴展了一個測試方法");

return jQuery;

}

});

$.test_func();

</script>

</body>

</html>

我們發現通過jQuery對象的extend方法,我們成功地給jQuery對象擴展了一個測試方法:

技術分享

4、核心方法

我們把jQuery對象核心方法定義的代碼放在文件jquery.extend.core.js中,這些方法都是通過jQuery對象的extend方法實現的。

// jQuery對象的核心方法

function jQuery_extend_core(jQuery,_$,_jQuery,toString){

var trim = String.prototype.trim;

var class2type = {};

jQuery.extend({

noConflict: function( deep ) {

if ( window.$ === jQuery ) {

window.$ = _$;

}

if ( deep && window.jQuery === jQuery ) {

window.jQuery = _jQuery;

}

return jQuery;

},

// Is the DOM ready to be used? Set to true once it occurs.

isReady: false,

// A counter to track how many items to wait for before

// the ready event fires. See #6781

readyWait: 1,

// Hold (or release) the ready event

holdReady: function( hold ) {

if ( hold ) {

jQuery.readyWait++;

} else {

jQuery.ready( true );

}

},

// Handle when the DOM is ready

ready: function( wait ) {

// Either a released hold or an DOMready/load event and not yet ready

if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {

// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).

if ( !document.body ) {

return setTimeout( jQuery.ready, 1 );

}

// Remember that the DOM is ready

jQuery.isReady = true;

// If a normal DOM Ready event fired, decrement, and wait if need be

if ( wait !== true && --jQuery.readyWait > 0 ) {

return;

}

// If there are functions bound, to execute

readyList.fireWith( document, [ jQuery ] );

// Trigger any bound ready events

if ( jQuery.fn.trigger ) {

jQuery( document ).trigger( "ready" ).off( "ready" );

}

}

},

bindReady: function() {

if ( readyList ) {

return;

}

readyList = jQuery.Callbacks( "once memory" );

// Catch cases where $(document).ready() is called after the

// browser event has already occurred.

if ( document.readyState === "complete" ) {

// Handle it asynchronously to allow scripts the opportunity to delay ready

return setTimeout( jQuery.ready, 1 );

}

// Mozilla, Opera and webkit nightlies currently support this event

if ( document.addEventListener ) {

// Use the handy event callback

document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

// A fallback to window. that will always work

window.addEventListener( "load", jQuery.ready, false );

// If IE event model is used

} else if ( document.attachEvent ) {

// ensure firing before onload,

// maybe late but safe also for iframes

document.attachEvent( "onreadystatechange", DOMContentLoaded );

// A fallback to window. that will always work

window.attachEvent( " jQuery.ready );

// If IE and not a frame

// continually check to see if the document is ready

var toplevel = false;

try {

toplevel = window.frameElement == null;

} catch(e) {}

if ( document.documentElement.doScroll && toplevel ) {

doScrollCheck();

}

}

},

// See test/unit/core.js for details concerning isFunction.

// Since version 1.3, DOM methods and functions like alert

// aren‘t supported. They return false on IE (#2968).

isFunction: function( obj ) {

return jQuery.type(obj) === "function";

},

isArray: Array.isArray || function( obj ) {

return jQuery.type(obj) === "array";

},

// A crude way of determining if an object is a window

isWindow: function( obj ) {

return obj && typeof obj === "object" && "setInterval" in obj;

},

isNumeric: function( obj ) {

return !isNaN( parseFloat(obj) ) && isFinite( obj );

},

type: function( obj ) {

return obj == null ?

String( obj ) :

class2type[ toString.call(obj) ] || "object";

},

isPlainObject: function( obj ) {

// Must be an Object.

// Because of IE, we also have to check the presence of the constructor property.

// Make sure that DOM nodes and window objects don‘t pass through, as well

if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {

return false;

}

try {

// Not own constructor property must be Object

if ( obj.constructor &&

!hasOwn.call(obj, "constructor") &&

!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {

return false;

}

} catch ( e ) {

// IE8,9 Will throw exceptions on certain host objects #9897

return false;

}

// Own properties are enumerated firstly, so to speed up,

// if last one is own, then all properties are own.

var key;

for ( key in obj ) {}

return key === undefined || hasOwn.call( obj, key );

},

isEmptyObject: function( obj ) {

for ( var name in obj ) {

return false;

}

return true;

},

error: function( msg ) {

throw new Error( msg );

},

parseJSON: function( data ) {

if ( typeof data !== "string" || !data ) {

return null;

}

// Make sure leading/trailing whitespace is removed (IE can‘t handle it)

data = jQuery.trim( data );

// Attempt to parse using the native JSON parser first

if ( window.JSON && window.JSON.parse ) {

return window.JSON.parse( data );

}

// Make sure the incoming data is actual JSON

// Logic borrowed from http://json.org/json2.js

if ( rvalidchars.test( data.replace( rvalidescape, "@" )

.replace( rvalidtokens, "]" )

.replace( rvalidbraces, "")) ) {

return ( new Function( "return " + data ) )();

}

jQuery.error( "Invalid JSON: " + data );

},

// Cross-browser xml parsing

parseXML: function( data ) {

var xml, tmp;

try {

if ( window.DOMParser ) { // Standard

tmp = new DOMParser();

xml = tmp.parseFromString( data , "text/xml" );

} else { // IE

xml = new ActiveXObject( "Microsoft.XMLDOM" );

xml.async = "false";

xml.loadXML( data );

}

} catch( e ) {

xml = undefined;

}

if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {

jQuery.error( "Invalid XML: " + data );

}

return xml;

},

noop: function() {},

// Evaluates a script in a global context

// Workarounds based on findings by Jim Driscoll

// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context

globalEval: function( data ) {

if ( data && rnotwhite.test( data ) ) {

// We use execScript on Internet Explorer

// We use an anonymous function so that context is window

// rather than jQuery in Firefox

( window.execScript || function( data ) {

window[ "eval" ].call( window, data );

} )( data );

}

},

// Convert dashed to camelCase; used by the css and data modules

// Microsoft forgot to hump their vendor prefix (#9572)

camelCase: function( string ) {

return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );

},

nodeName: function( elem, name ) {

return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();

},

// args is for internal usage only

each: function( object, callback, args ) {

var name, i = 0,

length = object.length,

isObj = length === undefined || jQuery.isFunction( object );

if ( args ) {

if ( isObj ) {

for ( name in object ) {

if ( callback.apply( object[ name ], args ) === false ) {

break;

}

}

} else {

for ( ; i < length; ) {

if ( callback.apply( object[ i++ ], args ) === false ) {

break;

}

}

}

// A special, fast, case for the most common use of each

} else {

if ( isObj ) {

for ( name in object ) {

if ( callback.call( object[ name ], name, object[ name ] ) === false ) {

break;

}

}

} else {

for ( ; i < length; ) {

if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {

break;

}

}

}

}

return object;

},

// Use native String.trim function wherever possible

trim: trim ?

function( text ) {

return text == null ?

"" :

trim.call( text );

} :

// Otherwise use our own trimming functionality

function( text ) {

return text == null ?

"" :

text.toString().replace( trimLeft, "" ).replace( trimRight, "" );

},

// results is for internal usage only

makeArray: function( array, results ) {

var ret = results || [];

if ( array != null ) {

// The window, strings (and functions) also have ‘length‘

// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930

var type = jQuery.type( array );

if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {

push.call( ret, array );

} else {

jQuery.merge( ret, array );

}

}

return ret;

},

inArray: function( elem, array, i ) {

var len;

if ( array ) {

if ( indexOf ) {

return indexOf.call( array, elem, i );

}

len = array.length;

i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

for ( ; i < len; i++ ) {

// Skip accessing in sparse arrays

if ( i in array && array[ i ] === elem ) {

return i;

}

}

}

return -1;

},

merge: function( first, second ) {

var i = first.length,

j = 0;

if ( typeof second.length === "number" ) {

for ( var l = second.length; j < l; j++ ) {

first[ i++ ] = second[ j ];

}

} else {

while ( second[j] !== undefined ) {

first[ i++ ] = second[ j++ ];

}

}

first.length = i;

return first;

},

grep: function( elems, callback, inv ) {

var ret = [], retVal;

inv = !!inv;

// Go through the array, only saving the items

// that pass the validator function

for ( var i = 0, length = elems.length; i < length; i++ ) {

retVal = !!callback( elems[ i ], i );

if ( inv !== retVal ) {

ret.push( elems[ i ] );

}

}

return ret;

},

// arg is for internal usage only

map: function( elems, callback, arg ) {

var value, key, ret = [],

i = 0,

length = elems.length,

// jquery objects are treated as arrays

isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;

// Go through the array, translating each of the items to their

if ( isArray ) {

for ( ; i < length; i++ ) {

value = callback( elems[ i ], i, arg );

if ( value != null ) {

ret[ ret.length ] = value;

}

}

// Go through every key on the object,

} else {

for ( key in elems ) {

value = callback( elems[ key ], key, arg );

if ( value != null ) {

ret[ ret.length ] = value;

}

}

}

// Flatten any nested arrays

return ret.concat.apply( [], ret );

},

// A global GUID counter for objects

guid: 1,

// Bind a function to a context, optionally partially applying any

// arguments.

proxy: function( fn, context ) {

if ( typeof context === "string" ) {

var tmp = fn[ context ];

context = fn;

fn = tmp;

}

// Quick check to determine if target is callable, in the spec

// this throws a TypeError, but we will just return undefined.

if ( !jQuery.isFunction( fn ) ) {

return undefined;

}

// Simulated bind

var args = slice.call( arguments, 2 ),

proxy = function() {

return fn.apply( context, args.concat( slice.call( arguments ) ) );

};

// Set the guid of unique handler to the same of original handler, so it can be removed

proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;

return proxy;

},

// Mutifunctional method to get and set values to a collection

// The value/s can optionally be executed if it‘s a function

access: function( elems, key, value, exec, fn, pass ) {

var length = elems.length;

// Setting many attributes

if ( typeof key === "object" ) {

for ( var k in key ) {

jQuery.access( elems, k, key[k], exec, fn, value );

}

return elems;

}

// Setting one attribute

if ( value !== undefined ) {

// Optionally, function values get executed if exec is true

exec = !pass && exec && jQuery.isFunction(value);

for ( var i = 0; i < length; i++ ) {

fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );

}

return elems;

}

// Getting an attribute

return length ? fn( elems[0], key ) : undefined;

},

now: function() {

return ( new Date() ).getTime();

},

// Use of jQuery.browser is frowned upon.

// More details: http://docs.jquery.com/Utilities/jQuery.browser

uaMatch: function( ua ) {

ua = ua.toLowerCase();

var match = rwebkit.exec( ua ) ||

ropera.exec( ua ) ||

rmsie.exec( ua ) ||

ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||

[];

return { browser: match[1] || "", version: match[2] || "0" };

},

sub: function() {

function jQuerySub( selector, context ) {

return new jQuerySub.fn.init( selector, context );

}

jQuery.extend( true, jQuerySub, this );

jQuerySub.superclass = this;

jQuerySub.fn = jQuerySub.prototype = this();

jQuerySub.fn.constructor = jQuerySub;

jQuerySub.sub = this.sub;

jQuerySub.fn.init = function init( selector, context ) {

if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {

context = jQuerySub( context );

}

return jQuery.fn.init.call( this, selector, context, rootjQuerySub );

};

jQuerySub.fn.init.prototype = jQuerySub.fn;

var rootjQuerySub = jQuerySub(document);

return jQuerySub;

},

browser: {}

});

}

其中,noConflict方法實現了jQuery和其他框架的多庫共存。我們可以測試一下。當然,測試之前,要修改文件jquery.main.js,添加對方法jQuery_extend_core的調用。

技術分享

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<script>$="假設這是另一個庫的命名空間";</script>

<script src="jquery.init.js"></script>

<script src="jquery.extend.define.js"></script>

<script src="jquery.extend.core.js"></script>

<script src="jquery.main.js"></script>

<title>jquery demo</title>

</head>

<body>

<script>

alert($);

</script>

</body>

</html>

測試文件中<script>$="假設這是另一個庫的命名空間";</script>,是假設我們引用了其他的庫,而且這個庫的命名空間同樣是$。測試結果是:

技術分享

顯示的是jQuery對象的構造函數,說明我們引入jQuery庫的時候,jQuery框架的命名空間$覆蓋了之前那個庫的命名空間。現在我們用方法noConflict實現多庫共存。

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<script>$="假設這是另一個庫的命名空間";</script>

<script src="jquery.init.js"></script>

<script src="jquery.extend.define.js"></script>

<script src="jquery.extend.core.js"></script>

<script src="jquery.main.js"></script>

<title>jquery demo</title>

</head>

<body>

<script>

var myQuery = $.noConflict();

alert($);

alert(myQuery);

</script>

</body>

</html>

這樣以來,jQuery對象就釋放了命名空間$,而使用新的命名空間myQuery。

技術分享 技術分享

(待續)

第五節:JQuery框架源碼簡析(1)