Skip to content

Latest commit

 

History

History
1780 lines (1340 loc) · 42 KB

工具方法.md

File metadata and controls

1780 lines (1340 loc) · 42 KB

5. 工具方法

利用4. $.extend()拷贝继承构建工具方法,工具方法是jQuery的最底层方法,通常实例方法中会调用工具方法

jQuery.extend({
	expando:                唯一的jQuery字符串(内部使用)
	noConflict:             防冲突
	isReady:                DOM是否加载完毕(内部使用)
	readyWait:              等待异步文件先执行后执行DOM加载完毕事件的计数器(内部使用)
	holdReady():            推迟DOM触发
	ready():                准备触发DOM加载完毕后的事件
	isFunction():           是否为函数
	isArray():              是否为数组(不支持IE6、7、8)
	isWindow():             是否为window对象
	isNumeric():            是否为数字
	type():                 判断数据类型
	isPlantObject():        判断是否为对象字面量
	isEmptyObject():        判断是否为空对象
	error():                抛弃异常
	parseHTML():            将字符串转换成DOM数组
	parseJSON():            JSON.parse()
	parseXML():
	globalEval():           类似于eval()
	camelCase():            转驼峰
	nodeName():             判断节点的Name
	each():                 ()数组遍历
	trim():                 去掉首位空字符
	makeArray():            转数组
	inArray():              查看元素在数组中的索引
	merge():                合并数组
	grep():                 数组过滤
	map():                  遍历数组并修改数组元素
	guid:                   绑定事件ID
	proxy():                改变this指向
	access():               多功能函数底层方法
	now():                  获取时间毫秒数
	swap():                 样式交换
})

5.1 $.expando

  • 唯一性

源码

//[351]
// Unique for each copy of jQuery on the page
//生成随机数并去掉小数点
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),

内容解析

console.log($.expando);	//jQuery20305959261594460556

5.2 $.noConflict

  • 防冲突

源码

[37~41]
//在引用jQuery库之前有使用$命令的变量,则保存引用之前的变量
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,

// Map over the $ in case of overwrite
_$ = window.$,


//[353]
noConflict: function( deep ) {
	//在[37-41]利用_$缓存引用库之前的$值
	//库加载完毕后[8826]先执行
	//此时把$值在jQuery中的引用放弃掉
	//详见(二)
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}
    //加入参数true以后和(二)一样,放弃掉jQuery变量的命名
    if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	//详见(一)
	return jQuery;
},


//[8826]
window.jQuery = window.$ = jQuery

内容解析

(一)、$在引用jQuery库之后改变

var new$ = $.noConflict();	//创建$变量的副本
$ = 2017;					//$本身的值改变

new$(function(){
    alert(new$().jquery);	//2.0.3 new$ = 未改变之前的$值
    alert($);				//2017 $值被改变
})

(二)、$在引用jQuery库之前已经存在

//保留引用库前的$
<script>
	var $ = 2017;
</script>


<script src='Jquery2.0.3.js'></script>

<script>
	$.noConflict();	//获取加载jQuery库之前的$变量值,并放弃$变量对于jQuery的意义
	console.log($);	//2017
</script>


//保留引用库前的jQuery
<script>
	var jQuery = 2017;
</script>


<script src='Jquery2.0.3.js'></script>

<script>
       $.noConflict(true);	 //放弃掉jQuery
	console.log(jQuery); //2017,仍然是引用jQuery库之前的jQuery变量
</script>

5.3 $.ready()

  • DOM加载完毕的触发事件
  • $(function(){})
  • $(document).ready()
  • DOMContentLoaded事件(等文档流、普通脚本、延迟脚本加载完毕后触发)
  • load事件(等文档流、普通脚本、延迟脚本、异步脚本、图片等所有内容加载完毕后触发)

源码

//历程: $(function(){}) -> $(document).ready() -> $.ready.promise().done(fn) -> complete()回调 -> $.ready()

// 步骤一、[182]
// HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
	//$(document).ready(function(){}) 调用了[240]的$().ready()
	return rootjQuery.ready( selector );
}

// 步骤二、[240-245]
//$().ready()
ready: function( fn ) {
    // 使用Promise的形式等待回调
    // 创建了延迟对象
    jQuery.ready.promise().done( fn );
    return this;
},


// 步骤三、[819]
jQuery.ready.promise = function( obj ) {
    //第一次是空对象,可以进入if
	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和else都是在DOM加载完毕后执行$.ready()

		//详见(一) DOM加载完毕 IE会提前出发
		if ( document.readyState === "complete" ) {
			// Handle it asynchronously to allow scripts the opportunity to delay ready
			// hack写法,兼容IE
			setTimeout( jQuery.ready );

		} else {

			// Use the handy event callback
			// DOM没有加载完毕时,监测
			document.addEventListener( "DOMContentLoaded", completed, false );

			// A fallback to window.onload, that will always work
			// 如果浏览器有缓存事件,则load会比DOMContentLoaded先触发,所以两个事件都要监听
			window.addEventListener( "load", completed, false );
		}
	}
	//promise的状态不能被修改
	return readyList.promise( obj );
};



