很早就聽一些大神說要讀源碼,直到前一段時(shí)間春招受挫才立下決心,先入手一個(gè)JQuery吧,以后有機(jī)會(huì)可以摸一摸Vue。
jQuery,說起都有一種張國榮、陳百強(qiáng)的感覺了,但是還是可以重溫的,面試官教育我,不能盲目跟風(fēng),人云亦云。
其實(shí)看了幾天了,一行一行看沒有重點(diǎn),直到看了幾個(gè)大佬的博文(比如下面這位),覺得可以嘗試了。

還有一位出了一個(gè)系列的,大家在中文社區(qū)應(yīng)該看得到(他也參加過螞蟻金服17春招,而且通過了,敬佩之心油然而生?。?。
從外層入手
( function( global, factory ) {
"use strict";
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}// 在node環(huán)境非全局執(zhí)行時(shí)不會(huì)暴露為接口
});
首先把映入眼簾的最外層拿出來,就是一個(gè)自執(zhí)行函數(shù),大佬也叫閉包??梢宰龅健接小捅┞兜綀?zhí)行環(huán)境指定的jQuery接口(noGlobal控制)。
自執(zhí)行函數(shù)里放的啥?
if ( typeof module === "object" && typeof module.exports === "object" ) {
// 自執(zhí)行函數(shù)(模塊化)commenJs?(存在document屬性?
// 執(zhí)行函數(shù):暴露包裝函數(shù)待傳入一個(gè)含document的對象):執(zhí)行函數(shù)
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 );
} // node環(huán)境檢查是否是全局執(zhí)行,非node環(huán)境直接執(zhí)行
判斷了是不是commonJS(node的模塊化標(biāo)準(zhǔn)),傳入的執(zhí)行環(huán)境作用域變量是不是有document。
進(jìn)入正題
現(xiàn)在可以正視那個(gè)被嚴(yán)格控制的神秘又長的函數(shù)了。JQuery是啥?我找一下,看起來一時(shí)半會(huì)沒有一個(gè)全面的答案。
var
version = "3.3.1", //版本號(hào)唄,忽視一下。
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );//jQuery ()實(shí)際實(shí)例化了init
};
jQuery.fn = jQuery.prototype = { };
jQuery就是一個(gè)起構(gòu)造作用的函數(shù),實(shí)例化了 jQuery.fn.init。fn是一個(gè)私有屬性,指向jQuery的原型。
init是啥
var rootjQuery,
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// 規(guī)避: $(""), $(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" ) {
// selector是string
} else if ( selector.nodeType ) {
// selector是DOM節(jié)點(diǎn)
} else if ( isFunction( selector ) ) {
// selector是函數(shù)
}
return jQuery.makeArray( selector, this );
};
開始有些東西試圖阻止我順藤摸瓜了,先記下來。
rootjQuery、jQuery.makeArray|init私有match,elem
注意到了return this;如果被作為一個(gè)構(gòu)造函數(shù)使用,這個(gè)return是不言自明的,但是他明說了。猜測,有非new的調(diào)用,或者定制無論何種調(diào)用的return(返回一個(gè)空對象可能想得到其原型?)。
先往下瞄一眼 眼熟吧,一句委托,return的this就是prototype指向jQuery原型的空對象。
init.prototype = jQuery.fn;
有三個(gè)流程判斷語句,我要撿軟的捏!
selector是DOM節(jié)點(diǎn)
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
this[ 0 ] = selector;
this.length = 1;
return this;
}
第一個(gè)參數(shù)如果傳入的DOM節(jié)點(diǎn),放入0屬性,length屬性置1,返回了一個(gè)像數(shù)組的對象。
selector是函數(shù)
// HANDLE: $(function)
// Shortcut for document ready
} else if ( isFunction( selector ) ) {
return root.ready !== undefined ?
root.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}
init參數(shù)root.ready
在前面jQuery實(shí)例化的時(shí)候并沒有傳入root參數(shù),在沒有root的情況下執(zhí)行selector( jQuery ),應(yīng)該可以在傳入的函數(shù)里擴(kuò)充jQuery函數(shù)的內(nèi)容吧。
selector非DOM、function、string
return jQuery.makeArray( selector, this );
那就return 讓這個(gè)未知的函數(shù)執(zhí)行唄!
selector是string
if ( typeof selector === "string" ) {
if ( selector[ 0 ] === "<" &&
selector[ selector.length - 1 ] === ">" &&
selector.length >= 3 ) { // /^<[\w\W]+>$/
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
if ( match && ( match[ 1 ] || !context ) ) {
} else if ( !context || context.jquery ) {
} else {
}
}
這段的結(jié)構(gòu)也被抽離出來了。有必要回看rquickExpr了/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,就是它,匹配到以空格或<[\w\W]+>開頭的字符串。
這樣match就有兩種樣子了,[ null, selector, null ]或正則匹配后的(數(shù)組第二項(xiàng)為去空格的標(biāo)簽字符串)。
if ( match && ( match[ 1 ] || !context ) ) {
// HANDLE: $(html) -> $(array)
if ( match[ 1 ] ) {
// 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;
}
}
如果match的第二項(xiàng)空,則按照第三項(xiàng)獲取全局的對應(yīng)id的節(jié)點(diǎn),存入類數(shù)組對象返回。
if ( match[ 1 ] ) {
context = context instanceof jQuery ? context[ 0 ] : context;
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 ( isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
}
要補(bǔ)充一句了
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
jQuery.merge jQuery.isPlainObject init的方法attr
其中context.ownerDocument得到頁面根節(jié)點(diǎn),而rsingleTag 可以匹配一個(gè)無文本的完整標(biāo)簽。
后面的for-in循環(huán)大概是可以判斷出操作的合法性了后,對初始化的對象拷貝屬性,在原基礎(chǔ)上重寫DOM節(jié)點(diǎn)的一些方法。
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 );
}
find()可能就是我們用到的find方法
其他情況則以第二參數(shù)構(gòu)造,再find第一參數(shù)。
另外,向下瞄一眼rootjQuery 找到了(init了document)。
// Initialize central reference
rootjQuery = jQuery( document );
整體架構(gòu)的入手和init函數(shù)就完成了,希望能堅(jiān)持下去吧。