jQuery源码学习(4)-变量解析及工具函数

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

阅读更多
文章标签: jQuery工具函数
个人分类: jQuery源码分析
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