<b>2.8 靜态屬性和方法</b>
在構造jquery對象子產品中還定義了一些重要的靜态屬性和方法,它們是其他子產品實作的基礎。其整體源碼結構如代碼清單2-3所示。
代碼清單2-3 靜态屬性和方法
388 jquery.extend({
389
noconflict: function( deep ) {},
402
isready: false,
406
readywait: 1,
409
holdready: function( hold ) {},
418
ready: function( wait ) {},
444
bindready: function() {},
492
isfunction: function( obj ) {},
496
isarray: array.isarray || function( obj ) {},
501
iswindow: function( obj ) {},
505
isnumeric: function( obj ) {},
509
type: function( obj ) {},
515
isplainobject: function( obj ) {},
544
isemptyobject: function( obj ) {},
551
error: function( msg ) {},
555
parsejson: function( data ) {},
581
parsexml: function( data ) {},
601
noop: function() {},
606
globaleval: function( data ) {},
619
camelcase: function( string ) {},
623
nodename: function( elem, name ) {},
628
each: function( object, callback, args ) {},
669
trim: trim ? function( text ) {} : function( text ) {},
684
makearray: function( array, results ) {},
702
inarray: function( elem, array, i ) {},
724
merge: function( first, second ) {},
744
grep: function( elems, callback, inv ) {},
761
map: function( elems, callback, arg ) {},
794
guid: 1,
798
proxy: function( fn, context ) {},
825
access: function( elems, key, value, exec, fn, pass ) {},
852
now: function() {},
858
uamatch: function( ua ) {},
870
sub: function() {},
891
browser: {}
892 });
jquery.isready、jquery.readywait、jquery.holdready()、jquery.ready()、jquery.bindready()用于支援ready事件,将在9.11節對它們進行介紹和分析;jquery.each()和jquery.map()已經在2.7.3節和2.7.4節介紹和分析過了;jquery.sub()在jquery源碼中沒有用到,并且不推薦使用,這裡不做分析;下面對其他的靜态屬性和方法逐一介紹和分析。
2.8.1 jquery.noconflict(
[removeall] )
方法jquery.noconflict( [removeall] )用于釋放jquery對全局變量$的控制權,可選的參數removeall訓示是否釋放對全局變量jquery的控制權。$僅僅是jquery的别名,所有的功能沒有$也能使用。
很多javascript庫使用美元符$作為函數名或變量名,在使用jquery的同時,如果需要使用另一個javascript庫,可以調用$.noconflict()傳回$給其他庫。如果有必要(例如,在一個頁面中使用多個版本的jquery庫,但很少有這樣的必要),也可以釋放全局變量jquery的控制權,隻需要給這個方法傳入參數true即可。
方法jquery.noconflict( [removeall] )相關代碼如下所示:
30 // map over jquery in case
of overwrite
31 _jquery = window.jquery,
32
33 // map over the $ in case
34 _$ = window.$,
388 jquery.extend({
389
noconflict: function( deep ) {
390
if ( window.$ === jquery ) {
391
window.$ = _$;
392
}
393
394
if ( deep && window.jquery === jquery ) {
395
window.jquery = _jquery;
396
397
398
return jquery;
399
},
9245 // expose jquery to
the global object
9246 window.jquery =
window.$ = jquery;
第30~34行:jquery初始化時,把可能存在的window.jquery和window.$備份到局部變量_jquery和_$。
第390~392行:如果window.$ === jquery,則設定window.$為初始化時備份的_$。也就是說,隻有在目前jquery庫持有全局變量$的情況下,才會釋放$的控制權給前一個javascript庫。
從jquery 1.6開始增加了對window.$ === jquery的檢測。如果不檢測,則每次調用jquery.noconflict()時都會釋放$給前一個javascript庫,當頁面中有兩個以上定義了$的 javascript庫時,對$的管理将會變得混亂。
第394~396行:如果參數deep為true,并且window.jquery === jquery,則設定window.jquery為初始化時備份的_jquery。也就是說,如果參數deep為true,隻有在目前jquery庫持有全局變量jquery的情況下,才會釋放jquery的控制權給前一個javascript庫。
從jquery 1.6開始增加了對window.$ === jquery的檢測。如果不檢測,則每次調用jquery.noconflict( true )時都會釋放jquery給前一個javascript庫,當頁面中有兩個以上定義了jquery的javascript庫時,對jquery的管理将會變得混亂。
2.8.2 類型檢測:jquery.isfunction(
obj )、jquery.isarray( obj )、jquery
.iswindow( obj )、jquery.isnumeric( value )、jquery.type( obj )、
jquery.isplainobject( object )、jquery.isemptyobject(
object )
1.?jquery.isfunction( obj )、jquery.isarray(
obj )
方法jquery.isfunction( obj )用于判斷傳入的參數是否是函數;方法jquery.isarray(
obj )用于判斷傳入的參數是否是數組。這兩個方法的實作依賴于方法jquery.type( obj ),通過判斷 jquery.type( obj )傳回值是否是“function”和“array”來實作。相關代碼如下所示:
489
// see test/unit/core.js for details concerning isfunction.
490
// since version 1.3, dom methods and functions like alert
491
// aren't supported. they return false on ie (#2968).
isfunction: function( obj ) {
493
return jquery.type(obj) === "function";
494
495
isarray: array.isarray || function( obj ) {
497
return jquery.type(obj) === "array";
498
499
2.?jquery.type( obj )
方法jquery.type( obj )用于判斷參數的内建javascript類型。如果參數是undefined或null,傳回“undefined”或“null”;如果參數是javascript内部對象,則傳回對應的字元串名稱;其他情況一律傳回“object”。相關代碼如下所示:
type: function( obj ) {
510
return obj == null ?
511 string( obj ) :
512 class2type[ tostring.call(obj) ] ||
"object";
513
第510~511行:如果參數obj是undefined或null,通過string( obj )轉換為對應的原始字元串“undefined”或“null”。
第512行:先借用object的原型方法tostring()擷取obj的字元串表示,傳回值的形式為[object class],其中的class是内部對象類,例如,object.prototype.tostring.call( true )會傳回
[object boolean];然後從對象class2type中取出[object
class]對應的小寫字元串并傳回;如果未取到則一律傳回“object”。原型方法tostring()和對象class2type的定義和初始化源碼如下所示:
87 tostring = object.prototype.tostring,
94 // [[class]] -> type pairs
95 class2type = {};
894 // ppulate the
class2type map
895 jquery.each("boolean
number string function array date regexp object".split(" "),
function(i, name) {
896 class2type[ "[object " + name +
"]" ] = name.tolowercase();
897 });
對象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"
3.?jquery.iswindow( obj )
方法jquery.iswindow( obj )用于判斷傳入的參數是否是window對象,通過檢測是否存在特征屬性setinterval來實作,相關代碼如下所示:
500
// a crude way of determining if an object is a window
iswindow: function( obj ) {
502
return obj && typeof obj === "object" &&
"setinterval" in obj;
503
在本書寫作時釋出的jquery 1.7.2中,該方法改為檢測特征屬性window,該屬性是對視窗自身的引用,相關代碼如下所示:
// 1.7.2
return obj != null && obj == obj.window;
4.?jquery.isnumeric( value )
方法jquery.isnumeric( value )用于判斷傳入的參數是否是數字,或者看起來是否像數字,相關代碼如下所示:
isnumeric: function( obj ) {
506
return !isnan( parsefloat(obj) ) && isfinite( obj );
507
第506行:先執行parsefloat( obj )嘗試把參數obj解析為數字,然後用isnan()判斷解析結果是否是合法數字,并用isfinite()判斷參數obj是否是有限的。如果parsefloat( obj )的解析結果是合法數字,并且參數obj是有限數字,則傳回true;否則傳回false。
方法parsefloat( string )用于對字元串參數進行解析,并傳回字元串中的第一個數字。在解析過程中,如果遇到了不是有效數字的字元,解析就會停止并傳回解析結果;如果字元串沒有以一個有效的數字開頭,則傳回nan;如果傳入的參數是對象,則自動調用該對象的方法tostring(),得到該對象的字元串表示,然後再執行解析過程。
方法isnan( x )用于判斷參數是否為非數字值,常用于檢測方法parsefloat()和parseint()的解析結果。
方法isfinite( number )用于判斷一個數字是否是有限的。
5.?jquery.isplainobject( object )
方法jquery.isplainobject( object )用于判斷傳入的參數是否是“純粹”的對象,即是否是用對象直接量{}或new object()建立的對象。相關代碼如下所示:
isplainobject: function( obj ) {
516
// must be an object.
517
// because of ie, we also have to check the presence of the constructor
property.
518
// make sure that dom nodes and window objects don't pass through, as
well
519
if ( !obj || jquery.type(obj) !== "object" || obj.nodetype ||
jquery.iswindow( obj ) ) {
520 return false;
521
522
523
try {
524 // not own constructor property
must be object
525 if ( obj.constructor &&
526 !hasown.call(obj,
"constructor") &&
527
!hasown.call(obj.constructor.prototype, "isprototypeof") ) {
528 return false;
529 }
530
} catch ( e ) {
531 // ie8,9 will throw exceptions on
certain host objects #9897
532 return false;
533
534
535
// own properties are enumerated firstly, so to speed up,
536
// if last one is own, then all properties are own.
537
538
var key;
539
for ( key in obj ) {}
540
541
return key === undefined || hasown.call( obj, key );
542
第519~521行:如果參數obj滿足以下條件之一,則傳回false:
參數obj可以轉換為false。
object.prototype.tostring.call( obj )傳回的不是[object
object]。
參數obj是dom元素。
參數obj是window對象。
如果參數obj不滿足以上所有條件,則至少可以确定參數obj是對象。
第523~533行:檢查對象obj是否由構造函數object()建立。如果對象obj滿足以下所有條件,則認為不是由構造函數object()建立,而是由自定義構造函數建立,傳回false:
對象obj含有屬性constructor。由構造函數建立的對象都有一個constructor屬性,預設引用了該對象的構造函數。如果對象obj沒有屬性constructor,則說明該對象必然是通過對象字面量{}建立的。
對象obj的屬性constructor是非繼承屬性。預設情況下,屬性constructor繼承自構造函數的原型對象。如果屬性constructor是非繼承屬性,說明該屬性已經在自定義構造函數中被覆寫。
對象obj的原型對象中沒有屬性isprototypeof。屬性isprototypeof是object原型對象的特有屬性,如果對象obj的原型對象中沒有,說明不是由構造函數object()建立,而是由自定義構造函數建立。
執行以上檢測時抛出了異常。在ie 8/9中,在某些浏覽器對象上執行以上檢測時會抛出異常,也應該傳回false。
函數hasown()指向object.prototype.hasownproperty( property ),用于檢查對象是否含有執行名稱的非繼承屬性。
第539~541行:檢查對象obj的屬性是否都是非繼承屬性。如果沒有屬性,或者所有屬性都是非繼承屬性,則傳回true。如果含有繼承屬性,則傳回false。
第539行:執行for-in循環時,javascript會先枚舉非繼承屬性,再枚舉從原型對象繼承的屬性。
第541行:如果對象obj的最後一個屬性是非繼承屬性,則認為所有屬性都是非繼承屬性,傳回true;如果最後一個屬性是繼承屬性,即含有繼承屬性,則傳回false。
6.?jquery.isemptyobject( object )
方法jquery.isemptyobject( object )用于檢測對象是否是空的(即不包含屬性)。例如:
jquery.isemptyobject( {} ) // true
jquery.isemptyobject( new object() ) // true
jquery.isemptyobject( { foo:
"bar" } ) // false
方法jquery.isemptyobject( object )的相關代碼如下所示:
isemptyobject: function( obj ) {
545
for ( var name in obj ) {
546 return false;
547
548
return true;
549
第545~548行:for-in循環會同時枚舉非繼承屬性和從原型對象繼承的屬性,如果有,則立即傳回false,否則預設傳回true。
2.8.3 解析json和xml:jquery.parsejson(
data )、jquery.parsexml( data )
1.?jquery.parsejson( data )
方法jquery.parsejson( data )接受一個格式良好的json字元串,傳回解析後的javascript對象。如果傳入殘缺的json字元串可能導緻程式抛出異常;如果不傳入參數,或者傳入空字元串、null、undefined,則傳回null。
如果浏覽器提供了原生方法json.parse(),則使用該方法解析json字元串;否則使用
( new function( "return"+ data )
)()解析json字元串。
方法jquery.parsejson( data )的相關代碼如下所示:
555
parsejson: function( data ) {
556
if ( typeof data !== "string" || !data ) {
557 return null;
558
559
560
// make sure leading/trailing whitespace is removed (ie can't handle it)
561
data = jquery.trim( data );
562
563
// attempt to parse using the native json parser first
564
if ( window.json && window.json.parse ) {
565 return window.json.parse( data );
566
567
568
// make sure the incoming data is actual json
569
// logic borrowed from http:// json.org/json2.js
570
if ( rvalidchars.test( data.replace( rvalidescape, "@" )
571 .replace( rvalidtokens,
"]" )
572 .replace( rvalidbraces,
"")) ) {
573
574 return ( new function(
"return " + data ) )();
575
576
577
jquery.error( "invalid json: " + data );
578
第556~561行:對于非法參數一律傳回null。如果參數data不是字元串,或者可以轉換為false,則傳回null。
第561行:移除開頭和末尾的空白符。在ie 6/7中,如果不移除就不能正确的解析,例如:
typeof ( new function( 'return ' + '\n{}' )
)();
// 傳回"undefined"
第564~566行:嘗試使用原生方法json.parse()解析json字元串,并傳回。
json對象含有兩個方法:json.parse()和json.stringify(),用于json字元串和javascript對象之間的互相轉換。下面是它們的文法和使用示例。
json.parse()解析json字元串為 json對象,其文法如下:
json.parse(text[, reviver])
// text 待解析為 json 對象的字元串
// reviver 可選。在傳回解析結果前,對解析結果中的屬性值進行修改
json.parse()的使用示例如下所示:
json.parse( '{ "abc": 123 }' );
// {"abc": 123 }
json.parse( '{ "abc": 123 }',
function( key, value ){
if( key === '' ) return value;
return value * 2;
} );
// {"abc": 246 }
json.stringify()轉換json對象為json字元串,其文法如下:
json.stringify( value[, replacer [, space]]
)
// value 待轉換為 json 字元串的對象
// replacer 可選。如果 replacer 是函數,轉換前先執行
replacer 改變屬性值,如果函數 replacer 傳回 undefined,則對應的屬性不會出現在結果中;如果 replacer 是數組,指定最終字元串中包含的屬性集合,不在數組 replacer 中的屬性不會出現在結果中
// space 增加轉換後的 json 字元串的可讀性
json.stringify()的使用示例如下所示:
json.stringify( { a: 1, b: 2 } );
// '{"a":1,"b":2}'
json.stringify( { a: 1, b: 2 }, function(
key, value ){
if( key === 'a' ) return value * 10;
if( key === 'b' ) return undefined;
return value;
// '{"a":10}'
json.stringify( { a: 1, b: 2 }, ['b'] );
// '{"b":2}'
json.stringify( { a: 1, b: 2 }, null, 4 );
// '{\n
"a": 1,\n
"b": 2\n}'
json對象、json.parse()、json.stringify()在ecmascript 5中被标準化,ie 8以下的浏覽器不支援。關于json規範和浏覽器實作的更多資訊請通路以下位址:
http://json.org/json-zh.html
http://www.ecma-international.org/publications/standards/ecma-262.htm(ecmascript 5第15.12節)
https://developer.mozilla.org/en/javascript/reference/global_objects/json
下面回到對方法jquery.parsejson()的分析中來。
第570~576行:在不支援json.parse()的浏覽器中,先檢查字元串是否合法,如何合法,才會執行( new function("return"+ data) )()并傳回執行結果。檢查字元串是否合法的正則和邏輯來自開源json解析庫json2.js(https://github.com/douglascrockford/json-js),檢測過程分為4步,用到了4個正規表達式:rvalidchars、rvalidescape、rvalidtokens、rvalidbraces,相關代碼如下:
53
// json regexp
54 rvalidchars = /^[\],:{}\s]*$/,
55
rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fa-f]{4})/g,
56
rvalidtokens =
/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[ee][+\-]?\d+)?/g,
57
rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
第54~57行:正則rvalidescape用于比對轉義字元;正則rvalidtokens用于比對有效值(字元串、true、false、null、數值);正則rvalidbraces用于比對正确的左方括号“[”;正則rvalidchars用于檢查字元串是否隻含有指定的字元(“]”、“,”、“:”、“{”、“}”、“\s”)。
第570~572行:先利用正則rvalidescape把轉義字元替換為“@”,為進行下一步替換做準備;再利用正則rvalidtokens把字元串、true、false、null、數值替換為“]”;然後利用rvalidbraces删除正确的左方括号;最後檢查剩餘字元是否隻包含“]”、“,”、“:”、“{”、“}”、“\s”,如果隻包含這些字元,那麼認為json字元串是合法的。
第574行:通過構造函數function()建立函數對象,然後執行。構造函數function()的文法如下:
new function( arguments_names..., body)
// 參見arguments_names...:任意多個字元串參數,每個字元串命名一個或多個要建立的 function 對象的參數
// 參見body:一個字元串,指定函數的主體,可以含有任意多條 javascript 語句,這些語句之間用分号隔開,可以給該構造函數引用前面的參數設定的任何參數名
// 傳回新建立的 function 對象。調用該函數,将執行
body 指定的 javascript 代碼
第577行:如果浏覽器不支援json.parse(),并且json字元串不合法,則在最後抛出一個異常。
2.?jquery.parsexml( data )
方法jquery.parsexml( data )接受一個格式良好的xml字元串,傳回解析後的xml文檔。
方法jquery.parsexml()使用浏覽器原生的xml解析函數實作。在ie 9+和其他浏覽器中,會使用domparser對象解析;在ie 9以下的浏覽器中,則使用activexobject對象解析。相關代碼如下所示:
580
// cross-browser xml parsing
parsexml: function( data ) {
582
var xml, tmp;
583
584 if ( window.domparser ) { //
sandard
585 tmp = new domparser();
586 xml = tmp.parsefromstring( data
, "text/xml" );
587 } else { //ie
588 xml = new activexobject(
"microsoft.xmldom" );
589 xml.async = "false";
590 xml.loadxml( data );
591
}
592
} catch( e ) {
593 xml = undefined;
594
595
if ( !xml || !xml.documentelement || xml.getelementsbytagname(
"parsererror" ).length ) {
596 jquery.error( "invalid xml:
" + data );
597
598
return xml;
599
第584~586行:嘗試用标準解析器domparser解析。domparser在html5中标準化,可以将xml或html字元串解析為一個dom文檔。解析時,首先要建立一個domparser對象,然後使用它的方法parsefromstring()來解析xml或html字元串。方法parsefromstring()的文法如下:
domparser.parsefromstring( domstring str,
supportedtype type )
// 參數str:待解析的 xml 或 html 字元串
// 參數type:支援的類型有"text/html"、"text/xml"、"application/xml"、"application/xhtml+xml"、"image/svg+xml"
// 傳回一個解析後的新建立的文檔對象
在ie以外的浏覽器中,如果解析失敗,方法parsefromstring()不會抛出任何異常,隻會傳回一個包含了錯誤資訊的文檔對象,如下所示:
<parsererror xmlns="http:// www.mozilla.org/newlayout/xml/parsererror.xml">
(error description)
<sourcetext>(a snippet of the source xml)</sourcetext>
</parsererror>
是以,在第595行需要檢查解析後的文檔中是否包含<parsererror>節點,如果包含則表示解析失敗,抛出一個更易讀的異常。
在ie 9+中,如果解析失敗,則會抛出異常。如果抛出異常,在catch塊中設定xml為undefined,然後抛出一個更易讀的異常。
可以運作下面的測試代碼來驗證上述内容:
new
domparser().parsefromstring("<a>hello" , "text/xml")
在chrome中傳回一個包含了錯誤資訊的文檔對象:
<a>
<parsererror style="display: block; white-space: pre; border:
2px solid #c77;
padding: 0 1em 0 1em; margin: 1em; background-color: #fdd; color:
black">
<h3>this page contains the following errors:</h3>
<div style="font-family:monospace;font-size:12px">
error on line 1 at column 9: extra content at the end of the document
</div>
<h3>below is a rendering of the page up to the first
error.</h3>
</parsererror>hello
</a>
在ie 9中則抛出異常:
"dom exception: syntax_err (12)"
xml5602: 輸入意外結束。
, 行1 字元9
下面回到對方法jquery.parsexml()源碼的分析中來。
第588~594行:ie 9以下的浏覽器不支援domparser,需要使用微軟的xml解析器microsoft.xmldom解析。解析步驟依次為:建立一個空的xml文檔對象,設該文檔對象為同步加載,調用方法loadxml()解析xml字元串。
如果解析成功,方法loadxml()會傳回true,解析結果存放在建立的xml文檔對象中;如果解析失敗,方法loadxml()會傳回false(不會抛出異常),并設定文檔根節點documentelement為null。
第595~597行:如果解析失敗,則抛出一個更易讀的異常。如果滿足以下條件之一,則認為解析失敗:
在ie 9+中,通過标準xml解析器domparser解析失敗,此時!xml為true。
在ie 9以下的浏覽器中,通過微軟的xml解析器microsoft.xmldom解析失敗,此時!xml.documentelement為true。
在其他浏覽器中,通過标準xml解析器domparser解析失敗,此時xml.getelementsbytagname("parsererror").length可以轉換為true。
第598行:如果解析成功,則傳回解析結果。
下面是一些擴充閱讀:
xml文檔屬性和方法:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms763798(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms757828(v=vs.85).aspx
解析xml文檔:
http://www.w3schools.com/xml/xml_parser.asp
html5規範中的xml序列化:
http://html5.org/specs/dom-parsing.html#xmlserializer
2.8.4 jquery.globaleval(
code )
方法jquery.globaleval( code )用于在全局作用域中執行javascript代碼。很多時候我們希望javascript代碼是在全局作用域中執行,例如,當動态加載并執行javascript代碼時。
在ie中,可以調用方法execscript()讓javascript代碼在全局作用域中執行;在其他浏覽器中,則需要在一個自調用匿名函數中調用eval()執行javascript代碼,自調用匿名函數確定了執行環境是全局作用域。相關代碼如下所示:
603
// evaluates a script in a global context
604
// workarounds based on findings by jim driscoll
605
//
http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-
global-context
globaleval: function( data ) {
607
if ( data && rnotwhite.test( data ) ) {
608 // we use execscript on internet
explorer
609 // we use an anonymous function so
that context is window
610 // rather than jquery in firefox
611 ( window.execscript || function(
data ) {
612 window[ "eval" ].call(
window, data );
613 } )( data );
614
615
方法execscript()在全局作用域中按照指定的腳本語言執行腳本代碼,預設語言是jscript,沒有傳回值。該方法的文法如下:
execscript( code, language )
// 參數code:待執行的腳本代碼
// 參數language:腳本語言,可選值有javascript、javascript1.1、javascript1.2、javascript1.3、
jscript、vbs、vbscript,預設是 jscript
chrome的早期版本曾支援方法execscript(),現已不支援。
方法eval()在調用它的作用域中計算或執行javascript代碼。如果javascript代碼是一條表達式,則計算并傳回計算結果;如果含有一條或多條javascript語句,則執行這些語句,如果最後一條語句有傳回值,則傳回這個值,否則傳回undefined。該方法的文法如下:
eval( code )
// 參數code:待執行的
javascript 表達式或語句
第611~613行:如果支援方法execscript(),則執行execscript( data );否則建立一個自調用匿名函數,在函數内部執行“window[ "eval" ].call( window, data );”,讀者可以通路源碼注釋第605行的網頁位址檢視如此書寫的原因。
2.8.5 jquery.camelcase(
string )
方法jquery.camelcase( string )轉換連字元式的字元串為駝峰式,用于css子產品和資料緩存子產品。例如:
jquery.camelcase( 'background-color' );
// "backgroundcolor"
方法jquery.camelcase( string )的相關代碼如下所示:
65
// matches dashed string for camelizing
66
rdashalpha = /-([a-z]|[0-9])/ig,
67
rmsprefix = /^-ms-/,
68
69
// used by jquery.camelcase as callback to replace()
70
fcamelcase = function( all, letter ) {
71
return ( letter + "" ).touppercase();
72
617
// convert dashed to camelcase; used by the css and data modules
618
// microsoft forgot to hump their vendor prefix (#9572)
camelcase: function( string ) {
620
return string.replace( rmsprefix, "ms-" ).replace( rdashalpha,
fcamelcase );
621
第66行:正則rdashalpha用于比對字元串中連字元“-”和其後的第一個字母或數字。如果連字元“-”後是字母,則比對部分會被替換為對應的大寫字母;如果連字元“-”後是數字,則會删掉連字元“-”,保留數字。
第67行:正則rmsprefix用于比對字元串中字首“-ms-”,比對部分會被替換為“ms-”。這麼做是因為在ie中,連字元式的樣式名字首“-ms-”對應小寫的“ms”,而不是駝峰式的“ms”。例如,“-ms-transform”對應“mstransform”而不是“mstransform”。在ie以外的浏覽器中,連字元式的樣式名則可以正确地轉換為駝峰式,例如,“-moz-transform”對應“moztransform”。
第70~72行:函數fcamelcase()負責把連字元後的字母轉換為大寫并傳回。
第619~621行:在方法jquery.camelcase()中,先用正則rmsprefix比對字首“-ms-”,如果有則修正為“ms-”;然後用正則rdashalpha比對連字元“-”和其後的第一個字母或數字,并用字元串方法replace()和函數fcamelcase()把比對部分替換為對應的大寫字母或數字。
2.8.6 jquery.nodename(
elem, name )
方法jquery.nodename( elem, name )用于檢查dom元素的節點名稱(即屬性nodename)與指定的值是否相等,檢查時忽略大小寫。
dom元素的屬性nodename傳回該元素的節點名稱;對于html文檔,始終傳回其大寫形式;對于xml文檔,因為xml文檔區分大小寫,是以傳回值與源代碼中的形式一緻。在方法jquery.nodename( elem, name )中會把屬性nodename和參數name轉換為大寫形式後再做比較,即忽略大小寫。相關代碼如下所示:
nodename: function( elem, name ) {
624
return elem.nodename && elem.nodename.touppercase() ===
name.toupper
case();
625
第624行:把屬性elem.nodename和參數name都轉換為大寫再做比較。在執行elem.nodename.touppercase()前先檢查elem.nodename是否存在,可以有效地避免參數elem不是dom元素,或者參數elem沒有屬性nodename導緻的錯誤。
2.8.7 jquery.trim( str )
方法jquery.trim( str )用于移除字元串開頭和結尾的空白符。如果傳入的參數是null或undefined,則傳回空字元串;如果傳入的參數是對象,則先擷取對象的字元串表示,然後移除開頭和結尾的空白符,并傳回。相關代碼如下所示:
43
// check if a string has a non-whitespace character in it
44
rnotwhite = /\s/,
45
46
// used for trimming whitespace
47
trimleft = /^\s+/,
48
trimright = /\s+$/,
91
trim = string.prototype.trim,
910 // ie doesn't match
non-breaking spaces with \s
911 if ( rnotwhite.test(
"\xa0" ) ) {
912
trimleft = /^[\s\xa0]+/;
913
trimright = /[\s\xa0]+$/;
914 }
668
// use native string.trim function wherever possible
trim: trim ?
670
function( text ) {
671 return text == null ?
672 "" :
673 trim.call( text );
674
} :
675
676
// otherwise use our own trimming functionality
677
678 return text == null ?
679 "" :
680 text.tostring().replace(
trimleft, "" ).replace( trimright, "" );
681
第47~48行:正則trimleft用于比對字元串開頭的空白符;trimright用于比對字元串結尾的空白符。
第911~914行:在ie 9以下的浏覽器中,\s不比對不間斷空格\xa0,需要為正則trimleft和trimright加上“\xa0”。
第669~681行:如果浏覽器支援string.prototype.trim()則“借雞生蛋”,string.prototype.
trim()是ecmascript 5新增的string原型方法;如果不支援,則先調用方法tostring()得到參數text的字元串表示,然後調用方法replace()把正則trimleft和trimright比對到的空白符替換為空字元串。如果參數是null或undefined,則傳回空字元串。
2.8.8 數組操作方法:jquery.makearray(
obj )、jquery.inarray( value,
array [, fromindex] )、jquery.merge( first, second )、jquery.
grep( array, function(elementofarray, indexinarray) [, invert] )
1.?jquery.makearray( obj )
方法jquery.makearray( obj )可以将一個類數組對象轉換為真正的數組。
在jquery内部,還可以為方法jquery.makearray()傳入第二個參數,這樣,第一個參數中的元素将被合并入第二個參數,最後會傳回第二個參數,此時傳回值的類型不一定是真正的數組。
方法jquery.makearray( obj )的源碼如下:
89
push = array.prototype.push,
683
// results is for internal usage only
makearray: function( array, results ) {
685
var ret = results || [];
686
687
if ( array != null ) {
688 // the window, strings (and
functions) also have 'length'
689 // theaked logic slightly to handle
blackberry 4.7 regexp issues #6930
690 var type = jquery.type( array );
691
692 if ( array.length == null
|| type ===
"string"
"function"
"regexp"
|| jquery.iswindow( array ) )
693 push.call( ret, array );
694 } else {
695 jquery.merge( ret, array );
696 }
697
698
699
return ret;
700
第684行:定義方法jquery.makearray( array, results ),它接受2個參數:
參數array:待轉換對象,可以是任何類型。
參數results:僅在jquery内部使用。如果傳入參數results,則在該參數上添加元素。
第685行:定義傳回值ret。如果傳入了參數results則把該參數作為傳回值,否則建立一個空數組作為傳回值。
第687行:過濾參數array是null、undefined的情況。
第690~693行:如果參數array滿足以下條件之一,則認為該參數不是數組,也不是類數組對象,調用數組方法push()把該參數插入傳回值ret的末尾:
參數array沒有屬性length。
參數array是字元串,屬性length傳回字元串中的字元個數。
參數array是函數,屬性length傳回函數聲明時的參數個數。
參數array是window對象,屬性length傳回視窗中的架構(frame、iframe)個數。
參數array是正則對象,在blackberry(黑莓)4.7中,正則對象也有length屬性。
注意,第693行插入元素時執行的是push.call( ret,array ),而不是ret.push( array ),這是因為傳回值ret不一定是真正的數組。如果隻傳入參數array,則傳回值ret是真正的數組;如果還傳入了第二個參數result,則傳回值ret的類型取決于該參數的類型。
第694~696行:否則認為參數array是數組或類數組對象,調用方法jquery.merge()把該參數合并到傳回值ret中。
第699行:最後傳回ret。
2.?jquery.inarray( value, array[,
fromindex] )
方法jquery.inarray( value, array[, fromindex] )在數組中查找指定的元素并傳回其下标,未找到則傳回-1。相關代碼如下所示:
inarray: function( elem, array, i ) {
703
var len;
704
705
if ( array ) {
706 if ( indexof ) {
707 return indexof.call( array,
elem, i );
708 }
709
710 len = array.length;
711 i = i ? i < 0 ? math.max( 0,
len + i ) : i : 0;
712
713 for ( ; i < len; i++ ) {
714 // sip accessing in sparse
arrays
715 if ( i in array &&
array[ i ] === elem ) {
716 return i;
717 }
718 }
719
720
721
return -1;
722
第702行:定義方法jquery.inarray( elem, array, i ),它接受3個參數:
參數elem:要查找的值。
參數array:數組,将周遊這個數組來查找參數value在其中的下标。
參數i:指定開始查找的位置,預設是0即查找整個數組。
第705行:過濾array可以轉換為false的情況。
第706~708行:如果浏覽器支援數組方法indexof(),則調用它并傳回下标。該方法在ecmascript 5中被标準化。
第711行:修正參數i。如果未指定參數i,則初始化為0,表示預設從頭開始周遊;如果i小于0,則加上數組長度len,即從數組末尾開始計算,例如,-1表示最後一個元素,-2表示倒數第二個元素,以此類推。注意,這裡調用了math.max()方法在0和len+i之間取最大值,即如果len+i依然小于0,則把i修正為0,仍然從頭開始周遊。
第713~718行:從指定位置開始周遊數組,查找與指定值elem相等的元素,并傳回其下标。先檢測i in array,如果結果是false,說明數組array的下标是不連續的,也不需要與指定值elem比較;然後檢測array[i]=== elem,如果結果是true,則傳回目前下标。這裡使用等同運算符( === )來避免類型轉換。
第721行:如果沒有找到與指定值相等的元素,則預設傳回-1。從方法jquery.inarray()的命名來看,這個方法應該傳回true或false,而它實際上傳回的卻是下标,是以把方法名改為indexof或許更合适些,但是這個方法從jquery 1.2就一直存在,如果修改則會導緻嚴重的向後相容問題,是以傳回值和方法名都不宜修改。
通常我們會比較jquery.inarray()的傳回值是否大于0,來判斷某個指定的元素是否是數組中的元素,就像下面這樣:
if( jquery.inarray( elem, array ) > 0 ){
//
elem 是 array 中的元素
上面的寫法比較繁瑣,特别是當if語句檢測的條件是複合布爾表達式時,可讀性會很差;可以用按位非運算符(~)簡化上面的代碼:
if( ~jquery.inarray( elem, array ) ){
按位非運算符(~)會将運算數的所有位取反,相當于改變它的符号并且減1,例如:
~-1 == 0; // true
~0 == -1; // true
~1 == -2; // true
~2 == -3; // true
更進一步,可以結合使用按位非運算符(~)和邏輯非運算符(!)把jquery.inarray()的傳回值轉換為布爾型:
!!~jquery.inarray( elem, array )
// 如果elem 可以比對 array 中的某個元素,則該表達式的值為 true
// 如果elem 比對不到 array 中的元素,則該表達式的值為
false
3.?jquery.merge( first, second )
方法jquery.merge( first, second )用于合并兩個數組的元素到第一個數組中。事實上,第一個參數可以是數組或類數組對象,即必須含有整型(或可以轉換為整型)屬性length;第二個參數則可以是數組、類數組對象或任何含有連續整型屬性的對象。
方法jquery.merge()的合并行為是破壞性的,将第二個數組中的元素添加到第一個數組中後,第一個數組就被改變了。如果希望原來的第一個數組不被改變,可以在調用jquery.merge()之前建立一份第一個數組的副本:
var newarray = $.merge([], oldarray);
方法jquery.merge( first, second )的相關代碼如下所示:
merge: function( first, second ) {
725
var i = first.length,
726 j = 0;
727
728
if ( typeof second.length === "number" ) {
729 for ( var l = second.length; j
< l; j++ ) {
730 first[ i++ ] = second[ j ];
731 }
732
733
} else {
734 while ( second[j] !== undefined )
735 first[ i++ ] = second[ j++ ];
736 }
737
738
739
first.length = i;
740
741
return first;
742
第724行:定義方法jquery.merge( first, second ),它接受2個參數:
參數first:數組或類數組對象,必須含有整型(或可以轉換為整型)屬性length,第二個數組second中的元素會被合并到該參數中。
參數second:數組、類數組對象或任何含有連續整型屬性的對象,其中的元素會被合并到第一個參數first中。
第725行:初始化變量i為first.length,該變量訓示了插入新元素時的下标。first.length必須是整型或可以轉換為整型,否則後面執行i++時會傳回nan。
第728~731行:如果參數second的屬性length是數值類型,則把該參數當作數組處理,把其中的所有元素都添加到參數first中。
第733~737行:如果參數second沒有屬性length,或者屬性length不是數值類型,則把該參數當作含有連續整型(或可以轉換為整型)屬性的對象,例如,{ 0:'a', 1:'b'},把其中的非undefined元素逐個插入參數first中。
第739行:修正first.length。因為參數first可能不是真正的數組,是以需要手動維護屬性length的值。
第741行:傳回改變後的參數first。
4.?jquery.grep( array, function(
elementofarray, indexinarray )[, invert] )
方法jquery.grep( array, function( elementofarray, indexinarray )[,
invert] )用于查找數組中滿足過濾函數的元素,原數組不會受影響。
如果參數invert未傳入或是false,元素隻有在過濾函數傳回true,或者傳回值可以轉換為true時,才會被儲存在最終的結果數組中,即傳回一個滿足回調函數的元素數組;如果參數invert是true,則情況正好相反,傳回的是一個不滿足回調函數的元素數組。
該方法的相關代碼如下所示:
grep: function( elems, callback, inv ) {
745
var ret = [], retval;
746
inv = !!inv;
747
748
// go through the array, only saving the items
749
// that pass the validator function
750
for ( var i = 0, length = elems.length; i < length; i++ ) {
751 retval = !!callback( elems[ i ], i
);
752 if ( inv !== retval ) {
753 ret.push( elems[ i ] );
754 }
755
756
757
758
第744行:定義方法jquery.grep( elems, callback, inv ),它接受3個參數:
參數array:待周遊查找的數組。
參數callback:過濾每個元素的函數,執行時被傳入兩個參數:目前元素和它的下标。該函數應該傳回一個布爾值。
參數inv:如果參數inv是false或未傳入,方法jquery.grep()會傳回一個滿足回調函數的元素數組;如果參數inv是true,則傳回一個不滿足回調函數的元素數組。
第746行:周遊數組elems,為每個元素執行過濾函數。如果參數inv為true,把執行結果為false的元素放入結果數組ret;如果inv為false,則把執行結果為true的元素放入結果數組ret。
第757行:最後傳回結果數組ret。
2.8.9 jquery.guid、jquery.proxy(
function, context )
1.?jquery.guid
屬性jquery.guid是一個全局計數器,用于jquery事件子產品和緩存子產品。在jquery事件子產品中,每個事件監聽函數會被設定一個guid屬性,用來唯一辨別這個函數;在緩存子產品中,通過在dom元素上附加一個唯一辨別,來關聯該元素和該元素對應的緩存。屬性jquery.guid初始值為1,使用時自增1,相關代碼如下所示:
793
// a global guid counter for objects
// jquery.data( elem, name, data, pvt /*
internal use only */ )
1679 elem[ internalkey ] = id =
++jquery.uuid;
// jquery.event.add: function( elem, types,
handler, data, selector )
2861
handler.guid = jquery.guid++;
2.?jquery.proxy( function, context )
方法jquery.proxy( function, context )接受一個函數,傳回一個新函數,新函數總是持有特定的上下文。這個方法有兩種用法:
(1)jquery.proxy( function,
context )
參數function是将被改變上下文的函數,參數context是上下文。指定參數function的上下文始終為參數content。
(2)jquery.proxy( context, name )
參數name是參數context的屬性。指定參數name對應的函數的上下文始終為參數context。
796
// bind a function to a context, optionally partially applying any
797
// aguments.
proxy: function( fn, context ) {
799
if ( typeof context === "string" ) {
800 var tmp = fn[ context ];
801 context = fn;
802 fn = tmp;
803
804
805
// quick check to determine if target is callable, in the spec
806
// this throws a typeerror, but we will just return undefined.
807
if ( !jquery.isfunction( fn ) ) {
808 return undefined;
809
810
811
// simulated bind
812
var args = slice.call( arguments, 2 ),
813 proxy = function() {
814 return fn.apply( context,
args.concat( slice.call( arguments ) ) );
815 };
816
817
// set the guid of unique handler to the same of original handler, so it
can be removed
818
proxy.guid = fn.guid = fn.guid
|| proxy.guid || jquery.guid++;
819
820
return proxy;
821
第798行:定義方法jquery.proxy( fn, context ),參數有兩種格式:
jquery.proxy( fn, context )
jquery.proxy( context, name )
第799行:修正參數fn和context。如果第二個參數是字元串,說明參數格式是jquery.proxy( context, name ),修正為jquery.proxy( fn,
context )。
第807~809行:如果參數fn不是函數,則傳回undefined。
第812行:收集多餘參數。如果調用jquery.proxy()時,除了傳入參數fn、context之外,還傳入了其他參數,那麼在調用函數fn時,這些多餘的參數将會優先傳入。這裡借用數組方法slice()來擷取參數對象arguments中fn、context後的其他參數。下面的例子測試了傳入多餘參數的情況:
var proxied = $.proxy( function(){
console.log( this ); //
object
console.log( arguments ); // [1, 2,
3]
}, {}, 1, 2, 3 );
proxied(4, 5);
// 在控制台依次列印:
// object
// [1, 2, 3, 4, 5]
第813~815行:建立一個代理函數,在代理函數中調用原始函數fn,調用時通過方法apply()指定上下文。代理函數通過閉包機制引用context、args、slice。
第818行:為代理函數設定與原始函數相同的唯一辨別guid。如果原始函數沒有,則重新配置設定一個。
相同的唯一辨別将代理函數和原始函數關聯了起來。例如,在jquery事件系統中,如果為dom元素綁定了事件監聽函數的代理函數,當移除事件時,即使傳入的是原始函數,jquery也能通過唯一辨別guid移除正确的函數。
第820行:最後傳回建立的代理函數。
2.8.10 jquery.access(
elems, key, value, exec, fn( elem, key, value ), pass )
方法jquery.access( elems, key, value, exec, fn( elem, key, value ), pass
)可以為集合中的元素設定一個或多個屬性值,或者讀取第一個元素的屬性值。如果設定的屬性值是函數,并且參數exec是true時,還會執行函數并取其傳回值作為屬性值。
方法jquery.access()為.attr()、.prop()、.css()提供支援,這三個方法在調用jquery.access()時,參數exec為true,參數fn是同時支援讀取和設定屬性的函數(例jquery.attr()、jquery.prop()),相關代碼如下所示:
// .attr()
2166
attr: function( name, value ) {
2167
return jquery.access( this, name, value, true, jquery.attr );
2168
// p.rop()
2176
prop: function( name, value ) {
2177
return jquery.access( this, name, value, true, jquery.prop );
2178
// c.ss()
6460 jquery.fn.css = function( name, value
) {
6461
// ...
6466
return jquery.access( this, name, value, true, function( elem, name,
value ) {
6467
return value !== undefined ?
6468 jquery.style( elem, name, value ) :
6469 jquery.css( elem, name );
6470
});
6471 };
方法jquery.access( elems, key, value, exec, fn, pass )的相關代碼如下所示:
823
// mutifunctional method to get and set values to a collection
824
// the value/s can optionally be executed if it's a function
access: function( elems, key, value, exec, fn, pass ) {
826
var length = elems.length;
827
828
// setting many attributes
829
if ( typeof key === "object" ) {
830 for ( var k in key ) {
831 jquery.access( elems, k,
key[k], exec, fn, value );
832
}
833 return elems;
834
835
836
// setting one attribute
837
if ( value !== undefined ) {
838 // optionally, function values get
executed if exec is true
839 exec = !pass && exec
&& jquery.isfunction(value);
840
841 for ( var i = 0; i < length;
i++ ) {
842 fn( elems[i], key, exec ?
value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
843 }
844
845 return elems;
846
}
847
848
// getting an attribute
849
return length ? fn( elems[0], key ) : undefined;
850
第825行:定義方法jquery.access( elems, key, value, exec, fn, pass ),它接受 6 個參數:
參數elems:元素集合,通常是jquery對象。
參數key:屬性名或含有鍵值對的對象。
參數value:屬性值或函數。當參數key是對象時,該參數為undefined。
參數exec:布爾值,當屬性值是函數時,該參數訓示了是否執行函數。
參數fn:回調函數,同時支援讀取和設定屬性。
參數pass:布爾值,該參數在功能上與參數exec重疊,并且用法相當繁瑣,可以忽略這個參數。
第829~834行:如果參數key是對象,表示要設定多個屬性,則周遊參數key,為每個屬性遞歸調用方法jquery.access(),周遊完後傳回元素集合elems。
第837~846行:如果參數value不是undefined,表示要設定單個屬性,則周遊元素集合elems,為每個元素調用回調函數fn,周遊完後傳回元素集合elems。如果參數exec為true,并且參數value是函數,則執行參數value,并取其傳回值作為屬性值。
第849行:讀取一個屬性。如果元素集合elems不為空,則為第一個元素調用回調函數fn,讀取參數key對應的屬性值;否則傳回undefined。
2.8.11 jquery.error(
message )、jquery.noop()、jquery.now()
方法jquery.error( message )接受一個字元串,抛出一個包含了該字元串的異常。開發插件時可以覆寫這個方法,用來顯示更有用或更多的錯誤提示消息。
方法jquery.noop()表示一個空函數。當希望傳遞一個什麼也不做的函數時,可以使用這個空函數。開發插件時,這個方法可以作為可選回調函數的預設值,如果沒有提供回調函數,則執行jquery.noop()。
方法jquery.now()傳回目前時間的毫秒表示,是( newdate() ).gettime()的簡寫。
上面3個方法的相關代碼如下所示:
error: function( msg ) {
552
throw new error( msg );
553
now: function() {
853
return ( new date() ).gettime();
854
2.8.12 浏覽器嗅探:jquery.uamatch(
ua )、jquery.browser
屬性jquery.browser提供了通路目前頁面的浏覽器的資訊,其中包含最流行的4種浏覽器類型(ie、mozilla、webkit、opera)和版本資訊,屬性值的結構如下:
// jquery.browser
webkit/opera/msie/mozilla: true,
version: '版本号'
chrome和safari使用webkit作為核心引擎,是以如果jquery.browser.webkit為true則表示浏覽器是chrome或safari;如果jquery.browser.mozilla為true,則表示浏覽器是mozilla
firefox。
jquery.browser通過解析navigator.useragent來擷取浏覽器類型和版本号,這種技術也稱為浏覽器嗅探技術,用于解決浏覽器不相容問題;navigator是全局對象window的屬性,指向一個navigator對象,包含了正在使用的浏覽器的資訊;navigator.useragent包含了浏覽器用于http請求的使用者代理頭(user-agent)的值。
應該避免編寫基于特定浏覽器類型或版本号的代碼,因為這會導緻代碼與特定的浏覽器類型或版本緊密綁定在一起,另外,使用者或浏覽器也可以修改navigator.useragent,欺騙腳本和伺服器端;解決浏覽器不相容問題的更好做法是基于浏覽器功能測試編寫代碼,具體請參閱第7章。
對navigator.useragent的解析由方法jquery.uamatch( ua )實作,相關代碼如下所示:
59
// useragent regexp
60
rwebkit = /(webkit)[ \/]([\w.]+)/,
61
ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
62
rmsie = /(msie) ([\w.]+)/,
63
rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
74
// keep a useragent string for use with jquery.browser
75
useragent = navigator.useragent,
856
// use of jquery.browser is frowned upon.
857
// more details: http:// docs.jquery.com/utilities/jquery.browser
uamatch: function( ua ) {
859
ua = ua.tolowercase();
860
861
var match = rwebkit.exec( ua ) ||
862 ropera.exec( ua ) ||
863 rmsie.exec( ua ) ||
864 ua.indexof("compatible")
< 0 && rmozilla.exec( ua ) ||
865 [];
866
867
return { browser: match[1] || "", version: match[2] ||
"0" };
868
899 browsermatch =
jquery.uamatch( useragent );
900 if (
browsermatch.browser ) {
901
jquery.browser[ browsermatch.browser ] = true;
902
jquery.browser.version = browsermatch.version;
903 }
904
905 // dprecated, use
jquery.browser.webkit instead
906 if (
jquery.browser.webkit ) {
907
jquery.browser.safari = true;
908 }
第60~63行:定義用于解析使用者代理navigator.useragent的4個正規表達式:rwebkit、ropera、rmsie、rmozilla。每個正則包含兩個分組:浏覽器類型特征字元和浏覽器版本特征字元。
第858~868行:定義方法jquery.uamatch( ua ),用于解析目前浏覽器的類型和版本号。在方法jquery.uamatch( ua )中,依次嘗試用4個正規表達式比對使用者代理navigator.useragent,并傳回一個包含了比對結果的對象,對象的結構是:
browser: 分組 1 或空字元串,
version: 分組 2 或字元串"0"
第899~903行:調用方法jquery.uamatch( ua )解析使用者代理navigator.useragent,并把解析結果重新封裝為jquery.browser。
2.8.13 小結
構造jquery對象子產品的靜态屬性和方法總結如圖2-9所示。