jQuery源代码阅读之二——jQuery静态属性和方法
重点掌握以下这些方法(可以手写代码):
1.深复制与浅复制
jQuery.extend
2.类型判断相关方法
jQuery.type,jQuery.isFunction,jQuery.isArray,jQuery.isWindow,jQuery.isNumberic,jQuery.isPlainObject,jQuery.isArrayLike
3.数组相关方法
jQuery.each,jQuery.map,jQuery.grep
4.转驼峰的实现
jQuery.camelCase
一、jQuery.extend/jQuery.fn.extend
//可接受的参数类型如下:jQuery.extend([deep],target,object1,[objectN]) jQuery.extend = jQuery.fn.extend = function() { var target = arguments[0] || {}, //指向目标对象 deep = false, //是否进行深度复制 i = 1, //表示源对象的起始下标 length = arguments.length, //表示参数个数; options, name, src, copy, copyIsArray; //options指向某个源对象,name指向源对象的某个属性名,src目标对象某个属性的原始值,copy某个源对象的某个属性的值,copyIsArray指示变量copy是否为数组 //首先进行参数修正 if (typeof target === 'boolean') { deep = target; target = arguments[1] || {}; i = 2; } //此时target就是jQuery或jQuery.fn if (i === length) { target = this; i--; } //处理target是字符串或者其他情形,这在深度复制中可能出现 // if(typeof target!=='object'||!jQuery.isFunction(target)){ // target={}; // } for (i; i < length; i++) { options = arguments[i]; for (name in options) { src = target[name]; copy = options[name]; if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } //递归调用 target[name] = jQuery.extend(deep, clone, copy); } else { target[name] = copy; } } } return target; };
二、jQuery静态方法和属性
大部分jQuery静态方法为实例方法提供底层支持,相关代码结构如下:
jQuery.extend({ //一堆静态方法和属性 expando:'...', noConflict:function(deep){}, isReady:false, readyWait:1, holdReady:function(hold){}, ready:function(){}, isFunction:function(obj){}, isArray:Array.isArray, isWindow:function(obj){}, isNumeric:function(obj){}, type:function(obj){}, isPlainObject:function(obj){}, isEmptyObject:function(obj){}, error:function(msg){}, parseHTML: function( data, context, keepScripts ){}, parseJSON:JSON.parse, parseCML:function(data){}, noop:function(){}, globalEval:function(data){}, camelCase:function(string){}, nodeName:function(elem,name){}, each:function(object,callback,args){}, trim:function(string){}, makeArray:function(array,results){}, inArray:function(elem,array,i){}, merge:function(first,second){}, grep:function(elems,callback,inv){}, map:function(elems,classback,arg){}, guid:1, proxy:function(fn,context){}, access: function( elems, fn, key, value, chainable, emptyGet, raw ), now:Date.now, swap: function( elem, options, callback, args ), });
下面逐一对上述方法的实现进行介绍,方法的作用,实现原理不少都在源代码的注释中,请展开来看。
2.1 jQuery.expando
expando:'jQuery'+(core_version+Math.random()).replace(/\D/g,''),
2.2 jQuery.noConflict
在变量区域定义:
_jQuery=window.jQuery,
_$=window.$,
noConflict:function(deep){ if(window.$===jQuery){ window.$=_$; } if(deep&&window.jQuery===jQuery){ window.jQuery=_jQuery; } return jQuery; },
2.3 (todo)ready事件相关静态方法
JQuery.isReady,jQuery.readyWait;jQuery.holdReady;jQuery.ready 这几个静态方法与事件相关,暂且略过
2.4 类型判断相关静态方法,
包括jQuery.isFunction,jQuery.isArray,jQuery.isWindow,jQuery.isNumeric,jQuery.type.这里方法的底层大部分都基于jQuery.type实现.
具体实现如下
/****下面是一系列类型检测的静态方法*******/ isFunction:function(obj){ //如果使用typeof,在有些浏览器中,正则也会返回function,因此这里采用jQuery处理后的方法,jQuery.type return jQuery.type(obj)==='function'; }, isArray:Array.isArray, isWindow:function(obj){ return obj!==null&&obj===obj.window; }, //判断obj是否为数字或者数字类型的字符串,并且是有效数字 isNumeric:function(obj){ return !isNaN(parseFloat(obj))&&isFinite(obj); },
// 每个对象都有一个toString()
方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()
方法被每个Object
对象继承。
// 如果此方法在自定义对象中未被覆盖,toString()
返回 "[object type]",其中type
是对象的类型。
type:function(obj){ if(obj===null){ return String(null); } //Date,Array等类型typeof都会返回object,function、正则(部分浏览器)中 typeof都会返回function if(typeof obj==='object'||typeof obj==='function'){ return class2type[core_toString.call(obj)]; } return typeof obj; },
其中在变量区域定义class2type
class2type={},
并为之赋值
//目前,js中typeof的返回值有六种:"number," "string," "boolean," "object," "function," 和 "undefined." //通过object.prototype.toString/或者{}.toString 返回值有九种:Boolean Number String Function Array Date RegExp Object Error,其中的Array,Date,RegExp,Object,Error都属于Object类型,在有些浏览器中typeof 正则会返回function jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(i,name){ class2type["[object "+name+"]"]=name.toLowerCase(); });
2.4 jQuery对象判断相关方法
jQuery.isPlainObject,jQuery.isEmptyObject
jQuery.isPlainObject判断的原理,在于判断一些特定的方法,比如isPrototypeOf,hasOwnProperty,是否位于对象自身的原型,而非继承得到的原型链中
isPlainObject:function(obj){ if(jQuery.type(obj)!=='object'||obj.nodeType||jQuery.isWindow(obj)){ return false; } //如果是纯粹的对象,那么obj一定有constructor属性,并且方法hasOwnPropertyOf一定就在构造函数本身的原型中,而不用通过原型链查找得到 if(obj.constructor&&!core_hasOwn.call(obj.constructor.prototype,'isPrototypeOf')){ return false; } return true; }, //检查是否是空对象 isEmptyObject:function(obj){ for(var name in obj){ return false; } return true; },
2.5 jQuery数组遍历相关方法
jQuery.each;jQuery.grep;jQuery.map
each:function(object,callback,args){ var i, value, length=object.length, isArray=isArrayLike(object); if(args){//说明是内部调用 if(isArray){ for(i=0;i<length;i++){ value= callback.call(object[i],args); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],args); if(value===false){ break; } } } }else{ if(isArray){ for(i=0;i<length;i++){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } } } return object; }, //用于查找数组中满足过滤函数的元素,形成新的数组之后返回,原数组不受影响 //如果inv未传入或者是false,元素只有在过滤函数返回true时,才会被保存在最终的结果数组中 //如果参数inv是true,则恰好相反 grep:function(elems,callback,inv){ var i, ret=[], length=elems.length, retVal; inv=!!inv; for(i=0;i<length;i++){ retVal=!!callback.call(elems[i],i); if(retVal!==inv){ ret.push(elems[i]); } } return ret; }, //用于对数组中每个元素执行callback操作,并将结果形成新的数组返回 //参数arg仅仅是jQuery内部使用 map:function(elems,callback,arg){ var ret=[], retVal, i, length=elems.length, isArray=isArrayLike(elems); if(isArray){ for(i=0;i<length;i++){ retVal=callback.call(elems[i],i,arg); if(retVal!=null){ ret.push(retVal); } } }else{ for(i in elems){ retVal=callback.call(elems[i],i,arg); if(retVal!=null){ ret.push(retVal); } } } //保证最终返回的是一维数组 return core_concat.call([],ret); },
2.6 jQuery.error
error:function(msg){ throw new Error(msg); },
2.7(todo) jQuery.parseHTML,jQuery.parseJSON,jQuery.parseXML
parseHTML: function( data, context, keepScripts ){ }, parseJSON:JSON.parse, 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; },
2.8 jQuery.noop,(todo)jQuery.globalEval,jQuery.camelCase,jQuery.nodeName
noop:function(){}, //用于在全局作用域执行javascript代码,这里暂略 globalEval:function(data){}, //转换连字符字符串为驼峰类型 camelCase:function(string){ return string.replace(rmsPrefix,'ms-').replace(rdashAlpha,fcamelCase); }, //判断elem的nodeName是否=name nodeName:function(elem,name){ return elem.nodeName&&elem.nodeName.toLowerCase()==name.toLowerCase(); },
2.9 jQuery.makeArray,jQuery.inArray
makeArray:function(array,results){ var ret=results||[], type=jQuery.type(array); //undefined,null都会==null if(array!=null){ //1,没有length属性,或者具有length属性,但是是以下几种情况的 //2.如果array是string 的length表示字符串的长度 //3.如果array是函数,其length代表函数生命时的参数个数 //4,如果array是window对象,属性Length返回窗口中的框架(frame,iframe)个数 if(array.length==null|| type=='string' || type=='function' ||type=='regexp'||jQuery.isWindow(array)){ core_push.call(ret,array); }else{//否则说明是类数组对象 jQuery.merge(ret,array); } } return ret; }, inArray:function(elem,array,i){ return array==null?-1:core_indexOf.call(array,elem,i); },
2.10 jQuery.merge
//用于合并两个数组的元素到第一个数组中 //事实上,jquery源代码中第一个参数可以是数组或者类数组对象,第二个参数可以是数组、类数组对象或任何含有连续整型属性的对象 //第一个参数是数组,最后返回数组;第一个参数是类数组,则返回类数组 merge:function(first,second){ var l=second.length, i=first.length, j; for(j=0;j<l;j++){ first[i++]=second[j]; } first.length=i; return first; },
2.11 jQuery.guid
guid:1,
2.12 jQuery.proxy
//该方法用于更改函数的执行上下文 //源代码中有两种传参形式,这里仅考虑最常见的一种 proxy:function(fn,context){ if(!jQuery.isFunction(fn)){ return undefined; } var args=core_slice.call(arguments,2); proxy=function(){ return fn.call(context||this,core_concat.call(args,core_slice.call(arguments))); }; proxy.guid=fn.guid=fn.guid||jQuery.guid++; return proxy; },
2.13 (todo) jQuery.access
//用一个方法同时实现get和set操作 //如何设置或者获取由回调函数fn确定 //这个方法的实现等用到的时候结合来看 access: function( elems, fn, key, value, chainable, emptyGet, raw ){ },
2.14 jQuery.now,jQuery.swap
now:Date.now, //该方法用于交换css样式,在support模块较多用到 //要交换的样式由参数options传递 swap: function( elem, options, callback, args ){ var name,ret, old={}; for(name in options){ old[name]=elem.style[name]; elem.style[name]=options[name]; } ret=callback.call(elem,args||[]); for(name in options){ elem.style[name]=old[name]; } return ret; },
截止目前,myJquey的当前版本代码:
(function(window,undefined){ var rootjQuery, core_version='2.0.3', idExpr=/^#([\w\-]*)$/, //下面两个正则用于转驼峰 rmsPrefix = /^-ms-/, rdashAlpha = /-([\da-z])/gi, class2type={}, core_deletedIds=[], _jQuery=window.jQuery, _$=window.$, core_toString=class2type.toString, core_hasOwn=class2type.hasOwnProperty, core_trim=core_version.trim, core_indexOf=core_deletedIds.indexOf, core_push=core_deletedIds.push, core_concat=core_deletedIds.concat, core_slice=core_deletedIds.slice, //用于jQuery.camelCase转驼峰函数中 //当replace函数只有一个匹配项时,第二个参数可以是一个函数 //如果repalce中的正则没有捕获组,会向这个函数传递三个参数:模式的匹配项,模式匹配项在字符串中的位置,原始字符串 //如果replace中的正则有捕获组,也会向这个函数传递三个参数,模式的匹配项,捕获组的匹配项,模式匹配项在字符串中的位置 fcamelCase=function(all,letter){ return letter.toUpperCase(); }, jQuery=function(selector,context){ return jQuery.fn.init(selector,context,rootjQuery); }; jQuery.fn=jQuery.prototype={ constructor:jQuery, init:function(selector,context,rootjQuery){ var match,elem; //selector是选择器表达式 if(typeof selector ==='string'){ match=idExpr.exec(selector); if(match&&!context){ elem=document.getElementById(match[1]); if(elem&&elem.parentNode){ this[0]=elem; this.length=1; } this.selector=selector; this.context=document; return this; }else{ //说明是复杂的选择器表达式,这里暂不考虑 } } //处理selector是DOM元素的情形 if(selector&&selector.nodeType){ this[0]=selector; this.length=1; this.context=selector; return this; } //处理selector是函数的情形 if(jQuery.isFunction(selector)){ return rootjQuery.ready( selector ); } //处理selector是jQuery对象的情形 if(selector.selector){ this.selector=selector.selector; this.context=selector.context; } //处理其他情形 return jQuery.makeArray(selector,this); } }; jQuery.fn.init.prototype=jQuery.prototype; //可接受的参数类型如下:jQuery.extend([deep],target,object1,[objectN]) jQuery.extend=jQuery.fn.extend=function(){ var target=arguments[0]||{},//指向目标对象 deep=false,//是否进行深度复制 i=1,//表示源对象的起始下标 length=arguments.length,//表示参数个数; options,name,src,copy,copyIsArray;//options指向某个源对象,name指向源对象的某个属性名,src目标对象某个属性的原始值,copy某个源对象的某个属性的值,copyIsArray指示变量copy是否为数组 //首先进行参数修正 if(typeof target==='boolean'){ deep=target; target=arguments[1]||{}; i=2; } //此时target就是jQuery或jQuery.fn if(i===length){ target=this; i--; } //处理target是字符串或者其他情形,这在深度复制中可能出现 // if(typeof target!=='object'||!jQuery.isFunction(target)){ // target={}; // } for(i;i<length;i++){ options=arguments[i]; for(name in options){ src=target[name]; copy=options[name]; if(deep&©&&(jQuery.isPlainObject(object)||(copyIsArray=jQuery.isArray(object)))){ if(copyIsArray){ copyIsArray=false; clone=src&&jQuery.isArray(src)?src:[]; }else{ clone=src&&jQuery.isPlainObject(src)?src:{}; } target[name]=jQuery.extend(deep,clone,copy); }else{ target[name]=copy; } } } return target; }; //检查是否是数组或者类数组 function isArrayLike(obj){ var length=obj.length, type=jQuery.type(obj); if(obj&&jQuery.isWindow(obj)){ return false; } if(obj.nodeType===1&&length){ return true; } if(type==='array'){ return true; } if(typeof length==='number'&&(length==0||(length>0&&(length-1) in obj))){ return true; } return false; } jQuery.extend({ //一堆静态方法和属性 expando:'jQuery'+(core_version+Math.random()).replace(/\D/g,''), // 该函数用于释放jQuery对于全局变量$的控制权,可选的参数deep代表是否释放对全局变量jQuery的控制权 noConflict:function(deep){ if(window.$===jQuery){ window.$=_$; } if(deep&&window.jQuery===jQuery){ window.jQuery=_jQuery; } return jQuery; }, /********isReady,readyWait,holdReay,ready与加载事件有关,暂且略过***********/ isReady:false, readyWait:1, holdReady:function(hold){}, ready:function(){}, /*******/ /****下面是一系列类型检测的静态方法*******/ isFunction:function(obj){ //如果使用typeof,在有些浏览器中,正则也会返回function,因此这里采用jQuery处理后的方法,jQuery.type return jQuery.type(obj)==='function'; }, isArray:Array.isArray, isWindow:function(obj){ return obj!==null&&obj===obj.window; }, //判断obj是否为数字或者数字类型的字符串,并且是有效数字 isNumeric:function(obj){ return !isNaN(parseFloat(obj))&&isFinite(obj); }, type:function(obj){ if(obj===null){ return String(null); } //Date,Array等类型typeof都会返回object,function、正则(部分浏览器)中 typeof都会返回function if(typeof obj==='object'||typeof obj==='function'){ return class2type[core_toString.call(obj)]; } return typeof obj; }, //判断是否为以下两种情况:1,对象字面量;2,通过new Object()创建 isPlainObject:function(obj){ if(jQuery.type(obj)!=='object'||obj.nodeType||jQuery.isWindow(obj)){ return false; } //如果是纯粹的对象,那么obj一定有constructor属性,并且方法hasOwnPropertyOf一定就在构造函数本身的原型中,而不用通过原型链查找得到 if(obj.constructor&&!core_hasOwn.call(obj.constructor.prototype,'isPrototypeOf')){ return false; } return true; }, //检查是否是空对象 isEmptyObject:function(obj){ for(var name in obj){ return false; } return true; }, /******类型检测静态方法结束********/ error:function(msg){ throw new Error(msg); }, //将html字符串转换为html DOM结构, parseHTML: function( data, context, keepScripts ){ }, parseJSON:JSON.parse, 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; }, noop:function(){}, //用于在全局作用域执行javascript代码,这里暂略 globalEval:function(data){}, //转换连字符字符串为驼峰类型 camelCase:function(string){ return string.replace(rmsPrefix,'ms-').replace(rdashAlpha,fcamelCase); }, //判断elem的nodeName是否=name nodeName:function(elem,name){ return elem.nodeName&&elem.nodeName.toLowerCase()==name.toLowerCase(); }, //jQuery遍历方法,其中args是传递给回调callback的参数,仅供jQuery内部使用;外部调用该方法时,回调的参数默认为数组下标/对象key,对应数组值/对象value each:function(object,callback,args){ var i, value, length=object.length, isArray=isArrayLike(object); if(args){//说明是内部调用 if(isArray){ for(i=0;i<length;i++){ value= callback.call(object[i],args); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],args); if(value===false){ break; } } } }else{ if(isArray){ for(i=0;i<length;i++){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } } } return object; }, trim:function(str){ return str==null?'':core_trim.call(str); }, //将一个类数组对象转换为真正的对象 //results参数仅供jquery内部使用,此时在该参数的基础上添加元素 makeArray:function(array,results){ var ret=results||[], type=jQuery.type(array); //undefined,null都会==null if(array!=null){ //1,没有length属性,或者具有length属性,但是是以下几种情况的 //2.如果array是string 的length表示字符串的长度 //3.如果array是函数,其length代表函数生命时的参数个数 //4,如果array是window对象,属性Length返回窗口中的框架(frame,iframe)个数 if(array.length==null|| type=='string' || type=='function' ||type=='regexp'||jQuery.isWindow(array)){ core_push.call(ret,array); }else{//否则说明是类数组对象 jQuery.merge(ret,array); } } return ret; }, inArray:function(elem,array,i){ return array==null?-1:core_indexOf.call(array,elem,i); }, //用于合并两个数组的元素到第一个数组中 //事实上,jquery源代码中第一个参数可以是数组或者类数组对象,第二个参数可以是数组、类数组对象或任何含有连续整型属性的对象 //第一个参数是数组,最后返回数组;第一个参数是类数组,则返回类数组 merge:function(first,second){ var l=second.length, i=first.length, j; for(j=0;j<l;j++){ first[i++]=second[j]; } first.length=i; return first; }, //用于查找数组中满足过滤函数的元素,形成新的数组之后返回,原数组不受影响 //如果inv未传入或者是false,元素只有在过滤函数返回true时,才会被保存在最终的结果数组中 //如果参数inv是true,则恰好相反 grep:function(elems,callback,inv){ var i, ret=[], length=elems.length, retVal; inv=!!inv; for(i=0;i<length;i++){ retVal=!!callback.call(elems[i],i); if(retVal!==inv){ ret.push(elems[i]); } } return ret; }, //用于对数组中每个元素执行callback操作,并将结果形成新的数组返回 //参数arg仅仅是jQuery内部使用 map:function(elems,callback,arg){ var ret=[], retVal, i, length=elems.length, isArray=isArrayLike(elems); if(isArray){ for(i=0;i<length;i++){ retVal=callback.call(elems[i],i,arg); if(retVal!=null){ ret.push(retVal); } } }else{ for(i in elems){ retVal=callback.call(elems[i],i,arg); if(retVal!=null){ ret.push(retVal); } } } //保证最终返回的是一维数组 return core_concat.call([],ret); }, guid:1, //该方法用于更改函数的执行上下文 //源代码中有两种传参形式,这里仅考虑最常见的一种 proxy:function(fn,context){ if(!jQuery.isFunction(fn)){ return undefined; } var args=core_slice.call(arguments,2); proxy=function(){ return fn.call(context||this,core_concat.call(args,core_slice.call(arguments))); }; proxy.guid=fn.guid=fn.guid||jQuery.guid++; return proxy; }, //用一个方法同时实现get和set操作 //如何设置或者获取由回调函数fn确定 //这个方法的实现等用到的时候结合来看 access: function( elems, fn, key, value, chainable, emptyGet, raw ){ }, now:Date.now, //该方法用于交换css样式,在support模块较多用到 //要交换的样式由参数options传递 swap: function( elem, options, callback, args ){ var name,ret, old={}; for(name in options){ old[name]=elem.style[name]; elem.style[name]=options[name]; } ret=callback.call(elem,args||[]); for(name in options){ elem.style[name]=old[name]; } return ret; }, }); //目前,js中typeof的返回值有六种:"number," "string," "boolean," "object," "function," 和 "undefined." //通过object.prototype.toString/或者{}.toString 返回值有九种:Boolean Number String Function Array Date RegExp Object Error,其中的Array,Date,RegExp,Object,Error都属于Object类型,在有些浏览器中typeof 正则会返回function jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(i,name){ class2type["[object "+name+"]"]=name.toLowerCase(); }); //console.log(class2type,class2type); rootjQuery=jQuery(document); window.jQuery=window.$=jQuery; })(window);