//步骤四、[89]
//complete()回调
//这是一个自执行匿名函数中的局部函数,在自执行匿名函数内都可见,所以上述监听事件可以直接调用

// The ready event handler and self cleanup method
completed = function() {
    //尽管在jQuery.ready.promise两个事件都监听了,但是这里都取消了,所以任何一个监听事件触发,另外一个监听事件因为取消了不会再次执行,jQuery.ready();只会执行一次
	document.removeEventListener( "DOMContentLoaded", completed, false );
	window.removeEventListener( "load", completed, false );
	jQuery.ready();
};

//步骤五、[382]
//$.ready()

// Handle when the DOM is ready
ready: function( wait ) {
	//和$.holdRady()有关
	//--jQuery.readyWait如果hold了N次,则不会触发DOM加载事件,而是返回
	//如果jQuery.isReady为true则已经触发了一次了
	// Abort if there are pending holds or we're already ready
	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
	// 如果释放hold,则全部释放完后才能继续执行下面的DOM加载事件,否则return
	if ( wait !== true && --jQuery.readyWait > 0 ) {
		return;
	}

	// 在jQuery.ready.promise()中的延迟对象触发回调
	// 触发步骤二的jQuery.ready.promise().done( fn );回调函数done()
	// 平时用readyList.resolve()
	// 但是readyList.resolveWith()可以传递参数
	// 使this指向document
	// 传入的参数指向jQuery,详见(二)
	// If there are functions bound, to execute
	readyList.resolveWith( document, [ jQuery ] );

	// Trigger any bound ready events
	//主动触发
	//$(documnent).on('ready',function(){
	//})
	//详见(三)
	if ( jQuery.fn.trigger ) {
		jQuery( document ).trigger("ready").off("ready");
	}
},

内容解析

(一)、浏览器加载页面过程

  • 创建Document对象,解析Web页面,解析HTML元素和相应的文本内容添加Element对象和Text节点到文档中,此时document.readyState = 'loading'
  • 当HTML解析器遇到没有asyncdefer属性的<script>元素时,把元素添加到文档中,执行内部脚步或引用的外部脚本,这些脚本会同步执行,并且脚本下载和执行时HTML解析器暂停解析HTML元素,此时如果脚本中使用了document.write()方法,就会把该方法的内容插入输入流中,解析器恢复时这些文本会成为文档的一部分
  • 当解析器遇到了async属性的<script>元素时,开始下载脚本文本,并继续解析文档,意思是async属性的<script>元素异步执行,不会阻塞文档的解析,需要注意异步脚本禁止使用document.write()方法,因为此时文档不会暂停等待document.write()内容的插入,而是继续下载执行
  • 如果文档(文档流,不包括图片等其他内容)析完,则document.readyState = 'interactive'
  • 此时因为文档解析完毕,执行带有属性defer<script>脚本,需要注意async属性的脚本如果在文档流解析完毕还没有执行完毕时此时也会继续执行,defer<script>脚本可以访问完整的文档树,因为此时文档流已经解析完毕,但是也禁止使用document.write()方法
  • 如果 带有属性defer<script>脚本加载完毕,浏览器在Document对象上触发了DOMContentLoaded事件,这标志着程序执行从同步脚本执行 阶段转换到了异步时间驱动阶段,这时异步脚本能可能没有执行完成。
  • 文档已经完全解析完成,浏览器可能还在等待其他内容载入,比如图片,当所有的内容载入完成并且所有的异步脚本完成载入和执行,document.readState='complete', Web浏览器触发Window对象上的load事件
  • 之后就是调用异步事件,以异步响应用户输入事件,网络事件,计时器过期等

(二)、readyList.resolveWith( document, [ jQuery ] );

  • 参数一是context,即传递上下文
  • 参数二是一个需要传入的参数数组给完成的回调函数
$(function(obj){
    console.log(this); //this指向document这个DOM对象
    console.log(obj);  //obj指向jQuery对象
	/*function ( selector, context ) {
           	The jQuery object is actually just the init constructor 'enhanced'
          	return new jQuery.fn.init( selector, context, rootjQuery );
      	}
	*/
})

(三)、jQuery触发ready的三种方法

  • $(function(){})
  • $(document).ready(function(){})
  • $(document).on('ready',function(){})

5.4 $.holdReady()

  • 推迟DOM加载完毕事件的触发

源码

// Is the DOM ready to be used? Set to true once it occurs.
//为真说明DOM加载完成事件已经触发
isReady: false,

// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,

// Hold (or release) the ready event
holdReady: function( hold ) {
	if ( hold ) {
		//如果有多个需要hold的异步文件,则++多次
		jQuery.readyWait++;
	} else {
		jQuery.ready( true );
	}
},

内容解析

//异步加载外部文件,需要注意async属性只能用于外部文件
$.getScript('a.js', function(){

});

$(function () {
	alert(2);
      });

//此时先2后1,DOMContentLoaded事件比异步事加载javascript事件先触发

解决异步文件先执行,然后再执行DOM加载完毕事件

//延迟DOM加载完成事件
$.holdReady(true);

//异步加载外部文件,需要注意async属性只能用于外部文件
$.getScript('a.js', function(){
    //a.js -> alert(1)
	//释放延迟
	$.holdReady(false);
});

$(function () {
	alert(2);
});
//此时先1后2

5.5 $.isFunction()

源码

// See test/unit/core.js for details concerning isFunction.
// Since version 1.3, DOM methods and functions like alert
// aren't supported. They return false on IE (#2968).
// [409]
// 原生的方法alert在IE8下使用typeof alert不返回function而是返回object
// 所以jQuery放弃IE8的检测方法
isFunction: function( obj ) {
	//通过$.type()检测
	return jQuery.type(obj) === "function";
},

内容解析

function f() {
}
console.log($.isFunction(f));	//true

5.6 $.isArray()

  • 缩短查找的作用域链
  • 不支持IE6、7、8

源码

//[413]
isArray: Array.isArray,

5.7 $.isWindow()

源码

//[415]
isWindow: function( obj ) {
	//obj != null 详见(一),除了传入null和undefined不执行第二个&&
	//其他都会执行第二个&&进行判断,因为null和undefined不是包装对象,不可能有.window这样的属性
	//obj.window 详见(二)
	return obj != null && obj === obj.window;
},

内容解析

(一)、null非包装对象

console.log(null == null);			//true
console.log(undefined == null);		//true
console.log(false == null);			//false
console.log({} == null);			//false
console.log([] == null);			//false

(二)、window对象

  • 全局对象
  • 浏览器窗口
obj === obj.window	//全局对象下的浏览器窗口属性

5.8 $.isNumeric()

源码

isNumeric: function( obj ) {
	//parseFloat转化非数字是NaN
	//判断数字是否是有限的数字
	return !isNaN( parseFloat(obj) ) && isFinite( obj );
},

内容解析

console.log(typeof NaN);	//number,所以不能用typeof来判断是否为数字

5.9 $.type()

源码

//[423]
type: function( obj ) {
	//null或undefined
	if ( obj == null ) {
		//转字符串
		return String( obj );
	}
	// Support: Safari <= 5.1 (functionish RegExp)
	//如果是对象或函数,兼容性,Safari5.1
	return typeof obj === "object" || typeof obj === "function" ?
		//引用数据类型检测
		class2type[ core_toString.call(obj) ] || "object" :
		//基本数据类型检测
		typeof obj;
},



// Populate the class2type map
// [844]
// 这个写法很巧妙
// class2type["object Number"] = 'number'
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

内容解析

(一)、原生的typeof更丰富

var a = [],
	b = {},
	c = 'string',
	d = null,
	e = NaN,
	f = undefined,
	g = 123,
	h = new Date;

console.log(typeof a);	//object
console.log(typeof b);	//object
console.log(typeof c);	//string
console.log(typeof d);	//object
console.log(typeof e);	//number
console.log(typeof f);	//undefined
console.log(typeof g);	//number
console.log(typeof h);	//object

console.log($.type(a));	//array
console.log($.type(b));	//object
console.log($.type(c));	//string
console.log($.type(d));	//null
console.log($.type(e));	//number
console.log($.type(f));	//undefined
console.log($.type(g));	//number
console.log($.type(h));	//date

(二)、Object.prototype.toString().call()

console.log(Object.prototype.toString.call([]));	//[object Array]
console.log({}.toString.call({}));					//[object Object]

(三)、instance of

需要注意使用instance of的方法进行类型检测需要考虑跨iframe子框架的检测问题

//a.js
function Test() {
    this.name = 'ziyi2';
}

//child_index.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h2>child_index.html</h2>
    <script src="a.js"></script>
    <script>
        var test = new Test();
        document.test = test;
        document.data = '123';
    </script>
</body>
</html>

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8"/>
	<title>Jquery2.0.3源码分析</title>

</head>

