前言:jQuery的工具方法是jQuery源码中的基石,是构建庞大的jQuery库的根本。这些工具方法也给我们的编程带来了很多便利。
【jquery源码】目录
一、修改$.extend()
【jquery源码三】jQuery是如何创建方法和扩展方法
前面的文章也说到了$.extend(),$.fn.extend()的作用。我们这里进行简易的修改,一样实现扩展方法的效果。
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy,
target = arguments[0] || {},
i = 1,
length = arguments.length,
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
if ( (options = arguments[ i ]) != null ) {
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
target[ name ] = copy;
}
}
}
return target;
};
二、jQuery工具方法
1、因为工具方法比较多,我们先来看看以下这些工具方法和扩展属性
jQuery.extend({
noConflict: function(){},
isReady: false,
readyWait: 1,
holdReady: function(){},
ready: function(){},
each: function(obj, callback, args){},
isFunction: function( obj ){},
isArray: function(){},
isWindow: function( obj ){},
isNumeric: function( obj ){},
type: function( obj ){},
isPlainObject: function(){},
isEmptyObject: function( obj ){},
error: function( msg ){},
parseJSON: function(){},
trim: function( text ){}
});
2、模拟jQuert扩展属性跟方法
(function(window,undefined){
var _$ = window.$,
_jQuery = window.jQuery;
var jQuery = function(selector){
return new jQuery.fn.init(selector);
};
jQuery.fn = jQuery.prototype = {
jquery:'2.0.3', //jquery版本号信息
constructor: jQuery, //添加构造器属性
length:0, //初始length属性
selector:'', //初始selector属性
init: function(selector){
if(selector.nodeType){
this.context = this[0] = selector;
this.length = 1;
}else if(typeof selector === 'function'){
return jQuery(document).ready(selector);
}
},
ready: function(fn){
jQuery.ready.promise().done( fn );
return this;
},
};
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
if ( (options = arguments[ i ]) != null ) {
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
target[ name ] = copy;
}
}
}
return target;
};
jQuery.extend({
noConflict: function(deep){
if( window.$$ === jQuery ){
window.$ = _$;
}
if( deep && window.jQuery === jQuery ){
window.jQuery = _jQuery;
}
return jQuery;
},
isReady: false,
readyWait: 1,
holdReady: function(){},
ready: function(){},
each: function(obj, callback, args){},
isFunction: function( obj ){},
isArray: Array.isArray,
isWindow: function( obj ){},
isNumeric: function( obj ){},
type: function( obj ){},
isPlainObject: function( ){},
isEmptyObject: function( obj ){},
error: function( msg ){},
parseJSON: JSON.parse,
trim: function( text ){}
});
window.$$ = window.jQuery = jQuery;
})( window );
1、noConflict:解决jQuery或$ 命名冲突
①、noConflict的使用例子
<script>
var $ = '$等待被覆盖';
var jQuery = 'jQuery等待被覆盖';
console.log($);
console.log(jQuery);
</script>
<script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.js"></script>
<script>
console.log($);
console.log(jQuery);
var $ = '覆盖$';
var jQuery = '覆盖jQuery';
console.log($);
console.log(jQuery);
$(function(){
console.log('$失效了');
});
</script>
</body>
输出结果:
这时就可以看到,最先定义的$,jQuery跟在引入jquery.js后,就被覆盖了jQuery覆盖了,然后我们再次定义$,jQuery,又把jQuery对象覆盖了,这时候$、jQuery也失效了。这时候就需要用到noConflict了。
<body>
<script>
var $ = '$等待被覆盖';
var jQuery = 'jQuery等待被覆盖';
console.log($);
console.log(jQuery);
</script>
<script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.js"></script>
<script>
var $$ = $.noConflict();
console.log($);
var wQ = $$.noConflict(true);
console.log(jQuery);
$$(function(){
console.log('$$替换了$');
});
wQ(function(){
console.log('wQ替换了jQuery');
});
</script>
</body>
输出结果:
这时候就不担心命名冲突问题啦。
②、noConflict实现原理
noConflict: function(deep){
if( window.$$ === jQuery ){
window.$ = _$;
}
if( deep && window.jQuery === jQuery ){
window.jQuery = _jQuery;
}
return jQuery;
}
在源码头部创建了_$跟_jQuery用来储存在jQuery引入之前-全局创建的$,jQuery的值。然后在noConflict方法中,将之前储存的_$跟_jQuery赋值还给全局的$跟jQuery,然后return出去局部的jQuery对象。
2.isReady 3.readyWait 4.holdReady 5.ready
这两个属性跟两个方法设计到$(document).ready()的使用,较为复杂,后面会有单独一篇文章来说他们。
6、each
each相信大家也用的比较多了,用于数组,类数组、对象、json数据的遍历。
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;
}
}
}
} else { //我们外部使用一般走这里
if ( isArray ) { //是数组或类数组用for循环
for ( ; i < length; i++ ) {
//这里的第一个参数是改变this指向,第二个参数是索引i,第三个参数是值
//value是callback运行后的返回值
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else { //不是数组或类数组的就用for in 循环
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
}
return obj;
}
有第三个参数argsr一般都是jQuery源码中为了处理更加复杂的循环的时候用到的,我们现实开发工程中并不常用到。
实验:
<body>
<script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.js"></script>
<script>
var arr = ['nick','freddy','mike'];
$.each(arr, function(i,item){
if(item === 'freddy'){
console.log(item);
return false;
}
console.log(item);
});
</script>
</body>
运行结果:
像这样,设置如果当item===‘freddy’的时候,return false; 源码中遍历到'freddy'的时候value = false。于是触发了break,跳出了循环。
7、isFunction(obj) 用于检测传入的参数是否是函数。
这里调用了jQuery.type方法,查看获取到的类型是否与'function'相等
isFunction: function( obj ){
return jQuery.type(obj) === "function";
}
11、type(obj) 用于检测传入参数的数据类型。
var class2type = {},
core_toString = class2type.toString;
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
输出class2type来看看
type: function( obj ){
if ( obj == null ) {
return String( obj );
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[ core_toString.call(obj) ] || "object" :
typeof obj;
}
如有不太明白的朋友可以看看我的这篇文章。
【JavaScript】封装可以辨别全部数据类型的方法
8、isArray(obj) 用于检测传入的参数是否是数组。
isArray: Array.isArray
这里直接引用了原生js中的Array.isArray方法。该ECMAScript5下的用于检测是否是数组,该方法不支持iE6,7,8
9、isWindow(obj) 用于检测传入的参数是否是window对象。
isWindow: function( obj ) {
return obj != null && obj === obj.window;
}
只有window对象才有名字为window的属性
10、isNumeric(obj) 用于检测传入的参数是否是数字类型的
isNumeric: function( obj ) {
return !isNaN( parseFloat(obj) ) && isFinite( obj );
}
看下以下例子就能看懂该源码了
<body>
<script>
console.log(parseFloat('123a'));
console.log(Number('123a'));
console.log(typeof NaN);
console.log(typeof Infinity);
console.log(isFinite(123));
console.log(isFinite(Infinity));
</script>
</body>
输出结果
这里显示的NaN 还有 Infinity(无穷大数) 的数据类型竟然也是number,这个很不合理。还有的是,parseFloat('123a')竟然把该字符串转成了123数字,我觉得该源码不是很合理,所以我改成了以下代码。
isNumeric: function( obj ){
return !isNaN( Number(obj) ) && isFinite( obj );
}
12、isPlainObject(obj) 用于检测传入的参数是否是对象自变量,也是我们常说的键值对格式对象
js中的对象还是蛮多了,例如DOM节点对象,还有window对象,window.location
isPlainObject: function(obj){
if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
return true;
}
DOM节点对象都有nodeType属性
13、isEmptyObject 用于检测传入的参数是否是空对象
isEmptyObject: function( obj ) { //判断是否为空的对象
var name;
for ( name in obj ) {
return false;
}
return true;
}
空对象不能进行for in循环
14、error 用于在控制台输出错误信息
error: function( msg ){
throw new Error( msg );
}
<body>
<script>
throw new Error('这是错误信息');
</script>
</body>
输出结果:
15、parseJSON 用于将字符串转成JSON格式数据
parseJSON: JSON.parse
这里也是直接引用了原生js中的JSON.parse方法。
16、trim 用于去除' string '前后空格
trim: function( text ) {
return text == null ? "" : "".trim.call( text );
}
<body>
<script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.js"></script>
<script>
var str = ' string ';
console.log(str.length);
console.log($.trim(str).length);
</script>
</body>
输出结果: