点开jQuery的核心代码,可以看到其前面定义的很多变量都由正则表达式赋值(觉得想出那么难的正则表达式,都是反人类的存在),这也是我在jQuery学习中插入正则表达式学习的目的:对正则表达式感兴趣的可以前往jQuery源码学习(2)-正则表达式看一下。关于正则,提供网址https://regexper.com/可用于正则表达式意思的查询。
1、源码变量解析
jQuery中定义变量的优点:
- 防止污染全局变量;
- 自定义变量有助于压缩优化;
- 有助于后期维护。
var // A central reference to the root jQuery(document) //rootjQuery定义Jq的根对象,为了可压缩;它的存在使jQuery.fn.find函数和jQuery.fn.ready函数可以级联使用,少传上下文参数。 //eg:$("#main").find(".menuitem").attr("color",red); //为此,最后一行代码,jQuery定义了rootjQuery,同时赋予了它合理的上下文,即document //另外,rootjQuery在jQuery核心类库中仅在jQuery.fn.init函数中被使用。 rootjQuery, // The deferred used on DOM ready //用于新建延迟对象,被用于jQuery.ready.promise中赋值 readyList, // Support: IE9 // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` //将typeof undefined的值"undefined"赋值给这个变量, //因为在IE9及以下对于xmlNode.method是不等于undefined的, //只有比较"undefined"字符串的时候才会相等,考虑兼容性 core_strundefined = typeof undefined, // Use the correct document accordingly with window argument (sandbox) //将一些对象赋值给变量,有利于压缩 // location = window.location, document = window.document, docElem = document.documentElement, //在jQuery初始化的时候保存了外部的$和jQuery //这是为了防止方法外部对$和jQuery赋值,将这些值保存在内部的变量里,不至于丢失。 // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, // [[Class]] -> type pairs //这个变量保存的一些js的类型例如:[{"Object String","string"}]等,以后做类型判断,用到这个数组 class2type = {}, // List of deleted data cache ids, so we can reuse them //在2.0一下的版本,对于data不是做面向对象处理的,在2.0以上才是,所以用到这个,在2.0以上,这个变量没什么太大的用处。 core_deletedIds = [], //版本号 core_version = "2.0.3", // Save a reference to some core methods //下面是对将一下数组方法赋值到这些变量里(减少代码长度), //core_deletedIds这个变量也就是在这用到了, 其他地方没再用到这个变量。 core_concat = core_deletedIds.concat, core_push = core_deletedIds.push, core_slice = core_deletedIds.slice, core_indexOf = core_deletedIds.indexOf, core_toString = class2type.toString, core_hasOwn = class2type.hasOwnProperty, core_trim = core_version.trim, // Define a local copy of jQuery。初始化JQ的方法,可以让我们直接jQuery()来创建init的实例 jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, // Used for matching numbers匹配数字: 正数、负数、科学计数法 core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, // Used for splitting on whitespace用空格 分开单词 core_rnotwhite = /\S+/g, // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, /*通过选择|分割二义,匹配^开头或者$结尾 1、 ^(?:\s*(<[\w\W]+>)[^>]* (?:pattern) : 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用 \s* : 匹配任何空白字符,包括空格、制表符、换页符等等 零次或多次 等价于{0,} (pattern) : 匹配pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,使用 $0…$9 属性 [\w\W]+ : 匹配于'[A-Za-z0-9_]'或[^A-Za-z0-9_]' 一次或多次, 等价{1,} (<[wW]+>) :这个表示字符串里要包含用<>包含的字符,例如<p>,<div>等等都是符合要求的 [^>]* : 负值字符集合,字符串尾部是除了>的任意字符或者没有字符,零次或多次等价于{0,}, 2、#([\w-]*))$ 匹配结尾带上#号的任意字符,包括下划线与- 3、 还要穿插一下exec方法 如果执行exec方法的正则表达式没有分组(没有括号括起来的内容), 那么如果有匹配,他将返回一个只有一个元素的数组, 这个数组唯一的元素就是该正则表达式匹配的第一个串;如果没有匹配则返回null。 exec如果找到了匹配,而且包含分组的话,返回的数组将包含多个元素, 第一个元素是找到的匹配,之后的元素依次为该匹配中的第一、第二...个分组(反向引用) 所以综合起来呢大概的意思就是:匹配HTML标记和ID表达式 (<前面可以匹配任何空白字符,包括空格、制表符、换页符等等) //防止通过url后边加入XSS注入木马,匹配一个标签或#id */ // Match a standalone tag,匹配成对的标签 rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, /* ^<(\w+)\s*\/?> : 以<开头,至少跟着一个字符和任意个空白字符,之后出现0或1次/> (?:<\/\1>|)$ : 可以匹配<、一个/或者空白并以之为结尾 */ // Matches dashed string for camelizing //浏览器内核,因为ie比较特殊,所以做专门处理。 //-webkit-margin-left : webkitMarginLeft //-ms-margin-left : MsMarginLeft rmsPrefix = /^-ms-/, //IE中的前缀 -ms 转成: Ms rdashAlpha = /-([\da-z])/gi, //转大小写 -left 转成 Left // Used by jQuery.camelCase as callback to replace() //下面这个方法是用于进行驼峰转换的,例如 case-int 可以转换为caseInt这种操作。 fcamelCase = function(all, letter) { return letter.toUpperCase(); }, // The ready event handler and self cleanup method //DOM加载成功触发的方法 completed = function() { document.removeEventListener("DOMContentLoaded", completed, false); window.removeEventListener("load", completed, false); jQuery.ready(); };
2、工具函数解析
参考博客地址:http://www.cnblogs.com/y8932809/p/5869288.html,分析得很清楚,适合新手
2.1 所有的工具方法都通过extend方法继承到jQuery中,其中定义的函数清单如下:
jQuery.extend({
expando:生成唯一的jQuery字符串(内部使用)
noConflict():避免冲突
isReady:DOM是否已经加载完(内部使用)
readyWait():等待多少文件的计时器(内部使用)
holdReady()::推迟DOM触发
ready():准备DOM触发
isFunction():是否为函数
isArray():是否为数组
isWindow():是否为window
isNumeric()是否为数字
type():判断数据类型
isPlainObject():是否为对象自变量
isEmptyObject():是否为空的对象
error();抛出异常
parseHTML():解析节点
parseJSON():解析JSON
parseXML:解析XML
noop():空函数
globalEval():全局解析JS
camelCase():转驼峰
nodeName():是否为指定节点名(内部)
each():遍历集合
trim():去前后空格
makeArray():类数组转换真数组
inArray():数组版indexOf
merge():合并数组
grep():过滤新数组
map():映射新数组
guid():唯一表示(内部)
proxy():改变this指向
access(): 多功能值操作
now():当前时间
swap():css交换(内部)
});
jQuery.ready.promise = function( obj ) {}检测dom的异步操作
2.2 expando和noConflict()的源码:
expando这个属性每次进行访问都是不同的值,在jQuery内部,有时会需要一个唯一的随机串来进行区分,例如缓存方法,ajax方法等。
noConflict方法主要是用来防止冲突,有时在页面中可能引入了多种框架,那就无法避免在其他框架中对$符号的占用,所以这个方法就起到了作用。
//扩展工具函数 jQuery.extend({ // Unique for each copy of jQuery on the page // 版本号+随机数 // 生成唯一的jQuery字符串,因为可能一个页面引入多个版本的jQuery expando: "jQuery" + (core_version + Math.random()).replace(/\D/g, ""), /* 调用noConflict将$甚至jQuery的使用权让渡出去。返回的jQuery保存为自定义的变量。如 var myJq = $.noConflict(); 然后就可以将myJq当成jQuery来使用。 var ps = myJq("p");//得到所有p标签的元素集合。 http://www.w3school.com.cn/jquery/core_noconflict.asp deep参数是一个bool值,如果传入的值为true的话,就代表对jQuery进行弃用 */ noConflict: function(deep) { //交出$的控制权 //首先判断挂载到window上的$符号是否和jQuery相等, //如果一致,则将_$符号重新对window上的$符进行覆盖 if (window.$ === jQuery) { window.$ = _$; } //交出jQuery的控制权 //如果传入的为true,则将_jQuery覆盖到window上的jQuery对象。 if (deep && window.jQuery === jQuery) { window.jQuery = _jQuery; } return jQuery; },
2.3 DOM加载相关的工具方法
用到的方法:
isReady:DOM是否已经加载完(内部使用)
readyWait():等待多少文件的计时器(内部使用)
holdReady()::推迟DOM触发
ready():准备DOM触发
jQuery.ready.promise = function( obj ) {}检测dom的异步操作
先看一下jQuery和原生js加载方式有什么不同:
$(function () { }); window.onload = function () { };
jQuery是等待页面中所有的dom加载完毕,而原生JavaScript的onload方法,则是等待页面所有的元素加载完毕,
例如:一个img标签,jQuery只等这个img标签加载完毕,不必等到src中所引用的图片加载完毕,但onload方法,则必须等到src中的图片加载完毕才可以。
下面看一下jQuery的加载流程图:
前面已经说过 $(function () {})和rootjQuery.ready( selector )是相等的,也就是说$(function () {})这种形式写法,其实最后也被转换成rootjQuery.ready( selector )。而rootjQuery.ready( selector )这个实例方法里,又是调用的工具方法中的jQuery.ready.promise().done( fn ),所以先从这个方法开始分析,代码如下:
//被实例方法ready()方法调用 jQuery.ready.promise = function(obj) { if (!readyList) { readyList = jQuery.Deferred(); // Catch cases where $(document).ready() is called after the browser event has already occurred. // we once tried to use readyState "interactive" here, but it caused issues like the one // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 if (document.readyState === "complete") { //运行到这时,页面中的dom已经加载完毕了,然后运行setTimeout( jQuery.ready ) // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout(jQuery.ready); //setTimeout方法,是为了兼容ie的hack方法 } else { //readyState 不等于complete,添加两个回调方法, //这里添加两个的原因,是因为在火狐等浏览器中,会缓存load方法, //所以就会先调用load方法,所以这里添加了两个回调, //保证了在第一时间内调用complete方法。 // Use the handy event callback document.addEventListener("DOMContentLoaded", completed, false); // A fallback to window.onload, that will always work window.addEventListener("load", completed, false); } } return readyList.promise(obj); };
回调里的complete方法(在本篇博客的1中,可在1中查看):
//DOM加载成功触发的方法,在jQuery.ready.promise方法中调用 completed = function() { //无论哪种方法进行了回调,都会在这里将事件解绑, //所以这里也就保证了只回调一次,最后又调用了jQuery.ready()方法。 //这也说明,其实在jQuery.ready.promise方法中,无论条件如何,都会调用jQuery.ready()工具方法。 document.removeEventListener("DOMContentLoaded", completed, false); window.removeEventListener("load", completed, false); jQuery.ready(); };
在说工具函数ready()方法前,先来说明一下holdReady方法。
//DOM是否已经加载完 isReady: false, // 一个计数器,用于跟踪在ready事件出发前的等待次数 // the ready event fires. See #6781 readyWait: 1, // 继续等待或DOM触发 holdReady: function(hold) { if (hold) { jQuery.readyWait++; } else { //如果传入的参数为false,则执行ready方法,并传入true jQuery.ready(true); } },方法内部有个计数器,这也就说明了,当页面加载多个js文件时,可以进行多次调用,保证所有文件都加载完毕,才可以继续运行。
如果传入的参数为false,则执行ready方法,并传入true:
ready()方法源码:
//文档加载完毕句柄,准备DOM触发 //http://www.cnblogs.com/fjzhou/archive/2011/05/31/jquery-source-4.html ready: function(wait) { // Abort if there are pending holds or we're already ready //如果为true,则将计数器减一,否则判断isReady变量 if (wait === true ? --jQuery.readyWait : jQuery.isReady) { return; } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be //如果计数器还大于0的话,则继续等待。 if (wait !== true && --jQuery.readyWait > 0) { return; } // If there are functions bound, to execute //开始运行延时的方法了,第一个参数指定节点,第二个是将jQuery传入 readyList.resolveWith(document, [jQuery]); // Trigger any bound ready events //这段代码是考虑到另一种加载方式: /* $(document).on('ready', function () { alert(1); }) */ //如果将这段代码注释,可以看到,这种方式就无法运行了。 if (jQuery.fn.trigger) { jQuery(document).trigger("ready").off("ready"); } //所以这也说明了,jQuery其实有三种写法: /* $(function () {}) $(document).ready(function () { }) $(document).on('ready', function () {}) */ },
2.4 判断数据和对象方法解析:
isFunction():是否为函数
isArray():是否为数组
isWindow():是否为window
isNumeric()是否为数字
type():判断数据类型
isPlainObject():是否为对象自变量
isEmptyObject():是否为空的对象
源码:
//在IE老的版本浏览器中,从1.3以后就对DOM的方法和原生的方法不在支持了,这时返回的是false。 isFunction: function(obj) { //是否函数,返回boolean值 return jQuery.type(obj) === "function"; }, //用浏览器内置的Array.isArray实现,1.6.0版本还有对浏览器自身实现方式的支持,将对象转为String,看是否为“[object Array]”。 //即:function(obj){return jQuery.type(obj)==="array";}, isArray: Array.isArray, //是否数组 //是否window对象 isWindow: function(obj) { return obj != null && obj === obj.window; }, //是否是可用范围内的数字 //用typeof方法判断NaN的时候返回也是number,console.log(typeof NaN); //number //所以为了可靠,不能使用原生的typeof方法。 isNumeric: function(obj) { return !isNaN(parseFloat(obj)) && isFinite(obj); }, //获取数据的类型 type: function(obj) { //如果为null或undefined,直接返回这两种类型的字符串。 if (obj == null) { return String(obj); } // Support: Safari <= 5.1 (functionish RegExp) // 如果是object或者function,调用Object.prototype.toString方法 //(core_toString对象其实就是{}.toString方法),生成 "[object Xxx]"格式的字符串 return typeof obj === "object" || typeof obj === "function" ? class2type[core_toString.call(obj)] || "object" : typeof obj; //否则直接返回 typeof obj就可以了,因为这种情况只能是基本类型,用typeof判断就足够了 }, /* class2type这个对象: jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); 这个方法把所有类型都放到了这个对象里,然后拼好 [object +name] 对应的属性名和简写的值, 那以后直接就可以通过属性名找出对应简写的数据类型了。 */ // 检查obj是否是一个纯粹的对象(通过"{}" 或 "new Object"创建的对象) // console.info( $.isPlainObject( {} ) ); // true // console.info( $.isPlainObject( '' ) ); // false // console.info( $.isPlainObject( document.location ) ); // true // console.info( $.isPlainObject( document ) ); // false // console.info( $.isPlainObject( new Date() ) ); // false // console.info( $.isPlainObject( ) ); // false // isPlainObject分析与重构 http://www.jb51.net/article/25047.htm // 对jQuery.isPlainObject()的理解 http://www.cnblogs.com/phpmix/articles/1733599.html isPlainObject: function(obj) { // Not plain objects: // - Any object or value whose internal [[Class]] property is not "[object Object]" // - DOM nodes // - window // 测试以下三中可能的情况:不是object类型、DOM节点类型、 window对象 if (jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) { return false; } // Support: Firefox <20 // The try/catch suppresses exceptions thrown when attempting to access // the "constructor" property of certain host objects, ie. |window.location| // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 // 测试constructor属性 // 具有构造函数constructor,却不是自身的属性(即通过prototype继承的), try { //判断这个对象是否有constructor,如果有,在判断其原型上是否有isPrototypeOf方法, //如果没有,那么则返回false //core_hasOwn = class2type.hasOwnProperty, if (obj.constructor && !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } } catch (e) { return false; } // If the function hasn't returned already, we're confident that // |obj| is a plain object, created by {} or constructed with new Object return true; }, // 是否空对象 isEmptyObject: function(obj) { var name; //如果对象内有属性,则会被 for in到,返回false。 //另外for in方法只会对自己添加属性进行遍历,而不会对自带属性(原型上的属性)遍历。 for (name in obj) { return false; } return true; },
2.5 处理数据函数解析:
error();抛出异常
parseHTML():解析节点
parseJSON():解析JSON
parseXML:解析XML
noop():空函数
globalEval():全局解析JS
camelCase():转驼峰
nodeName():是否为指定节点(内部)
// 抛出一个自定义异常 error: function(msg) { throw new Error(msg); }, // data: string of html // context (optional): If specified, the fragment will be created in this context, defaults to document // keepScripts (optional): If true, will include scripts passed in the html string //这个方法是将字符串转换为html节点数组的形式。 //最后一个参数,默认为false,为false代表不可以插入script代码,为true则代表可以。 //解析节点 parseHTML: function(data, context, keepScripts) { if (!data || typeof data !== "string") { return null; } //如果第二个参数为bool类型的时候,把执行上下文赋值成document if (typeof context === "boolean") { keepScripts = context; context = false; } context = context || document; //匹配单标签的,如果是标签,则直接用document.createElement方法创建 var parsed = rsingleTag.exec(data), scripts = !keepScripts && []; //是否可以加载script标签 // Single tag //单标签处理 if (parsed) { return [context.createElement(parsed[1])]; } //如果是多标签,则调用buildFragment方法进行处理 parsed = jQuery.buildFragment([data], context, scripts); //如果script不为false的话,则将script标签移除, if (scripts) { jQuery(scripts).remove(); } //最后通过merge方法,将其转换成数组。 return jQuery.merge([], parsed.childNodes); }, //解析json,将json字符串转换为json对象 parseJSON: JSON.parse, // Cross-browser xml parsing // 解析XML ,将xml字符串转换为xml节点的 // 跨浏览器:parseXML函数也主要是标准API和IE的封装。 // 标准API是DOMParser对象。 // 而IE使用的是Microsoft.XMLDOM的 ActiveXObject对象。 parseXML: function(data) { var xml, tmp; if (!data || typeof data !== "string") { return null; } // Support: IE9 /*这个try catch 主要是对Ie9进行支持的,因为在ie9中, 如果传入的xml字符串不合法的话,那么在ie9中直接报错,其他浏览器则不会报错, 直接返回一个parseerror的节点,所以用try进行处理,*/ try { tmp = new DOMParser(); //标准xml解析器 xml = tmp.parseFromString(data, "text/xml"); } catch (e) { xml = undefined; /*要兼容低版本的IE浏览器: xml = new ActiveXObject( "Microsoft.XMLDOM" ); xml.async = "false"; xml.loadXML( data ); */ } if (!xml || xml.getElementsByTagName("parsererror").length) { jQuery.error("Invalid XML: " + data); } return xml; }, //无操作函数 //这个空方法在做插件扩展的时候可能会用到 //在给默认属性的时候,会用到空方法 noop: function() {}, // Evaluates a script in a global context //一般的eval进行声明的,只是声明了局部变量,而通过jQuery,则在声明的是全局变量。 // 全局解析JS,globalEval函数把一段脚本加载到全局context(window)中。 // 因为整个jQuery代码都是一整个匿名函数,所以当前context是jQuery,如果要将上下文设置为window则需使用globalEval。 globalEval: function(code) { var script, indirect = eval; code = jQuery.trim(code); //对参数进行去空格 if (code) { /*然后进行判断是否在严格模式下运行,因为严格模式是禁止用eval方法的, 当为严格模式的时候,可以看到,内部其实是在head中添加了一个script标签, 把传入的代码附加到里面,进行声明之后,在将这个script标签移除, 这样声明的变量就存在全局作用域中了,如果非严格模式下, 直接用eval方法进行解析就可以了,但是这里可以看到这个细节, 将eval赋值给了声明的变量indirect中,这样做是因为eval有两种, 一种是关键字,一种是window下的属性,这样赋值就是代表调用的window下的属性方法, 所以在全局都可以找到,如果只用eval进行解析,那么就会认为是关键字,自然就是局部变量了。*/ if (code.indexOf("use strict") === 1) { script = document.createElement("script"); script.text = code; document.head.appendChild(script).parentNode.removeChild(script); } else { // Otherwise, avoid the DOM node creation, insertion // and removal by using an indirect global eval indirect(code); } } }, // Convert dashed to camelCase; used by the css and data modules // Microsoft forgot to hump their vendor prefix (#9572) //转驼峰 //在代码中并不能对margin-top这种新式的属性进行解析, //将margin-top转换为:marginTop这种形式: camelCase: function(string) { //RMSPrefix正则是对-ms-进行匹配 /*将-ms-替换成ms- 这样在进行转换就可以了,因为IE的前缀是msTransForm,第一个字母小写, 而火狐等则是MozTransForm,第一个字母大写,所以如果是-ms-开头的,先进行替换,然后在进行下面的转换。*/ //rdashAlpha是对 - 和后面的字母或数字进行匹配, //然后调用facmelCase回调方法,进行转换为大写。 return string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase); }, //判断节点名和传入的字符串是否相等 nodeName: function(elem, name) { // 忽略大小写 return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); },2.6 关于数组和类数组方法解析:
each():遍历集合
trim():去前后空格
makeArray():类数组转换真数组
inArray():数组版indexOf
merge():合并数组
grep():过滤新数组
map():映射新数组
// each方法不仅可以对数组,类数组进行遍历,还可以对json对象进行遍历 each: function(obj, callback, args) { var value, i = 0, length = obj.length, //通过isArraylike这个方法,可以判断是否为数组或类数组,如果是,则返回true。 isArray = isArraylike(obj); // 如果有参数args,调用apply,上下文设置为当前遍历到的对象,参数使用args if (args) { /* 先看传入的对象是否为数组或类数组,如果是则遍历这个对象, 然后将当前项和参数传入回调方法,进行回调。 如果不满足条件,那么就是json对象,所以进行for in遍历, 然后执行相同的操作,这里看到回调方法会返回一个值,如果为false,则跳出循环 */ if (isArray) { for (; i < length; i++) { value = callback.apply(obj[i], args); if (value === false) { break; } } } else { for (i in obj) { value = callback.apply(obj[i], args); if (value === false) { break; } } } // A special, fast, case for the most common use of each // 没有参数args,则调用call,上下文设置为当前遍历到的对象,参数设置为key/index和value } else { if (isArray) { for (; i < length; i++) { value = callback.call(obj[i], i, obj[i]); if (value === false) { break; } } } else { for (i in obj) { value = callback.call(obj[i], i, obj[i]); if (value === false) { break; } } } } return obj; }, // 尽可能的使用本地String.trim方法,否则先过滤开头的空格,再过滤结尾的空格 trim: function(text) { return text == null ? "" : core_trim.call(text); }, // results is for internal usage only // 将类数组转换为真数组,如果传入第二个参数,则返回类数组。 makeArray: function(arr, results) { var ret = results || []; if (arr != null) { //将参数外面包一层object,是将参数转换为对象,如果传入数字,则返回false, //如果传入的是字符串,那么会返回true,因为字符串被转成对象了,并且有长度。 //然后通过merge方法进行附加,把传入的参数合并到ret中。 //如果不为数组或类数组,则直接用数组的push将参数附加到数组中即可 if (isArraylike(Object(arr))) { jQuery.merge(ret, typeof arr === "string" ? [arr] : arr ); } else { core_push.call(ret, arr); } } return ret; }, //即函数indexOf的作用。。 //第一个参数是要查找的值,第二个是对象,第三个是从第几项开始查找。 inArray: function(elem, arr, i) { return arr == null ? -1 : core_indexOf.call(arr, elem, i); }, // 将数组second合并到数组first中 merge: function(first, second) { var l = second.length, i = first.length, j = 0; // 如果second的length属性是Number类型,则把second当数组处理 if (typeof l === "number") { for (; j < l; j++) { first[i++] = second[j]; } } else { // 第二个参数没有长度,也就是可能是json对象,那么直接遍历, //将非undefined的值添加到first中 while (second[j] !== undefined) { first[i++] = second[j++]; } } // 修正first的length属性,因为first可能不是真正的数组 first.length = i; return first; }, // 过滤数组,返回新数组;callback返回true时保留;如果inv为true,callback返回false才会保留 //第三个参数,如果传入,那么代表筛选条件进行反转。 grep: function(elems, callback, inv) { var retVal, ret = [], i = 0, length = elems.length; inv = !! inv; // Go through the array, only saving the items // that pass the validator function // 遍历数组,只保留通过验证函数callback的元素 for (; i < length; i++) { // 这里callback的参数列表为:value, index,与each的习惯一致 retVal = !! callback(elems[i], i); // 是否反向选择 if (inv !== retVal) { ret.push(elems[i]); } } return ret; }, // arg is for internal usage only // 将数组或对象elems的元素/属性,转化成新的数组 map: function(elems, callback, arg) { var value, i = 0, length = elems.length, isArray = isArraylike(elems), //是否为数组或类数组 ret = []; // Go through the array, translating each of the items to their if (isArray) { // 遍历数组,对每一个元素调用callback,将返回值不为null的值,存入ret for (; i < length; i++) { // 执行callback,参数依次为value, index, arg value = callback(elems[i], i, arg); // 如果返回null,则忽略(无返回值的function会返回undefined) if (value != null) { ret[ret.length] = value; } } // Go through every key on the object, // 不是数组,遍历对象,对每一个属性调用callback,将返回值不为null的值,存入ret } else { for (i in elems) { // 执行callback,参数依次为value, key, arg value = callback(elems[i], i, arg); // 如果返回null,则忽略(无返回值的function会返回undefined) if (value != null) { ret[ret.length] = value; } } } // Flatten any nested arrays // 使嵌套数组变平,concat: // 如果某一项为数组,那么添加其内容到末尾。 // 如果该项目不是数组,就将其作为单个的数组元素添加到数组的末尾。 return core_concat.apply([], ret); },
2.7 其他功能函数解析:
guid():唯一表示(内部)
proxy():改变this指向
access(): 多功能值操作
now():当前时间
swap():css交换(内部)
// A global GUID counter for objects //唯一表示(内部使用) /*对事件进行控制的,例如每次对dom元素进行绑定事件的时候, 会通过这个属性进行绑定,这个属性每次自增,产生一个唯一的标示, 所以对dom元素进行事件解绑等操作的时候,通过这个属性就可以找到。*/ guid: 1, // Bind a function to a context, optionally partially applying any // arguments. // 代理方法:为fn指定上下文(即this),改变this指向 // jQuery.proxy( function, context ) // jQuery.proxy( context, name ) proxy: function(fn, context) { // 如果context是字符串,设置上下文为fn,fn为fn[ context ] // 即设置fn的context方法的上下文为fn var tmp, args, proxy; //为了支持$.proxy(obj, 'show')();的调用方式 if (typeof context === "string") { tmp = fn[context]; context = fn; fn = tmp; } /* 例子:var obj={ show: function () { console.log(this); } } $.proxy(obj, 'show')(); //Object {} 输出为object{}的原因:经过上面if的转化后: $.proxy(obj, 'show')();等价于 $.proxy(obj.show, obj)(); */ // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. // 快速测试fn是否是可调用的(即函数),在文档说明中,会抛出一个TypeError, // 但是这里仅返回undefined if (!jQuery.isFunction(fn)) { return undefined; } // Simulated bind args = core_slice.call(arguments, 2);// 从参数列表中去掉fn,context proxy = function() { // 设置上下文为context和参数 //args.concat(core_slice.call(arguments))是对分开传参表示支持 //这么做的原因是通过这个方法可以进行科里化,对某个参数进行绑定。 /*例:function show(a ,b ) { console.log(a,b,this); } $.proxy(show, document,1)(2); //1 2 document */ return fn.apply(context || this, args.concat(core_slice.call(arguments))); }; // Set the guid of unique handler to the same of original handler, so it can be removed //当处理完方法后,把方法的guid属性进行自增。 // 统一guid,使得proxy能够被移除 proxy.guid = fn.guid = fn.guid || jQuery.guid++; //最后返回proxy,实际上也就是传入的方法指向,所以这个方法不是自动运行的, //想要调用,还需要在后面加上一对括号。 return proxy; }, // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function // 多功能函数,读取或设置集合的属性值;值为函数时会被执行 // fn:jQuery.fn.css, jQuery.fn.attr, jQuery.fn.prop /* 例:$('#div1').css('width') //传入一个参数的时候,是取值操作 $('#div1').css('width','200px') //当传入两个参数的时候,是赋值操作 $('#div1').css({ 'width': '200px', 'height': '200px' }) //当传入一个json对象的时候,是设置多个属性 */ access: function(elems, fn, key, value, chainable, emptyGet, raw) { /*elems表示节点元素,fn是回调方法,key是属性名,比如 width等,value是对应的值,比如100px, emptyGet: 读取如果为空用什么表示 chainable是用来区分取值还是赋值。赋值为true,取值为false, 通过css方法的arguments.length > 1,来进行判断。大于一个就是赋值,否则就是取值*/ //i:是用来循环的索引,length来保存传入元素的长度,后面判断用, //bulk来判断是否传入key这个变量,如果未传,则为true。 var i = 0, length = elems.length, bulk = key == null; // Sets many values //当传入的参数为object时,也就是json对象,设置多个属性 if (jQuery.type(key) === "object") { //先将chainable这个值赋值true,因为只传一个json对象时, //那么必然不满足arguments>1这个条件,所以这里将其手动改变一下。 chainable = true; for (i in key) { //对json进行拆解之后,对当前方法进行递归。 jQuery.access(elems, fn, i, key[i], true, emptyGet, raw); } // Sets one value // 只设置一个属性 //当value不为undefined时,也就代表了赋值操作, //避免漏传chainable参数的情况 } else if (value !== undefined) { chainable = true; if (!jQuery.isFunction(value)) { raw = true; } //如果未传属性值key if (bulk) { // Bulk operations run against the entire set if (raw) { //如果value不是function,直接回调并传入节点元素和value。 fn.call(elems, value); // 这种情况下的赋值操作到这里就已经完成了 // 所以在这里把fn置为了null,因为后面的if已经没有必要执行了 fn = null; // ...except when executing function values } else { //如果value是function的话,把回调函数用bulk保存起来 bulk = fn; // 把原本保存原始处理函数的变量fn重新用约定好的结构进行封装,以便后面统一进行调用 // 这里的key参数纯粹是为了在后面的调用处统一参数 // 注意这里的fn函数最后返回了原始处理函数的处理结果 fn = function(elem, key, value) { return bulk.call(jQuery(elem), value);//??作用 }; } } if (fn) { //把拆解之后的参数传入回调方法 for (; i < length; i++) { //value是函数则运行fn(elem, key, value.call(elem, i, fn(elem, key))) //value非函数则运行fn(elem, key, value) fn(elems[i], key, raw ? value : value.call(elems[i], i, fn(elems[i], key))); } } } //如果是可链式操作的则返回elems return chainable ? elems : // Gets // 否则则是读取操作 bulk ? fn.call(elems) : length ? fn(elems[0], key) : emptyGet;// 读取属性 }, // 获取当前时间的便捷函数 now: Date.now, // A method for quickly swapping in/out CSS properties to get correct calculations. // Note: this method belongs to the css module but it's needed here for the support module. // If support gets modularized, this method should be moved back to the css module. //CSS交换 //作用:原生js无法获取display为none的属性值,而jQuery却可以获取,原因就是在内部使用了swap方法。 swap: function(elem, options, callback, args) { var ret, name, old = {}; // Remember the old values, and insert the new ones //这部分主要将原有的样式存到old对象中,然后将新的样式插入到节点中。 for (name in options) { old[name] = elem.style[name]; elem.style[name] = options[name]; } //获取到需要的值。 ret = callback.apply(elem, args || []); // Revert the old values //将原有的样式还原。 for (name in options) { elem.style[name] = old[name]; } return ret; } });
2.8 判断是否为数组或类数组的方法
//用来判断是否为数组或类数组的方法。 function isArraylike(obj) { var length = obj.length, type = jQuery.type(obj); if (jQuery.isWindow(obj)) { return false; } //如果有nodeType的话并且还有长度,那么就是类数组的形式,返回true。 if (obj.nodeType === 1 && length) { return true; } //如果还不满足则判断是否为数组。 return type === "array" || type !== "function" && (length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj); }
工具函数就学习到这里,后期再深入。先了解各个函数的实现原理,于整个jQuery框架的作用。