<body>
	<h1>index.html</h1>
	<iframe src="child_index.html" frameborder="0" id="iframe"></iframe>
	<div>1</div>
	<div>2</div>
	<div>3</div>

	<script src='Jquery2.0.3.js'></script>
	<script src="a.js"></script>
	<script>

		window.onload = function() {
            var child_index= document.getElementById('iframe');		//注意与$('#iframe')的区别,一个是DOM对象,一个是jQuery对象

            (function fn() {
                if(!child_index && !child_index.contentWindow && !child_index.contentWindow.document) {
                    setTimeout(fn(),0);
                    console.log('document not ready...');
                } else {
                    var child_index_doc = child_index.contentWindow.document;	//获取子框架页面的文档对象
                    console.log(child_index_doc.data);		//123
                    console.log(child_index_doc.test);		//Test {name:ziyi2}
					console.log(child_index_doc.test.name);	//ziyi2
					console.log(child_index_doc.test instanceof Test);	//false
					//可以发现使用子框架的Test实例对象不能使用instanceof进行检测
                    var test1 = new Test();
                    console.log(test1 instanceof Test);		//true

                }
            })()
		}
	</script>
</body>
</html>

提示: 使用child_index.html页面实例化的对象和index.html页面实例化的对象是两个不同的执行环境,所以没办法进行检测

5.10 $.isPlantObject()

  • 检测对象字面量

源码

