点开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框架的作用。