jquery是一个强大的js类库,提供了不少便利的操做方法并兼容不一样的浏览器,一旦使用便欲罢不能,根本停不下来,今天咱们就来解读一下这个神秘的jquery源代码。html
前几天思考再三,本身尝试着封装了一下jquery的$选择器,然而并不完善,我只对id,class,和标签选择器进行了封装,发现其实若是实现浅层的封装那么咱们很容易就可以实现,可是一旦咱们尝试着选择器的层次嵌套就会出来不少大大小小的坑!node
下面咱们先来看一下我我的封装的jquery的选择器部分。jquery
window.$ = function (selector) { if (typeof selector === 'object') { console.log(selector,123) } else if (typeof selector === 'string') { var arr = selector.split(' ') for(var i=0;i<arr.length;i++){ oArr = [] if(arr[i].charAt(0)=='#'){ var a = arr[i].substr(1) // oEle = oEle.getElementById(a) if(!oEle.length){ oEle = oArr= document.getElementById(a) }else{ for(var j=0;j<oEle.length;j++){ // console.log(oEle[j]) for(var s = 0;s<oEle[j].childNodes.length;s++){ // console.log(oEle[j].childNodes[s]) if(oEle[j].childNodes[s].nodeType==1){ // console.log(oEle[j].childNodes[s].nodeType) if(oEle[j].childNodes[s].getAttribute('id')==a){ oArr[j] = document.getElementById(a) // console.log('yes',s) } // console.log(oEle[j].childNodes[s].getAttribute('id')) } } } oEle = oArr } }else if(arr[i].charAt(0)=='.'){ // console.log(oEle.length) // console.log(oEle) var a = arr[i].substr(1) if(!oEle.length){ oEle = oArr= oEle.getElementsByClassName(a) // console.log(oEle,'class') }else{ for(var j=0;j<oEle.length;j++){ // console.log(oEle) if(oEle[j].getElementsByClassName(a).length){ // console.log(oEle) // console.log(1,oEle.length) oArr[j] = oEle[j].getElementsByClassName(a) } // console.log(oEle[j].getElementsByClassName(a)) } oEle = oArr } // console.log(oEle) }else{ // console.log(oEle) // console.log(arr[i]) if(!oEle.length){ oEle = oArr = oEle.getElementsByTagName(arr[i]) // console.log(oEle,'tag') }else{ for(var j=0;j<oEle.length;j++){ // console.log(oEle[j].getElementsByTagName(arr[i]),oEle.length) if(oEle[j].getElementsByTagName(arr[i]).length){ oArr[j] = oEle[j].getElementsByTagName(arr[i]) // console.log(oEle[j].getElementsByTagName(arr[i])) // console.log(oEle[j].getElementsByTagName(arr[i]),j) } } oEle = oArr } // var oEle = oEle.getElementsByTagName(arr[i]) // console.log(oEle) } } // console.log(oEle) } // console.log(oEle,12) console.log(oArr) // console.log(oEle.length) // console.log(document.getElementById('c')) // console.log(oEle.getElementById('c')) } window.$('.content #c')
jquery的原理大概就是咱们把一个$暴漏给window,而后咱们在$下面能够写不少方法,而后咱们在$以后返回了一个jquery的dom对象在调用本身封装的jquery库中的方法来实现各类浏览器的兼容。数组
下面咱们来讲一下我封装这个类库的时候碰到的一些坑吧:promise
首先咱们封装类库无非用几种js原生的选择器,好比document.getElementById或者document.getElementsByTagName。浏览器
这两个是最经常使用的方法可是这个也是问题的所在,打个比方,咱们id选择器选到的是一个dom对象,可是咱们getElementsByTagName取到的倒是一个数组。dom
这个问题怎么解决,由于咱们选择器支持选择多个选择器,好比$('#div div'),咱们选择id=div的dom元素以后咱们在选择dom下面的全部的div,才能实现dom节点循环嵌套,可是换一个角度$('div #div'),咱们选择div标签下面的id=div的元素,那么咱们如何进行数组和dom元素的转化?函数
个人决解的方法是本身定义了一个数组,而后每次dom元素取出来都放在数组里面,数组的元素也放在这个数组里面从而实现真正的循环遍历。fetch
下面咱们来观看一个jquery是怎么实现$选择器的优化
( function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}
} )
咱们能够看到,jquery用了一个函数当即调用,而后把window传入函数中,这样作的意义是防治咱们的类库污染全局变量,可能有的同窗感受这个不是很重要是由于咱们写的代码变量比较少,还不设计代码优化,若是相似jquery的类库所有写进window里面,那你的代码也就不用运行了!
以后jquery进行了一些是否window异常,是否有commonJS标准等逻辑判断来加强类库的健壮性,这里咱们就不作过多的解释了。
以后咱们能够在源代码中看到这样一段代码
jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); },
这里的jquery其实就是$,jquery类库最后运行了这样一句话
window.jQuery = window.$ = jQuery;
因此咱们的jquery其实就是$ ,咱们在$方法传入两个参数,第一个参数为你输入的选择器,第二个参数为上下文,以后咱们return了一个新的对象jquery中的fn,这样的实现是为了获得咱们传入的那个dom节点,由于咱们全部的方法都写在了window中的$里面,咱们经过return的形式来获取dom以后在调用自身的方法,这个形式相似fetch中的promise对象同样,以后咱们能够.then执行同样,二者都有着殊途同归之妙。
好了咱们接着往下看:
init = jQuery.fn.init = function( selector, context, root ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Method init() accepts an alternate rootjQuery // so migrate can support jQuery.sub (gh-2101) root = root || rootjQuery; // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } // Match html or make sure no context is specified for #id if ( match && ( match[ 1 ] || !context ) ) { // HANDLE: $(html) -> $(array) if ( match[ 1 ] ) { context = context instanceof jQuery ? context[ 0 ] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( jQuery.isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { elem = document.getElementById( match[ 2 ] ); if ( elem ) { // Inject the element directly into the jQuery object this[ 0 ] = elem; this.length = 1; } return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || root ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { this[ 0 ] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : // Execute immediately if ready is not present selector( jQuery ); } return jQuery.makeArray( selector, this ); };
这里面咱们传入了选择器,咱们先判断了是否是存在这个selecter,若是不存在的话咱们就return this;
以后咱们判断传入的这个字符串的状况的和不是字符串的逻辑,若是传入的不是字符串,那咱们就看这个元素的nodeType值是否是不为0,若是不为0就说明元素存在,多是text,多是dom
对象等等,以后咱们就作一些逻辑return this,若是selecter为方法的话咱们就判断跟节点的ready是否是undefined,若是不是的话咱们执行root.ready(),不然就执行selecter方法。
注:这里的全部this都是jquery的对象,咱们是判断了对象是否是包的数组,若是是咱们作这样的操做:
this[ 0 ] = selector; this.length = 1; return this;
咱们取出第一项,以后把它变成jquery对象咱们就能够继续调用jquery的方法了,否则咱们获得了一个数组咱们是不能继续调用方法的,这里将会报错!
以后若是咱们传入的selecter为字符串,那么咱们就进行逻辑判断
if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); }
这里面是判断选择器是不是一个标签,若是不是标签的话咱们就执行
match = rquickExpr.exec( selector );
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
咱们匹配出的match若是是标签在前id在后咱们的match不为空,这样咱们就继续运行下面的代码,这里也是jquery健壮性的体现!
if ( match && ( match[ 1 ] || !context ) )
以后若是成功说明上面代码都成立,我门就继续往下执行
if ( match[ 1 ] ) { context = context instanceof jQuery ? context[ 0 ] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( jQuery.isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) }
以后咱们作了去标签以后id的逻辑,这里面的merge是合并的方法,parseHTML是判断是否有上下文的逻辑(这个上下文基本上我用jquery历来没用过- -)。以后咱们同样都把这个jquery对象return。
基本上的逻辑就是这样,咱们定义jquery的时候声明了许多变量许多方法,而后咱们吧一个$暴漏在外部,咱们的$内部new了一个jquery对象,而后调用的方法return this来实现jquery的链式操做,以后咱们对传入进来的选择器作了各类逻辑的判断增长了健壮性。
不知道你们有没有注意到,咱们的jquery对象想要变成dom对象咱们须要get(0) 或者[0]。其实咱们的jquery对象就是一个包着dom对象的一个数组对象,因此咱们才能这样子来取出来咱们我原生dom对象!其实jquery实现的思想很简单,复杂的是咱们对各个浏览器兼容作的逻辑,咱们对各个函数传入值状况做了逻辑,其实想实现jquery很简单,可是想实现真正的健壮性很好的jquery很是费劲了,这些都须要咱们去经验积累罢了!