isPlainObject: function( obj ) {
	// Not plain objects:
	// - Any object or value whose internal [[Class]] property is not "[object Object]"
	// - DOM nodes
	// - window
	//1.如果不是对象,DOM节点和window用$.type方法会返回object
	//2.如果是Node节点
	//3.如果是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
	try {
		//4.系统自带的对象,例如window.location,不是node节点,$.type又会返回object
		//详见(一)、(二)
		//obj.constructor指向对象的构造函数
		//obj.constructor.prototype指向构造函数对应的原型对象
		if ( obj.constructor &&
				//obj.constructor.prototype.hasOwnProperty('isPrototypeOf')
				//判断obj的原型是不是Object.prototype,而不是Array\Date等
				//var arr = [];
				//var bool = {}.hasOwnProperty.call( arr.constructor.prototype, "isPrototypeOf" );
				//console.log(bool);	//false
				//如果是Array类型则return false表明不是对象字面量
				!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;
},

内容解析

(一)、{}.hasOwnPrototype

  • 判断是否是自身的属性
  • 判断是否是原型对象的属性
function Obj() {
    this.name = 'ziyi2';
    this.age = 23;
}

Obj.prototype.name = 'prototype.ziyi2';
Obj.prototype.addr = 'zjut';

//是否是原型对象的属性和方法
function hasPrototypeProperty(obj,key) {
    //1.如果是自己的属性返回false,表明不是原型对象的属性
    //2.如果能使用in遍历,1返回true,则是原型对象的属性,使用in可以遍历自己的属性和原型对象的属性
    return !obj.hasOwnProperty(key) && (key in obj);
}

var obj = new Obj();

alert(hasPrototypeProperty(obj,'name'));		//false
alert(hasPrototypeProperty(obj,'age'));			//false
alert(hasPrototypeProperty(obj,'addr'));		//true

(二)、isPrototypeOf

创建了自定义的构造函数后,其原型对象的默认只会取得constructor属性,其余都是从Object继承而来,当调用构造函数创建新实例后,该实例内部将包含一个指向构造函数原型对象的指针([[Prototype]] 内部属性,注意是实例的属性而非构造函数的属性),脚本中没有标准的方式访问[[Prototype]],但在一些浏览器诸如Firefox、Safari、Chrome在每个对象上都支持属性__proto__,这个指针连接存在于实例对象与构造函数的原型对象之间,不是实例对象与构造函数之间,调用构造函数创建的实例都有[[Prototype]]属性,但是无法访问。唯一的方法是可以通过isPrototypeOf()方法来确定实例对象和原型对象之间是否存在这种关系。

function Obj() {
    this.name = 'ziyi2';
	this.age = 23;
}

Obj.prototype.name = 'prototype.ziyi2';
Obj.prototype.addr = 'zjut';

function hasPrototypeProperty(obj,key) {
    return !obj.hasOwnProperty(key) && (key in obj);
}


var obj = new Obj();

console.log(Obj.prototype.isPrototypeOf(obj));		//true
console.log(Object.prototype.isPrototypeOf(obj));	//true



//来个原型链
var data = new Date;
console.log(Date.prototype.isPrototypeOf(data));				//true
console.log(Object.prototype.isPrototypeOf(Date.prototype));	//true
console.log(Object.prototype.isPrototypeOf(data));				//true
// data -> Date.prototype 实例对象和构造函数对应的原型对象之间的关系
// Date.prototype -> Object.prototype Date.prototype相对于Object.prototype而言就是实例对象

isPrototypeOf属性是Object.prototype的自有属性,其他对象所持有的该属性都是继承的。

//是否是原型对象的属性和方法
function hasPrototypeProperty(obj,key) {
    //1.如果是自己的属性返回false,表明不是原型对象的属性
    //2.如果能使用in遍历,1返回true,则是原型对象的属性,使用in可以遍历自己的属性和原型对象的属性
    return !obj.hasOwnProperty(key) && (key in obj);
}

//Obeject.prototype自有的属性isPrototypeOf
console.log(Object.prototype.hasOwnProperty('isPrototypeOf'));		//true
//Date的该属性是原型对象Obeject.prototype那里继承过来的
console.log(hasPrototypeProperty(Date,'isPrototypeOf'));			//true

(三)、try{} catch{}

需要补上详细信息.

5.11 $.isEmptyObject()

源码

isEmptyObject: function( obj ) {
	var name;
	//可以遍历原型对象的属性和方法
	//for-in只遍历可枚举的属性
	//原型对象的系统自带属性可能是不可枚举的,所以虽然可以遍历原型对象的属性和方法
	//但是for in遍历不到系统自带的属性和方法,可以用来检测对象是否为空对象
	for ( name in obj ) {
		return false;
	}
	return true;
},

5.12 $.error()

  • 抛出异常错误

源码

//[468]
error: function( msg ) {
	throw new Error( msg );
},

5.13 $.parseHTML()

  • 将字符串转换成DOM数组

源码

// 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
	// context默认是document,如果被指定,则会在这个指定的context创建文档碎片
	// keepScripts如果是true,则会在文档中创建script标签
	parseHTML: function( data, context, keepScripts ) {
		// 如果是空,或者不是字符串
		if ( !data || typeof data !== "string" ) {
			return null;
		}
		// 如果省略了context参数,则第二个参数变成了keepScripts
		if ( typeof context === "boolean" ) {
			keepScripts = context;
			context = false;
		}

		// context默认是document对象
		context = context || document;

		// 匹配单标签 "<li></li>"
		var parsed = rsingleTag.exec( data ),
			scripts = !keepScripts && [];

		// Single tag
		// 单标签
		if ( parsed ) {
			// 单标签当然使用document.createElement方法创建DOM对象\
			// 返回的仍然是数组
			return [ context.createElement( parsed[1] ) ];
		}


		//多标签使用文档碎片的形式创建DOM对象数组
		parsed = jQuery.buildFragment( [ data ], context, scripts );

		// keepScripts = true scripts = false 因此不会移除script标签, 否则移除script 标签
		if ( scripts ) {
			jQuery( scripts ).remove();
		}

		// 返回的仍然是数组, jQuery.merge可以合并数组
		return jQuery.merge( [], parsed.childNodes );
	},

内容解析

//单标签
document.body.appendChild($.parseHTML('<li>1</li>')[0]); //将li元素插入body元素
//多标签
document.body.appendChild($.parseHTML('<li>1</li><li>1</li>')[0]);
document.body.appendChild($.parseHTML('<li>1</li><li>1</li>')[1]);

5.14 $.parseJSON()

源码

parseJSON: JSON.parse,

5.15 $.parseXML()

源码

// Cross-browser xml parsing
parseXML: function( data ) {
	var xml, tmp;
	if ( !data || typeof data !== "string" ) {
		return null;
	}

	// Support: IE9
	try {
		tmp = new DOMParser();
		xml = tmp.parseFromString( data , "text/xml" );
	} catch ( e ) {
		xml = undefined;
	}

	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
},

5.16 $.noop()

源码

noop: function() {},

5.17 $.globalEval()

源码

// Evaluates a script in a global context
globalEval: function( code ) {
	//详见(一)
	var script,
			indirect = eval;

	code = jQuery.trim( code );

	if ( code ) {
		// If the code includes a valid, prologue position
		// strict mode pragma, execute code by injecting a
		// script tag into the document.
		// 如果是在严格模式下,详见(二)
		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
		    // 非严格模式使用全局eval()
			indirect( code );
		}
	}
},

内容解析

(一)、eval

  • 直接调用eval,总是在调用它的上下文作用域内执行
  • 其他的间接调用则使用全局对象作为其上下文作用域
var geval = eval; //使用别名调用eval将是全局eval,这算是间接调用
var x = 'x global';
var y = 'y global';
function f(){
    var x = 'x local';
    eval("x += ' changed'"); //直接eval改变了局部变量的值
    return x;
}
function g(){
    var y = 'y local';
    geval("y += ' changed'"); //间接调用改变了全局变量的值
    return y;
}
console.log(f(),x);//x local changed      x global
console.log(g(),y);//y local              y global changed
//所以更可能会使用全局eval而不是局部eval

(二)、 严格模式

function fn(){
    eval('var i = 0');
    console.log(i);     //0
}

function f(){
    "use strict";
    eval('var i = 0');
    console.log(i);     //Uncaught ReferenceError: i is not defined(…)
}

fn();
f();

5.18 $.camelCase()

  • 字符串转驼峰

源码

//[81]
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,

// Used by jQuery.camelCase as callback to replace()
fcamelCase = function( all, letter ) {
	return letter.toUpperCase();
},

//[550]
// Convert dashed to camelCase; used by the css and data modules
// Microsoft forgot to hump their vendor prefix (#9572)
camelCase: function( string ) {
	//解析一
	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},

内容解析

(一)、string.replace

  • 参数一 规定子字符串或要替换的模式的 RegExp 对象
  • 参数二 规定了替换文本或生成替换文本的函数

5.19 $.nodeName()

源码

nodeName: function( elem, name ) {
	return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},

内容解析

console.log($.nodeName($('div')[0],'DIV')); //true

5.20 $.each()

  • 遍历
  • 参数一 index
  • 参数二 value
  • 参数三 内部使用

源码

// args is for internal usage only
each: function( obj, callback, args ) {
	var value,
		i = 0,
		length = obj.length,
		//判断是否是类数组对象
		isArray = isArraylike( obj );

	//如果第三参数存在,内部使用
	if ( args ) {
		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
	// 外部使用
	} else {
	    //数组或类数组对象
		if ( isArray ) {
			for ( ; i < length; i++ ) {
				//call的第一个参数是this指向,后面的参数是callback函数的参数
				//$.each(arr,function(index,value){})
				//callback -> functon(index,value){}
				//并且callback传入了两个参数i obj[i]
				//i -> index obj[i] -> value
				//this -> obj[i] 详见(二)
				value = callback.call( obj[ i ], i, obj[ i ] );
				//如果有return false 则终止遍历
				//详见(三)
				if ( value === false ) {
					break;
				}
			}
		} else {
		    //对象
			for ( i in obj ) {
				value = callback.call( obj[ i ], i, obj[ i ] );

				if ( value === false ) {
					break;
				}
			}
		}
	}

	return obj;
},

内容解析

(一)、参数解析

var arr = [1,2,3];

$.each(arr,function(index,value) {
   console.log(index);  //0 1 2
   console.log(value);  //1 2 3
});

(二)、this指向

var arr = [1,2,3];

$.each(arr,function(index,value) {
   console.log(this.valueOf());  //1 2 3
});

(三)、终止遍历

var arr = [1,2,3];

$.each(arr,function(index,value) {
  console.log(index);  //0
  return false;        //终止遍历
});

5.21 $.trim()

源码

trim: function( text ) {
	return text == null ? "" : core_trim.call( text );
},

5.22 $.makeArray()

  • 参数一
  • 参数二 内部使用

源码

// results is for internal usage only
makeArray: function( arr, results ) {
	//第二参数可能不存在,那么就是空数组
	var ret = results || [];

	//第一参数如果不存在返回空数组
	if ( arr != null ) {
		//Object(arr)
		//字符串形式 '123' -> ['123']
		//详见(一)
		//需要注意数组是走这里
		if ( isArraylike( Object(arr) ) ) {
			jQuery.merge( ret,
				typeof arr === "string" ?
				[ arr ] : arr
			);
		//数字形式,详见(二)
		} else {
			core_push.call( ret, arr );
		}
	}

	return ret;
},

内容解析

(一) Object

  • 转换成包装对象
var str = '123'
console.log(Object(str));
//String {0: "1", 1: "2", 2: "3", length: 3, [[PrimitiveValue]]:
console.log($.makeArray(str));  //['123']

(二) [].push()

var num = 123;
console.log(Object(num));   //Number(123);

var arr = [];

arr.push(num);      //传入单个num
arr.push([1,2,3]);  //传入数组

console.log(arr);   //[123,[1,2,3]]

//注意apply和call的用法区别
[].push.call(arr,4,5,6);
console.log(arr);  //[123,[1,2,3],4,5,6]
[].push.apply(arr,[7,8,9]);
console.log(arr);   //[123,[1,2,3],4,5,6,7,8,9]


//走的不是源码的else
//如果是else
//变成了[[1,2,3],[4,5,6]]
console.log($.makeArray([1,2,3],[4,5,6])); //1,2,3,4,5,6

5.23 $.inArray()

  • 数组版的indexOf()

源码

inArray: function( elem, arr, i ) {
	//i是indexOf的第二个参数,搜索的起始位置
	//详见(一)
	return arr == null ? -1 : core_indexOf.call( arr, elem, i );
},

内容解析

(一) indexOf()

//字符串索引
console.log('12345'.indexOf('3',4)); //-1
console.log('12345'.indexOf('3',1)); //2
console.log('12345'.indexOf('3',2)); //2
console.log('12345'.indexOf('3'));   //2


//数组索引
console.log([].indexOf.call([1,2,3,4,5],3)); //2

//jQuery数组索引
console.log($.inArray(2,[1,2,3,4,5]));  //1

5.24 $.merge()

  • 合并数组

  • 对外 转数组

  • 对内 转json

  • 针对情况[] {}, {}可能有length也可能没有length

merge: function( first, second ) {
	var l = second.length,
		i = first.length,
		j = 0;

	// $.merge(['a','b'],['a','b'])
	// second不是数组,没有length属性
	if ( typeof l === "number" ) {
		for ( ; j < l; j++ ) {
			first[ i++ ] = second[ j ];
		}
	// $.merge(['a','b'],{0:'a',1:'b'})
	} else {
		while ( second[j] !== undefined ) {
			first[ i++ ] = second[ j++ ];
		}
	}

	first.length = i;

	return first;
},

5.25 $.grep()

  • 过滤数组,返回新数组
  • 第三个参数 布尔值

源码

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
	// 只有数组才会遍历(类数组)
	for ( ; i < length; i++ ) {
		retVal = !!callback( elems[ i ], i );
		if ( inv !== retVal ) {
			ret.push( elems[ i ] );
		}
	}

	return ret;
},

