因为最近在项目当中使用 jQuery
比较多,所以打算抽点时间深入学习一下 jQuery
源码的相关内容,也算是学习笔记记录吧,主要参考的是 jQuery 技术内幕 这本书籍,下面我们就先从总体架构部分开始看起
源码的总体架构 jQuery
整体的架构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 16 (function (window, undefined ) { 22 var jQuery = (function ( ) { 25 var jQuery = function (selector, context ) { 27 return new jQuery.fn.init(selector, context, rootjQuery) 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 jQuery957 })(); 9246 window .jQuery = window .$ = jQuery9266 })(window )
jQuery
的最外层是一个自调用匿名函数,通过定义一个匿名函数,创建了一个私有的命名空间,该命名空间的变量和方法,不会破坏全局的命名空间,参数中传入 window
变量,使得 window
由全局变量变为局部变量,当在 jQuery
代码块中访问 window
时,不需要将作用域链回退到顶层作用域,这样可以更快的访问 window
更重要的是将 window
作为参数传入,可以在压缩代码时进行优化,传入 undefined
是因为在自调用匿名函数的作用域内,确保 undefined
是真的未定义,因为 undefined
在某些浏览器下是能够被重写或是被赋予新的值
下面我们就先来看看它最为重要的一个方法,也就是构造函数 jQuery()
,它一共有七种用法,如下
jQuery(selector, context) 接收一个 CSS
选择器表达式(selector
)和可选的选择器上下文(Context
),返回一个包含了匹配的 DOM
元素的 jQuery
对象,例如,在一个事件监听函数中,可以像下面这样限制查找范围
1 2 3 4 $('div.foo' ).click(function ( ) { $('span' , this ).addClass('bar' ) });
如果选择器表达式 selector
是简单的 "#id"
,且没有指定上下文 Context
,则调用浏览器原生方法 document.getElementById()
查找属性 id
等于指定值的元素
如果是比 "#id"
复杂的选择器表达式或指定了上下文,则通过 jQuery
方法 .find()
查找,因此 $('span', this)
等价于 $(this).find('span')
jQuery(html, ownerDocument) 和 jQuery(html, props) 用所提供的 HTML
代码创建 DOM
元素
1 2 3 4 5 6 7 8 $('<div><span>foo</span></div>' ).appendTo('body' )
jQuery(element or elementsArray) 如果传入一个 DOM
元素或 DOM
元素数组,则把 DOM
元素封装到 jQuery
对象中并返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $('li' ).each(function (index, ele ) { $(ele).on('click' , function ( ) { $(this ).css('background' , 'red' ) }) }) var aLi = document .getElementsByTagName('li' )aLi = [].slice.call(aLi) var $aLi = $(aLi)$aLi.html(`我是 jQuery 对象` )
jQuery(object) 如果传入一个普通 JavaScript
对象,则把该对象封装到 jQuery
对象中并返回
1 2 3 4 5 6 7 8 9 10 11 12 13 var foo = { foo : 'bar' , hello : 'world' }var $foo = $(foo)$foo.on('custom' , function ( ) { console .log(`custom event was called` ) }); $foo.trigger('custom' )
jQuery(callback) 当传进去的参数是函数的时候,则在 document
对象上绑定一个 ready
事件监听函数,当 DOM
结构加载完成的时候执行
1 2 3 4 5 6 $(function ( ) { }) $(document ).ready(function ( ) { })
jQuery(jQuery object) 如果传入一个 jQuery
对象,则创建该 jQuery
对象的一个副本并返回,副本与传入的 jQuery
对象引用完全相同的 DOM
元素
jQuery() 如果不传入任何的参数,则返回一个空的 jQuery
对象,属性 length
为 0
,这个功能可以用来复用 jQuery
对象,例如,创建一个空的 jQuery
对象,然后在需要时先手动修改其中的元素,再调用 jQuery
方法,从而避免重复创建 jQuery
对象
jQuery.fn.init() 在了解完 jQuery
的总体架构以后,我们下面再来深入的了解一下初始化方法 jQuery.fn.init()
的构成,具体源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 16 (function (window, undefined ) { 22 var jQuery = (function ( ) { 25 var jQuery = function (selector, context ) { 27 return new jQuery.fn.init(selector, context, rootjQuery) 28 }, 322 jQuery.fn.init.prototype = jQuery.fn955 return jQuery957 })()9266 })(window )
我们可以发现,这里有一个 return new jQuery.fn.init()
操作,那么为什么要这样操作呢?其实在 JavaScript
当中我们知道,如果构造函数有返回值,运算符 new
所创建的对象会被丢弃,返回值将作为 new
表达式的值,所以 jQuery
通过在构造函数 jQuery()
内部用运算符 new
创建并返回另一个构造函数的实例,省去了构造函数 jQuery()
前面的运算符 new
,即创建 jQuery
对象时,可以省略运算符 new
直接写 jQuery()
或者 $()
下面我们就来具体看看 jQuery.fn.init()
方法的构成
jQuery.fn.init() 和 jQuery.fn.init.prototype = jQuery.fn 源码如下
1 2 3 4 5 6 7 8 9 10 11 (function (window, undefined ) { jQuery.fn.init.prototype = jQuery.fn window .jQuery = window .$ = jQuery })(window )
当我们在调用 jQuery
构造函数时,实际返回的是 jQuery.fn.init()
的实例,在执行 jQuery.fn.init.prototype = jQuery.fn
时,用构造函数 jQuery()
的原型对象覆盖了构造函数 jQuery.fn.init()
的原型对象,从而使构造函数 jQuery.fn.init()
的实例也可以访问构造函数 jQuery()
的原型方法和属性,那么这里就存在一个问题了
为什么要覆盖构造函数 jQuery()
的原型对象 jQuery.prototype
因为在原型对象 jQuery.prototype
上定义的属性和方法会被所有 jQuery
对象继承,这样可以有效减少每个 jQuery
对象所需的内存,下面我们就正式来看一下 jQuery.fn.init()
这个方法
jQuery.fn.init(selector, context, rootjQuery) 构造函数 jQuery.fn.init()
负责解析参数 selector
和 Context
的类型,并执行相应的逻辑,最后返回 jQuery.fn.init()
的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 init: function (selector, context, rootjQuery ) { var match, elem, ret, doc if (!selector) { return this } if (selector.nodeType) { this .context = this [0 ] = selector this .length = 1 return this } if (selector === 'body' && !context && document .body) { this .context = document this [0 ] = document .body this .selector = selector this .length = 1 return this } if (typeof selector === 'string' ) { if (selector.charAt(0 ) === "<" && selector.charAt(selector.length - 1 ) === ">" && selector.length >= 3 ) { match = [null , selector, null ] } else { match = quickExpr.exec(selector) } if (match && (match[1 ] || !context)) { if (match[1 ]) { context = context instanceof jQuery ? context[0 ] : context doc = (context ? context.ownerDocument || context : document ) 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) } else { elem = document .getElementById(match[2 ]) if (elem && elem.parentNode) { if (elem.id !== match[2 ]) { return rootjQuery.find(selector) } this .length = 1 this [0 ] = elem } this .context = document this .selector = selector return this } } else if (!context || context.jquery) { return (context || rootjQuery).find(selector) } else { return this .constructor(context).find(selector) } } 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 ) },
辅助方法 关于架构方面的内容我们暂时就介绍这么多,下面我们再来看看 jQuery
当中的一些辅助方法,而这些辅助方法的实现方式也是我们在平时开发过程中可以去借鉴使用的,下面我们就先来看一些在源码当中经常可以看到的辅助方法
jQuery.noConflict([removeAll])
jQuery.isFunction(obj)
jQuery.isArray(obj)
jQuery.type(obj)
jQuery.isWindow(obj)
jQuery.isNumeric(value)
jQuery.isPlainObject(object)
jQuery.makeArray(obj)
jQuery.inArray(value, array[, fromIndex])
jQuery.merge(first, second)
jQuery.grep(array, function(elementOfArray, indexInArray)[, invert])
jQuery.noConflict([removeAll]) 方法 jQuery.noConflict([removeAll])
用于释放 jQuery
对全局变量 $
的控制权,可选参数 removeAll
表示是否释放对全局变量 jQuery
的控制权,$
仅仅是 jQuery
的别名,没有 $
,其余功能也是可以正常使用的(使用 jQuery
),如果需要使用另一个 JavaScript
库,可以调用 $.noConflict()
返回 $
给其他库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 _jQuery = window .jQuery, _$ = window .$, jQuery.extend({ noConflict: function (deep ) { if (window .$ === jQuery) { window .$ = _$ } if (deep && window .jQuery === jQuery) { window .jQuery = _jQuery } return jQuery } }), window .jQuery = window .$ = jQuery
如果有必要(例如,在一个页面中使用多个版本的 jQuery
库,但很少有这样的必要),也可以释放全局变量 jQuery
的控制权,只需要给这个方法传入参数 true
即可,从 jQuery 1.6
开始增加了对 window.$ === jQuery
的检测,如果不检测,则每次调用 jQuery.noConflict()
时都会释放 $
给前一个 JavaScript
库,不过建议页面当中还是只保持一个对于 $
的引用,因为当页面中有两个以上定义了 $
的 JavaScript
库时,对 $
的管理将会变得混乱
jQuery.isFunction(obj), jQuery.isArray(obj) 这两个方法主要用于判断传入的参数是否是函数(数组),这两个方法的实现依赖方法 jQuery.type(obj)
,通过返回值是否是 function
或者 Array
来进行判断
1 2 3 4 5 6 7 isFunction: function (obj ) { return jQuery.type(obj) === 'function' }, isArray: Array .isArray || function (obj ) { return jQuery.type(obj) === 'array' },
jQuery.type(obj) 这个方法主要用于判断参数的 JavaScript
类型,在平常的开发当中也是经常会遇到的,如果参数是 undefined
或 null
,返回 'undefined'
或 'null'
(注意是字符串类型),如果参数是内部对象,则返回对应的字符串名称,其他一律返回 Object
1 2 3 4 5 6 7 8 9 10 11 12 13 type: function (obj ) { return obj == null ? String (obj) : class2type[toString.call(obj)] || 'object' },
下面是原型方法 toString()
和 class2type
的定义及初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 toString = Object .prototype.toString, class2type = {} jQuery.each('Boolean Number String Function Array Date RegExp Object' ).split(' ' ), function (i, name ) { class2type['[object' + name + ']' ] = name.toLowerCase() } { '[object Array]' : 'array' '[object Boolean]' : 'boolean' '[object Date]' : 'date' '[object Function]' : 'function' '[object Number]' : 'number' '[object Object]' : 'object' '[object RegExp]' : 'regexp' '[object String]' : 'string' }
jQuery.isWindow(obj) 这个方法主要用于用于判断传入的参数是否是 window
对象,通过检测是否存在特征属性 setInterval
来实现
1 2 3 4 5 6 7 8 9 isWindow: function (obj ) { return obj && typeof obj === 'object' && 'setInterval' in obj }, isWindow: function (obj ) { return obj != null && obj == obj.window }
jQuery.isNumeric(value) 这个方法主要用于用于判断传入的参数是否是数字,或者看起来是否像数字
1 2 3 isNumeric: function (obj ) { return !isNaN (parseFloat (obj)) && isFinite (obj) },
先用 parseFloat(obj)
尝试把参数解析为数字,然后判断其是否合法,然后在使用 isFinite(obj)
判断其是否是有限的,均通过验证则返回 true
jQuery.isPlainObject(object) 用于判断传入的参数是否为纯粹的对象,即 {}
或 new Object()
创建的对象(使用 Object.create(null)
创建的空对象也是属于纯粹的对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 isPlainObject: function (obj ) { if (!obj || jQuery.type(obj) !== 'object' || obj.nodeType || jQuery.isWindow(obj)) { return false } try { if (obj.constructor && !hasOwn.call(obj, 'constructor' ) && !hasOwn.call(obj.constructor.property, 'isPrototypeOf' )) { return false } } catch (e) { return false } var key for (key in obj) { } return key === undefined || hasOwn.call(obj, key) },
如果参数 obj
满足下列条件之一,则返回 false
参数 obj
可以转换为 false
Object.prototype.toString.call(obj)
返回的不是 [object, Object]
参数 obj
是 DOM
元素
参数 obj
是 window
对象
如果不满足以上所有条件,则至少可以确定参数 obj
是对象,然后使用 try-catch
来检查对象 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)
,用于检查对象是否含有执行名称的非继承属性,而最后的 for-in
则是检查对象 obj
的属性是否都是非继承属性,如果没有属性,或者所有属性都是非继承属性,则返回 true
,如果含有继承属性,则返回 false
,执行 for-in
循环时,JavaScript
会先枚举非继承属性,再枚举从原型对象继承的属性
最后,如果对象 obj
的最后一个属性是非继承属性,则认为所有属性都是非继承属性,返回 true
,如果最后一个属性是继承属性,即含有继承属性,则返回 false
jQuery.makeArray(obj) 可以将一个类数组对象转换为真正的数组(类似于方法 Array.from()
),在 jQuery
内部,还可以为方法 jQuery.makeArray()
传入第二个参数,这样一来,第一个参数中的元素被合并入第二个参数,最后会返回第二个参数,此时返回值的类型不一定是真正的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 push = Array .prototype.push, makeArray: function (array, results ) { var ret = results || [] if (array != null ) { 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 }
jQuery.inArray(value, array[, fromIndex]) 在数组中查找指定的元素并返回其下标,未找到则返回 -1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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++) { if (i in array && array[i] === elem) { return i } } } return -1 }
通常我们会比较 jQuery.inArray()
的返回值是否大于 0
来判断某个元素是否是数组张的元素
1 2 3 if (jQuery.inArray(elem, array) > 0 ) { }
但是这种写法比较繁琐,可以利用按位非运算符(~
)简化上面的代码
1 2 3 if (~jQuery.inArray(elem, aray)) { }
按位非运算符(~
)会将运算数的所有位取反,相当于改变它的符号并且减 1
1 2 3 4 ~-1 == 0 ~0 == -1 ~1 == -2 ~2 == -3
更进一步,可以结合使用按位非运算符(~
)和逻辑非运算符(!
)把 jQuery.inArray()
的返回值转换为布尔类型
1 2 3 !!~jQuery.inArray(elem, array)
jQuery.merge(first, second) 方法 jQuery.merge()
用于合并两个数组的元素到第一个数组中,事实上,第一个参数可以是数组或类数组对象,即必须含有整型(或可以转换为整型)属性 length
,第二个参数则可以是数组,类数组对象或任何含有连续整型的对象,合并行为是不可逆的,即将第二个数组合并到第一个以后,第一个数组就改变了,如果不希望如此,则可以在调用 jQuery.merge()
之前创建一份数组的备份
1 var newArray = $.merge([], oldArray)
方法 jQuery.merge()
的定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 }
jQuery.grep(array, function(elementOfArray, indexInArray)[, invert]) 用于查找数组当中满足过滤函数的元素,原数组不会受影响,如果参数 invert
没有传入或者为 false
元素只有在过滤函数返回 true
,或者返回值可以转换为 true
的时候,才会被保存在最终的结果数组中,即返回一个满足回调函数的元素数组,如果参数 invert
为 true
,则反之
1 2 3 4 5 6 7 8 9 10 11 12 13 14 grep: function (elems, callback, inv ) { var ret = [], retVal, inv = !!inv for (var i = 0 ; length = elems.length, i < length; i++) { retVal = !!callback(elems[i], i) if (inv !== retVal) { ret.push(elems[i]) } } return ret }
其他方法 我们在上面介绍了源码当中的辅助函数,下面我们再来看看构造 jQuery
对象模块的原型属性和方法,因为它们当中某些方法都是依赖于其他的方法来进行实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 jQuery.fn = jQuery.prototype .constructor .init(selector, context, rootjQuery) .selector .jquery .length .size() .toArray() .get([index]) .pushStack(elements, name, arguments ) .each(function (index, Element )) // 遍历当前 jQuery 对象中的元素,并在每个元素上执行回调函数 .ready (handler ) // 绑定 ready 事件 .eq (index ) // 将匹配元素集合缩减为位于指定位置的新元素 .first ( ) // 将匹配元素集合缩减为集合中的第一个元素 .last ( ) // 将匹配元素集合缩减为集合中的最后一个元素 .slice ( ) // 将匹配元素集合缩减为指定范围的子集 .map (callback(index, domElement )) // 遍历当前 jQuery 对象中的元素,并在每个元素上执行回调函数, 将回调函数的返回值放入一个新的 jQuery 对象中 .end ( ) // 结束当前链条中最近的筛选操作,并将匹配元素集合还原为之前的状态 .push ( ) // Array .prototype .push .sort() // [].sort .splice()
createSafeFragment(document) 该方法的主要作用是用于处理兼容问题
1 2 3 4 5 6 7 8 9 10 11 12 13 function createSafeFragment (document ) { var list = nodeNames.split('|' ) safeFrag = document .createDocumentFragment() if (safeFrag.createElement) { while (list.length) { safeFrag.createElement(list.pop()) } } return safeFrag } var nodeNames = 'abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|' + 'header|hgroup|mark|meter|nav|output|progress|section|summary|time|video'
变量 nodeNames
中存放了所有的 html5
标签,createSafeFragment()
在传入的文档对象 document
上创建一个新的文档片段,然后在该文档片段上逐个创建 html5
元素,从而兼容不支持 html5
的浏览器,使之正确的解析和渲染
fixDefaultChecked(elem) 主要用于修正复选框和单选按钮的选中状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function fixDefaultChecked (elem ) { if (elem.type === 'checkbox' || elem.type === 'radio' ) { elem.defaultChecked = elem.checked } } function findInputs (elem ) { var nodeName = (elem.nodeName || '' ).toLowerCase() if (nodeName === 'input' ) { fixDefaultChecked(elem) } else if (nodeName !== 'script' && typeof elem.getElementsByTagName !== 'undefined' ) { jQuery.grep(elem.getElementsByTagName('input' ), fixDefaultChecked) } }
遍历转换后的 DOM
元素集合,在每个元素上调用函数 findInputs(elem)
,函数 findInputs(elem)
会找出其中的复选框和单选按钮,并调用函数 fixDefaultChecked(elem)
把属性 checked
的值赋值给属性 defaultChecked
.pushStack(elements, name, arguments) 原型方法 .pushStack()
创建一个新的空 jQuery
对象,然后把 DOM
元素集合放进这个 jQuery
对象中,并保留对当前 jQuery
对象的引用,它对 jQuery
对象遍历,DOM
查找,DOM
遍历,DOM
插入等方法提供支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 pushStack: function (elems, name, selector ) { var ret = this .constructor() if (jQuery.isArray(elems)) { push.apply(ret, elems) } else { jQuery.merge(ret, elems) } 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 ret }
.end() 结束当前链中最近的筛选操作,并将匹配元素集合还原为之前的状态
1 2 3 end: function ( ) { return this .prevObject || this .constructor(null ) }
返回一个 jQuery
对象,如果属性 prevObject
不存在,则构建一个空的 jQuery
对象返回,简单来说就是方法 pushStach()
用于入栈,而 end()
则用于出栈,比如
1 2 3 4 $('ul li' ).find('div' ).css('backgroundColor' ,'red' ) .end() .find('span' )css('backgroundColor' ,'blue' ) .end()
.eq(index),.first(),.last(),.slice(start[, end]) 方法 .first()
和 .last()
通过调用 .eq(index)
实现,.eq(index)
主要通过 .slice(start[, end])
来实现,而 .slice(start[, end])
则是通过调用 .pushStack(elements, name, arguments)
实现,方法调用链为依次如下
.first()/last()
==> .eq(index)
==> .slice(start[, end])
==> .pushStack(elements, name, arguments)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 eq: function (i ) { i = +i return i === -1 ? this .slice(i) : this .slice(i, i + 1 ) }, first: function ( ) { return this .eq(0 ) }, last: function ( ) { return this .eq(-1 ) }, slice: function ( ) { return this .pushStack(slice.apply(this , arguments ), 'slice' , slice.call(arguments ).join(',' )) }
.push(value, …), .sort([orderfunc]), .splice(start,deleteCount, value, …) 方法 .push(value, ...)
向当前 jQuery
对象的末尾添加新元素,并返回新长度
1 2 var foo = $(document )foo.push(document .body)
方法 .sort([orderfunc])
对当前 jQuery
对象中的元素进行排序,可以传入一个比较函数来指定排序方式
1 2 3 4 5 6 7 8 var foo = $([33 , 4 , 1111 , 222 ])foo.sort() foo.sort(function (a, b ) { return a - b })
方法 .splice(start, deleteCount, value, ...)
向当前 jQuery
对象中插入, 删除或替换元素,如果从当前 jQuery
对象中删除了元素,则返回含有被删除元素的数组
1 2 3 4 5 var foo = $('<div id="d1" /><div id="d2" /><div id="d3" />' )foo.splice(1 , 2 )
以上三个方法仅在内部使用,都指向同名的数组方法,因此它们的参数,功能和返回值与数组方法完全一致
1 2 3 push: push, sort: [].sort, splice: [].splice
jQuery.each(object, callback, args) 关于 each()
方法有一个需要注意的地方,那就是要区分 $.each()
和 $(selector).each()
两者之间的区别,$(selector).each()
一般用于 jQuery
对象的遍历,它会为每个匹配元素规定要运行的函数
1 2 3 $('ul li' ).each(function ( ) { alert($(this ).text()) })
通过源码可知,each
方法实际上调用的就是 jQuery.each()
方法
1 2 3 4 5 6 7 each: function (callback, args ) { return jQuery.each(this , callback, args) }
而 $.each()
使用的范围就很广了,可用于遍历任何的集合(无论是数组或对象),下面是几个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var arr = [{ name : 'zhangsan' , email : 'zhangsan@gmail.com' }, { name : 'lisi' , email : 'lisi@gmail.com' }]$.each(arr, function (i, n ) { console .log(`索引: ${i} 对应值为:${n.name} ` ) }) var arr1 = ['one' , 'two' , 'three' , 'four' , 'five' ]$.each(arr1, function ( ) { console .log(this ) }) var arr2 = [[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]]$.each(arr2, function (i, item ) { console .log(item[0 ]) }) var obj = { one : 1 , two : 2 , three : 3 , four : 4 , five : 5 }$.each(obj, function (key, val ) { console .log(obj[key]) })
方法 each()
遍历当前 jQuery
对象,并在每个元素上执行回调函数,每当回调函数执行时,会传递当前循环次数作为参数,循环次数从 0
开始计数,更重要的是,回调函数是在当前元素为上下文的语境中触发的,即关键字 this
总是指向当前元素,在回调函数中返回 false
可以终止遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 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 } } } } else { if (isObj) { for (name in object) { if (callback.call(object[name], name, object[name]) === false ) { break } } } else { for (var value = object[0 ]; i < length && callback.call(value, i, value) !== false ; value = object[++i]) { } } } return object }
jQuery.extend([deep], target, object1[, objectN]) 先来看看怎么使用
1 2 3 4 $.extend(target [, object1 ] [, objectN ]) $.extend([deep ], target, object1 [, objectN ])
需要注意的是,第一个参数不支持传递 false
取值
释义
deep
可选,布尔类型,指示是否深度合并对象,默认为 false
,如果该值为 true
,且多个对象的某个同名属性也都是对象,则该对象的属性也将进行合并
target
Object
类型,目标对象,其他对象的成员属性将被附加到该对象上
object1
可选,Object
类型,第一个被合并的对象
objectN
可选,Object
类型,第 N
个被合并的对象
简单来说,该方法的作用是用一个或多个其他对象来扩展一个对象,返回扩展后的对象,如果不指定 target
,则是给 jQuery
命名空间本身进行扩展(有利于为 jQuery
增加新方法),如果第一个参数设置为 true
,则 jQuery
返回一个深层次的副本,递归的复制找到的任何对象,否则的话副本会与原对象共享结构
未定义的属性不会被复制,然而从对象的原型继承的属性将会被复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 jQuery.extend = jQuery.fn.extend = function ( ) { var options, name, src, copy, copyIsArray, clone, target = arguments [0 ] || {}, i = 1 , length = arguments .length, deep = false if (typeof target === "boolean" ) { deep = target target = arguments [1 ] || {} i = 2 } if (typeof target !== "object" && !jQuery.isFunction(target)) { target = {} } if (length === i) { target = this --i } for (; i < length; i++) { if ((options = arguments [i]) !== null ) { for (name in options) { src = target[name] copy = options[name] if (target === copy) { continue } if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { if (copyIsArray) { copyIsArray = false clone = src && jQuery.isArray(src) ? src : [] } else { clone = src && jQuery.isPlainObject(src) ? src : [] } target[name] = jQuery.extend(deep, clone, copy) } else if (copy !== undefined ) { target[name] = copy } } } } }
jQuery.buildFragment(args, nodes, scripts) jQuery.buildFragment()
是一个私有函数,用来构建一个包含子节点 fragment
对象,但是关于 jQuery.buildFragment(args, nodes, scripts)
方法有一些需要注意的地方
如果 HTML
代码符合缓存条件,则尝试从缓存对象 jQuery.fragments
中读取缓存的 DOM
元素
创建文档片段 DocumentFragment
调用方法 jQuery.clean(elems, context, fragment, scripts)
将 HTML
代码转换为 DOM
元素,并存储在创建的文档片段中
如果 HTML
代码符合缓存条件,则把转换后的 DOM
元素放入缓存对象 jQuery.fragments
最后返回文档片段和缓存状态 {fragment: fragment, cacheable: cacheable}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 jQuery.buildFragment = function (args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc, first = args[0 ] if (nodes && nodes[0 ]) { doc = nodes[0 ].ownerDocument || nodes[0 ] } if (!doc.createDocumentFragment) { doc = document } if (args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && first.charAt(0 ) === "<" && !rnocache.test(first) && (jQuery.support.checkClone || !rchecked.test(first)) && (jQuery.support.html5Clone || !rnoshimcache.test(first))) { cacheable = true cacheresults = jQuery.fragments[first] if (cacheresults && cacheresults !== 1 ) { fragment = cacheresults } } jQuery.fragments = {} if (!fragment) { fragment = doc.createDocumentFragment() jQuery.clean(args, doc, fragment, scripts) } if (cacheable) { jQuery.fragments[first] = cacheresults ? fragment : 1 } return { fragment : fragment, cacheable : cacheable } }
jQuery.buildFragment()
的用法总结为
如果 HTML
代码不符合缓存条件,则总是会执行转换过程
如果 HTML
代码符合缓存条件,第一次转换后设置缓存值为 1
,第二次转换后设置为文档片段,从第三次开始则从缓存中读取
jQuery.clean(elems, context, fragment, scripts) 方法 jQuery.clean(elems, context, fragment, scripts)
负责把 HTML
代码转换成 DOM
元素,并提取其中的 script
元素
创建一个临时 div
元素,并插入一个安全文档片段中
为 HTML
代码包裹必要的父标签,然后用 innerHTML
赋值给临时 div
,从而将 HTML
代码转换为 DOM
元素,之后再层层剥去包裹的父元素,得到转换后的 DOM
元素
移除 IE 6/7
自动插入的空 tbody
元素,插入 IE 6/7/8
自动剔除的前导空白符
取到转换后的 DOM
元素集合
在 IE 6/7
中修正复选框和单选按钮的选中状态
合并转换后的 DOM
元素
如果传入了文档片段 fragment
,则提取所有合法的 script
元素存入数组 scripts
,并把其他元素插入文档片段 fragment
最后返回转换后的 DOM
元素数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 clean: function (elems, context, fragment, scripts ) { var checkScriptType context = context || document if (typeof context.createElement === 'undefined' ) { context = context.ownerDocument || context[0 ] && context[0 ].ownerDocument || document } var ret = [], j for (var i = 0 , elem; (elem = elems[i]) != null ; i++) { if (typeof elem === 'number' ) { elem += '' } if (!elem) { continue } if (typeof elem === 'string' ) { if (!rhtml.test(elem)) { elem = context.createTextNode(elem) } else { elem = elem.replace(rxhtmlTag, '<$1></$2>' ) var tag = (rtagName.exec(elem) || ['' , '' ])[1 ].toLowerCase(), wrap = wrapMap[tag] || wrapMap._default, deoth = wrap[0 ], div = context.createElement('div' ) if (context === document ) { safeFragment.appendChild(div) } else { createSafeFragment(context).appendChild(div) } div.innerHTML = wrap[1 ] + elem + wrap[2 ] while (depth--) { div = div.lastChild } elem = div.childNodes if (elem.nodeType) { ret.push(elem) } else { ret = jQuery.merge(ret, elem) } } } } if (fragment) { checkScriptType = function (elem ) { return !elem.type || rscriptType.test(elem.type) } for (i = 0 ; ret[i]; i++) { if (scripts && jQuery.nodeName(ret[i], 'script' ) && (!ret[i].type || ret[i].type.toLowerCase() === 'text/javascript' )) { scripts.push(ret[i].parentNode ? ret[i].parentNode.removeChild(ret[i]) : ret[i]) } else { if (ret[i].nodeType === 1 ) { var jsTags = jQuery.grep(ret[i].getElementsByTagName('script' ), checkScriptType) ret.splice.apply(ret, [i + 1 , 0 ].concat(jsTags)) } fragment.appendChild(ret[i]) } } } return ret }