作者:yanyige | 发布时间:2017-03-06 18:08

第一章 构造jQuery对象

jQuery对象是一个类数组对象,含有连续的整型属性、length属性和大量的jQuery方法。jQuery对象由构造函数jQuery()创建,$()则是jQuery()的缩写。

2.1 构造函数jQuery()

如果调用构造函数jQuery时传入的参数不同,创建jQuery对象的逻辑也会不同,构造函数jQuery有7种方法。如下:

2.1.1 jQuery( selector [, context])

如果传入一个字符串参数,jQuery会检查这个字符串是选择器还是html代码,如果是选择器表达式,则遍历文档,查找与之匹配的DOM元素,并创建一个包含了这些DOM元素引用的jQuery对象;如果没有匹配的对象,则会创建一个空jQuery对象。下一节介绍参数是HTML代码的情况。 默认情况下,对匹配元素的查找将会从document对象开始,即查找整个文档树,不过也可以传入第二个参数context来限定查找范围。例如,可以这样限制查找范围。

$('div.foo').click(function() {
    $('span', this).addClass('bar');
});

方法find是调用CSS选择器引擎Sizzle实现,会在第三章进行介绍和分析。

2.1.2 jQuery ( html [,ownerDocument] )、jQuery( html, props )

如果传入的参数是一段html代码,jQuery则会尝试用这段HTML代码创建新的DOM元素,并且创建一个包含了这些DOM元素引入的jQuery对象。 第二个参数ownerDocument用于指定创建的新的DOM元素的文档对象,如果不传入,则默认为当前文档对象。 第二个参数还可以是props。属性可以是任意的时间类型(如”click”)还可以有下面的特殊属性:val, css, html, text, data, width, height, offset, 对应的jQuery方法: .val(), .css(), .html(), .text(), data(), width(), height(), offset()将会执行。举个梨子:

$('<div>', {
    "class": "test",
    text: "click me",
    click: function() {
        $(this).toggleClass("test");
    }
}).appendTo("body")

2.1.3 jQuery( element)、jQuery( elementArray )

如果传入一个DOM元素或者DOM数组,则会把DOM元素封装到jQuery对象中并且返回。

2.1.4 jQuery( object )

如果传入一个普通的jQuery对象,则会把这个对象封装jQuery对象中并且返回。 举个梨子:

var foo = {foo: 'bar', hello: 'world'};

var $foo = $(foo);

$foo.on('custom', function(){
    console.log('custom event is called');
});

$foo.triggle('custom');

2.1.5 jQuery( callback )

如果传入一个函数,则在document上绑定一个ready事件监听函数,当DOM结构加载完成时执行。ready事件触发要早于load事件。ready事件并不是浏览器的原生事件,而是DOMContentLoaded事件、onreadystatechange事件和函数doScrollCheck()的统称。将在后面的学习中介绍。

2.1.6 jQuery( jQuery object )

如果传入一个jQuery对象,则创建这个jQuery对象的一个副本并且返回,副本与传入的jQuery对象引入的同一个元素。

2.2 总体结构

构造jQuery对象模块源码架构如下:

(function( window, undefined ) {
	 // 构造 jQuery 对象
22  var jQuery = (function() {
25  var jQuery = function( selector, context ) {
27  	return new jQuery.fn.init( selector, context, root jQuery );
28  },
	 // 一堆局部变量声明
97  jQuery.fn = jQuery.prototype = {
98  	constructor: jQuery,
99  	init: function( selector, context, rootjQuery ) { ... },
	 	// 一堆原型属性和方法
319 };
322 jQuery.fn.init.prototype = jQuery.fn;
324 jQuery.extend = jQuery.fn.extend = function() { ... };
388 jQuery.extend({
// 一堆静态属性和方法
892  });
955  return jQuery;
957 })();
// 省略其他模块的代码
9246  window.jQuery = window.$ = jQuery;
9266 })( window );

这里解释一下为什么要在第97行执行 jQuery.fn = jQuery.prototype, 设置jQuery.fn指向构造函数jQuery()的原型对象。

jQuery.fn 是jQuery.prototype 的简写,可以少写7 个字符,以方便拼写。

2.3 jQuery.fn.init( selector, context, rootjQuery )

2.3.1 12个分支

2.3.2 源码分析

1.定义jQuery.fn.init( selector, context, rootjQuery )

相关代码如下所示:

99  init: function( selector, context, rootjQuery ) {
100 	var match, elem, ret, doc;

// document.getElementById() 查找失败
172 	return rootjQuery.find( selector );

// selector 是选择器表达式且未指定 context
187 	return ( context || rootjQuery ).find( selector );

// selector 是函数
198 return rootjQuery.ready( selector );

// 定义 rootjQuery
916 // All jQuery objects should point back to these
917 rootjQuery = jQuery(document);

2. 参数selector 可以转换为false

参数selector 可以转换为false,例如是undefined、空字符串、null 等,则直接返回this,此时this 是空jQuery 对象,其属性length 等于0。

3. 参数selector 是DOM元素

如果参数selector 有属性nodeType,则认为selector 是DOM 元素,手动设置第一个元素和属性context指向该DOM元素、属性length为1,然后返回包含了该DOM 元素引用的jQuery对象。

相关代码:

107 // Handle $(DOMElement)
108 if ( selector.nodeType ) {
109 	this.context = this[0] = selector;
110 	this.length = 1;
111 	return this;
112 }
113

4. 参数selector是字符串“body”

如果参数selector 是字符串“ body”,手动设置属性context 指向document 对象、第一个元素指向body元素、属性length 为1,最后返回包含了body元素引用的jQuery 对象。这里是对查找字符串“ body”的优化,因为文档树中只会存在一个body 元素。相关代码如下所示:

114 // The body element only exists once, optimize finding it
115 if ( selector === "body" && !context && document.body ) {
116 	this.context = document;
117 	this[0] = document.body;
118 	this.selector = selector;
119 	this.length = 1;
120 	return this;
121 }
122

5. 参数selector还可以是复杂字符串

此时会根据context参数来返回不同的结果。

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

2.4 jQuery.buildFragment( args, nodes, scripts )

2.4.1 实现原理

方法jQuery.buildFragment( args, nodes, scripts ) 先创建一个文档片段DocumentFragment,然后调用方法jQuery.clean( elems, context, fragment, scripts ) 将HTML 代码转换为DOM 元素,并存储在创建的文档片段中。文档片段DocumentFragment 表示文档的一部分,但不属于文档树。当把DocumentFragment 插入文档树时,插入的不是DocumentFragment 自身,而是它的所有子孙节点,即可以一次向文档树中插入多个节点。当需要插入大量节点时,相比于逐个插入节点,使用ocumentFragment 一次插入多个节点,性能的提升会非常明显。2此外,如果HTML 代码符合缓存条件,方法jQuery.buildFragment() 还会把转换后的DOM 元素缓存起来,下次(实际上是第三次)转换相同的HTML 代码时直接从缓存中读取,不需要重复转换。方法jQuery.buildFragment() 同时为构造jQuery 对象和DOM 操作提供底层支持,DOM操作将在第11章介绍和分析。

2.4.2 源码分析

方法jQuery.buildFragment( args, nodes, scripts ) 执行的5 个关键步骤如下:

  1. 如果HTML 代码符合缓存条件,则尝试从缓存对象jQuery.fragments 中读取缓存的DOM 元素。
  2. 创建文档片段DocumentFragment。
  3. 调用方法jQuery.clean( elems, context, fragment, scripts ) 将HTML 代码转换为DOM元素,并存储在创建的文档片段中。
  4. 如果HTML 代码符合缓存条件,则把转换后的DOM 元素放入缓存对象jQuery.fragments。
  5. 最后返回文档片段和缓存状态{ fragment: fragment, cacheable: cacheable }。

2.5 jQuery.clean( elems, context, fragment, scripts )

2.5.1 实现原理

方法jQuery.clean( elems, context, fragment, scripts ) 负责把HTML代码转换成DOM元素,并提取其中的script元素。该方法先创建一个临时的div元素,并将其插入一个安全文档片段中,然后把HTML代码赋值给div元素的innerHTML属性,浏览器会自动生成DOM元素,最后解析div元素的子元素得到转换后的DOM元素。安全文档片段指能正确渲染HTML5元素的文档片段,通过在文档片段上创建HTML5元素,可以教会浏览器正确地渲染HTML5元素,稍后的源码分析会介绍其实现过程。如果HTML代码中含有需要包裹在父标签中的子标签,例如,子标签

2.5.2 源码分析

方法jQuery.clean( elems, context, fragment, scripts ) 执行的8 个关键步骤如下:

  1. 创建一个临时div 元素,并插入一个安全文档片段中。
  2. 为HTML 代码包裹必要的父标签,然后赋值给临时div 元素的innerHTML 属性,从而将HTML 代码转换为DOM 元素,之后再层层剥去包裹的父元素,得到转换后的DOM 元素。
  3. 移除IE 6/7 自动插入的空tbody 元素,插入IE 6/7/8 自动剔除的前导空白符。
  4. 取到转换后的DOM 元素集合。
  5. 在IE 6/7 中修正复选框和单选按钮的选中状态。
  6. 合并转换后的DOM 元素。
  7. 如果传入了文档片段fragment,则提取所有合法的script 元素存入数组scripts,并把其他元素插入文档片段fragment。
  8. 最后返回转换后的DOM 元素数组。

2.6 jQuery.extend()、jQuery.fn.extend()

2.6.1 如何使用

方法jQuery.extend() 和jQuery.fn.extend() 用于合并两个或多个对象的属性到第一个对 象,它们的语法如下:

jQuery.extend([deep], target, object1[, objectN])
jQuery.fn.extend([deep], target, object1[, objectN])

参数deep表示是一个可选的bool值,表示是否进行递归合并。合并默认是不递归的。 参数target是目标对象;参数object1和objectN是源对象包含了待合并的属性。如果只有一个对象,那么target参数被忽略,jQuery或者jQuery.fn被当做目标对象。

2.7 原型属性和方法

代码如下:

97 jQuery.fn = jQuery.prototype = {
98      constructor: jQuery,
99      init: function( selector, context, rootjQuery ) {}
210         selector: "",
213         jquery: "1.7.1",
216         length: 0,
219         size: function() {},
223         toArray: function() {},
229         get: function( num ) {},
241         pushStack: function( elems, name, selector ) {},
270         each: function( callback, args ) {},
274         ready: function( fn ) {}, //
284         eq: function( i ) {},
291         first: function() {},
295         last: function() {},
299         slice: function() {},
304         map: function( callback ) {},
310         end: function() {},
316         push: push,
317         sort: [].sort,
318         splice:
319 };

2.7.1 .selector、.jquery、.length、.size()

2.7.2 .toArray()、.get( [index] )

方法.toArray() 将当前jQuery 对象转换为真正的数组,转换后的数组包含了所有元素。 方法.toArray() 的实现巧妙地借用了数组的方法slice(),

86 // Save a reference to some core methods
87 toString = Object.prototype.toString,
88 hasOwn = Object.prototype.hasOwnProperty,
89 push = Array.prototype.push,
90 slice = Array.prototype.slice,
91 trim = String.prototype.trim,
92 indexOf = Array.prototype.indexOf,
223 toArray: function() {
224     return slice.call( this, 0 );
225 },

2.7.3 .get( [index] )

方法.get( [index] ) 返回当前jQuery 对象中指定位置的元素或包含了全部元素的数组。 如果没有传入参数,则调用.toArray() 返回包含了所有元素的数组;如果指定了参数index, 则返回一个单独的元素;参数index 从0 开始计算,并且支持负数,负数表示从元素集合末 尾开始计算。相关代码如下所示:

227 // Get the Nth element in the matched element set OR
228 // Get the whole matched element set as a clean array
229 get: function( num ) {
230 return num == null ?
231
232 // Return a 'clean' array
233 this.toArray() :
234
235 // Return just the object
236 ( num < 0 ? this[ this.length + num ] : this[ num ] );
237 },

2.7.3 .each( function(index, Element) )、jQuery.each( collection, callback(indexInArray, valueOfElement) )

1. .each( function(index, Element) )

方法.each() 遍历当前jQuery 对象,并在每个元素上执行回调函数。每当回调函数执行时,会 传递当前循环次数作为参数,循环次数从0 开始计数;更重要的是,回调函数是在当前元素为上 下文的语境中触发的,即关键字this 总是指向当前元素;在回调函数中返回false 可以终止遍历。 方法.each() 内部通过简单的调用静态方法jQuery.each() 实现,相关代码如下所示:

267 // Execute a callback for every element in the matched set.
268 // (You can seed the arguments with an array of args, but this is
269 // only used internally.)
270 each: function( callback, args ) {
271 return jQuery.each( this, callback, args );
272 },

2. jQuery.each( collection, callback(indexInArray, valueOfElement) )

静态方法jQuery.each() 是一个通用的遍历迭代方法,用于无缝地遍历对象和数组。对于 数组和含有length 属性的类数组对象(如函数参数对象arguments),该方法通过下标遍历, 从0 到length-1 ;对于其他对象则通过属性名遍历(for-in)。在遍历过程中,如果回调函数 返回false,则结束遍历。相关代码如下所示:

627 // args is for internal usage only
628 each: function( object, callback, args ) {
629 var name, i = 0,
630 length = object.length,
631 isObj = length === undefined || jQuery.isFunction( object );
632
633 if ( args ) {
634     if ( isObj ) {
635         for ( name in object ) {
636             if ( callback.apply( object[ name ], args ) === false ) {
637                 break;
638             }
639         }
640     } else {
641         for ( ; i < length; ) {
642             if ( callback.apply( object[ i++ ], args ) === false ) {
643                 break;
644             }
645         }
646     }
647
648 // A special, fast, case for the most common use of each
649     } else {
650         if ( isObj ) {
651             for ( name in object ) {
652                 if ( callback.call( object[ name ], name, object[ name ])===false){
653                     break;
654                 }
655             }
656         } else {
657             for ( ; i < length; ) {
658                 if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
659                     break;
660                 }
661             }
662         }
663     }
664
665     return object;
666 },