内容解析

var arr = [1,2,3,4];

var f = function(value,index) {
    return 1     //1类似于true
};

var f1 = function(value,index) {
    return value > 2
};

console.log($.grep(arr,f));         //[1,2,3,4]
console.log($.grep(arr,f1));        //[3,4]
console.log($.grep(arr,f1,true));   //[1,2]

5.26 $.map()

  • 改变数组value,返回新数组

源码

// arg is for internal usage only
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 ) {
		for ( ; i < length; i++ ) {
			value = callback( elems[ i ], i, arg );

			if ( value != null ) {
				// 注意是ret.length 会自动递增的
				ret[ ret.length ] = value;
			}
		}

	// Go through every key on the object,
	// Json格式
	} else {
		for ( i in elems ) {
			value = callback( elems[ i ], i, arg );

			if ( value != null ) {
				ret[ ret.length ] = value;
			}
		}
	}

	// Flatten any nested arrays
	// 返回的是单数组,而不是复合数组
	return core_concat.apply( [], ret );
},

内容解析

var arr = [1,2,3];

var newArr = $.map(arr,function(value,index) {
    return value + 1;
});

console.log(newArr); //[2,3,4]

5.27 $.guid

  • 取消绑定事件有关系
  • 唯一标识符,用于标识事件函数

