<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所示。