// A global GUID counter for objects guid: 1,

5.28 $.proxy()

  • 类似于callapply,改变this指向

源码

// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
	var tmp, args, proxy;

	// obj = {fn : function(){}}
	// $.proxy(obj,'fn') 情况
	// 详见(一)
	if ( typeof context === "string" ) {
		tmp = fn[ context ];
		context = fn;
		fn = tmp;
	}

	// Quick check to determine if target is callable, in the spec
	// this throws a TypeError, but we will just return undefined.
	// 如果不是函数
	if ( !jQuery.isFunction( fn ) ) {
		return undefined;
	}

	// Simulated bind
	// $.proxy()参数可以追加, 去除第一第二参数fn和context
	// 详见(二)
	args = core_slice.call( arguments, 2 );
	// $.proxy(arg1)(arg2) 这个扩展方法返回的是一个可执行的函数
	// apply改变this指向
	// 如果没有指定context 使用默认this
	// apply第二个参数是[],call后面可以跟n个参数
	// 将arguments类数组对象使用slice.call转化为数组
	// 将arg1和arg2合并,注意arg1和arg2的归属函数
	// arguments是arg2
	// 详见(三)
	proxy = function() {
		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
	// 设置唯一事件标识符
	// 如果要取消事件就能找到
	// 详见(四)
	proxy.guid = fn.guid = fn.guid || jQuery.guid++;

	// 返回的是函数
	return proxy;
},

内容解析

(一) $.proxy(obj, 'fn')

var obj = {
  show: function() {
      console.log(this);
  }
};

$(document).click(obj.show);   //绑定事件函数中的this默认指向绑定对象$(document)
$(document).click($.proxy(obj,'show'));     //改变了绑定事件函数中的this指向,指向了obj,需要注意的是$.proxy没有执行,点击事件之后才会执行

(二) 转数组

  var json = {
      0: 0,
      1: 1,
      2: 2,
      length:3
  }

  //slice默认不传参数就是起始开始,末尾结束
  console.log(Array.isArray([].slice.call(json))) //true
  console.log([].slice.call(json));               //[0,1,2]

(三) $.proxy()参数详见

var obj = {
	show: function(a,b) {
	      console.log(a);
	      console.log(b);
	      console.log(this);
	  }
};


$.proxy(obj.show,obj,1,2)(); //1,2,obj
$.proxy(obj.show,obj,1)(2);
$.proxy(obj.show,obj)(1,2);  //全都是一样的

(四) 事件绑定

function show() {
  console.log(this);
}

show();                 //Window

$(document).click(
    show                //绑定事件,Document
);

$(document).off()       //取消绑定

需要注意的是一般情况下, 想要取消绑定事件,需要调用同一个绑定事件的引用,例如以下取消绑定事件是会失败的,所以就有了唯一标识符guid,因为使用$.proxy()很容易改变绑定的事件函数,不使用唯一标识符的话,就不能取消绑定了

//绑定事件
document.addEventListener('click',function(){
    alert(1);
});

//取消绑定,并不能取消,因为事件函数并不是同一个引用对象
document.removeEventListener('click',function(){
   alert(1);
});


//正确的形式

//绑定事件
document.addEventListener('click',show);


//取消绑定,因为事件函数指向了同一个事件函数的引用
document.removeEventListener('click',show);

function show() {
    alert(1);
}

5.29 $.access()

  • 多功能函数的操作 底层工具方法
  • 内部使用

源码

// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
// key -> witdh
// value -> 200px
// chainable -> 获取还是设置
access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		length = elems.length,
		// 有值或者没值
		bulk = key == null;

	// Sets many values
	// 设置多组值
	// $('#div1').css({width:'200px',background:'yellow'})
	// key是Object
	if ( jQuery.type( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			//递归调用
			jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
		}

	// Sets one value
	// 如果是一组值
	// $('#div1').css('width','200px')
	} else if ( value !== undefined ) {
		chainable = true;

		// value是否是函数
		if ( !jQuery.isFunction( value ) ) {
			raw = true;
		}

		// 如果没有Key值
		if ( bulk ) {
			// Bulk operations run against the entire set
			// 如果value是字符串
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				// 如果是函数,则套上一个fn,并不是立即执行的
				bulk = fn;
				fn = function( elem, key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}


		// 存在key值得情况下
		if ( fn ) {
			for ( ; i < length; i++ ) {
				fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
			}
		}
	}

	// 判断是设置还是获取
	return chainable ?
		elems :

		// Gets
		// 获取
		bulk ?
			fn.call( elems ) :
			length ? fn( elems[0], key ) : emptyGet;
},

内容解析

 //$().css() \ $().val() \ $().attr()等方法都调用了$.access()工具方法
 //$.access() 多功能值操作(内部)

 //获取样式 一个参数
 console.log($('#div1').css('width'));   //100px

 //设置样式 两个参数
 $('#div1').css('width','200px')

 //设置样式 一个对象参数
 $('#div1').css({width:'200px',background:'yellow'})

5.30 $.now()

  • 获取时间
//和(new Date()).getTime()功能类似
//ECMAScript 5方法
now: Date.now,

5.31 $.swap()

  • css属性交换
// 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.
swap: function( elem, options, callback, args ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	// options当然是要设置的属性
	for ( name in options ) {
		// 把旧的属性先保存下来
		old[ name ] = elem.style[ name ];
		// 设置属性
		elem.style[ name ] = options[ name ];
	}

	// 这里是获取元素的某些参数
	ret = callback.apply( elem, args || [] );

	// Revert the old values
	// 还原css属性
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
}

内容解析

  • 交换样式有时候就如例子这么有用
var $div = $('#div1')
   , divDom = $div.get(0);

console.log(divDom.offsetWidth); //100

var oldStyle = divDom.style.cssText;  //保留老的样式

divDom.style.display = 'none';
console.log(divDom.offsetWidth); //0 当display: none时不能获取


//让内容脱离正常流,绝对定位,不可见,此时看起来页面的样式没有变
divDom.style.display = 'block';
divDom.style.visibility = 'hidden';
divDom.style.position = 'absolute';
console.log(divDom.offsetWidth); //100 此时可以获取宽度了

//重新改回原来的样式
divDom.style.cssText = oldStyle;

5-6.私有方法isArraylike

需要注意这是一个私有方法,不具备成立单独的一大节,所以此节命名为5-6,在整个自执行匿名函数中都可以调用,对外不可见.

源码

(function(window,undefined) {

    //[849]
    function isArraylike( obj ) {
        var length = obj.length,
                type = jQuery.type( obj );

        //如果是Window对象
        if ( jQuery.isWindow( obj ) ) {
            return false;
        }

        //如果是Node节点集合,类数组形式
        if ( obj.nodeType === 1 && length ) {
            return true;
        }

        //需要注意第一个||
        //obj不是函数且length=0 也算Array
        //obj不是函数length!=0且length是num且length>0且length-1是obj的key
        //需要注意如果去掉length = 0 的情况那么后面的就不好判断了,因为length-1 可能是-1了
        return type === "array" || type !== "function" &&
                ( length === 0 ||
                typeof length === "number" && length > 0 && ( length - 1 ) in obj );
    }

})(window);

内容解析:

isArraylike([]);
isArraylike({length:0});        //true
isArraylike({a:1,length:1});    //false, 0 in obj不存在
isArraylike({1:'a',length:1});  //false, 1 in obj也不存在
isArraylike({0:'a',length:1})   //true
//DOM节点也是可以的