一、jQuery($)命名空間 為了避免聲明瞭一些全局變數造成變數污染,使用立即執行函數形成jQuery($)獨立的命名空間; 二、jQuery的本質是什麼? 由jQuery的源碼可知,jQuery的本質是一個函數(對象),是函數就應該有原型(prototype對象),但是,jQuery重置了該原型 ...
一、jQuery($)命名空間
為了避免聲明瞭一些全局變數造成變數污染,使用立即執行函數形成jQuery($)獨立的命名空間;
(function(window, undefined){ })(window)
二、jQuery的本質是什麼?
(function(window, undefined){ var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); }; })(window)
由jQuery的源碼可知,jQuery的本質是一個函數(對象),是函數就應該有原型(prototype對象),但是,jQuery重置了該原型(prototype對象),也就是重新賦值,同時也將原型(prototype對象)賦值到jQuery的fn屬性上,也添加了很多屬性和方法,看源碼:
(function(window, undefined){ var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); };
var core_version = "2.0.3"
jQuery.fn = jQuery.prototype = { jquery: core_version, // 存放jQuewry版本 constructor: jQuery, // 構造函數
init: function( selector, context, rootjQuery ) { } } })(window)
三、jQuery對象是什麼,也就是$()返回了什麼?
我們都知道jQuery可以通過選擇器獲取DOM對象,但是jQuery返回的DOM對象和原生DOM對象(如通過document.getElementById獲取的DOM對象)是不一樣的,但是可以相互轉換,如可以通過$().get()方法或者$()[]將jQuery DOM對象轉換成原生DOM對象,也可以通過$(原生DOM對象)將原生DOM對象轉換成jQuery DOM對象。
但是jQuery對象是什麼($()返回了什麼),如圖用原生方法和jQuery方法獲取一個id為div1的元素:
通過document.getElementsByClassName和通過$方法獲取class為div的元素:
$()返回的值為什麼是這樣的呢?其實我們可以看源碼$()的返回值也就是構造函數init的返回值:
(function(window, undefined){ var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); };
var core_version = "2.0.3"
jQuery.fn = jQuery.prototype = { jquery: core_version, // 存放jQuewry版本 constructor: jQuery, // 構造函數 init: function( selector, context, rootjQuery ) { } } })(window)
返回值是執行一個函數,
return new jQuery.fn.init( selector, context, rootjQuery );
,是jQuery原型重置後原型上的一個函數,而且通過new創建的,那麼其實$()的返回值也就是init這個函數的返回值。通過new執行的函數也就是構造函數的執行,init是一個構造函數(雖然首字母沒有大寫),也就是說jQuery DOM對象($()的返回值)是init構造函數的一個實例,該實例繼承了構造函數init原型(prototype對象)上的所有屬性和方法(構造函數init原型上有哪些屬性和方法呢?,這一點很重要,先賣個關子)。我們先來看一下jQuery源碼上構造函數init返回了什麼:
1 (function(window, undefined){ 2 3 var jQuery = function( selector, context ) { 4 return new jQuery.fn.init( selector, context, rootjQuery ); 5 }; 6 7 jQuery.fn = jQuery.prototype = { 8 9 jquery: core_version, // 存放jQuewry版本 10 constructor: jQuery, // 構造函數 11 init: function( selector, context, rootjQuery ) { 12 var match, elem; 13 14 // HANDLE: $(""), $(null), $(undefined), $(false) 15 if ( !selector ) { 16 return this; 17 } 18 19 // Handle HTML strings 20 if ( typeof selector === "string" ) { 21 if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { 22 // Assume that strings that start and end with <> are HTML and skip the regex check 23 match = [ null, selector, null ]; 24 25 } else { 26 match = rquickExpr.exec( selector ); 27 } 28 29 // Match html or make sure no context is specified for #id 30 if ( match && (match[1] || !context) ) { 31 32 // HANDLE: $(html) -> $(array) 33 if ( match[1] ) { 34 context = context instanceof jQuery ? context[0] : context; 35 36 // scripts is true for back-compat 37 jQuery.merge( this, jQuery.parseHTML( 38 match[1], 39 context && context.nodeType ? context.ownerDocument || context : document, 40 true 41 ) ); 42 43 // HANDLE: $(html, props) 44 if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { 45 for ( match in context ) { 46 // Properties of context are called as methods if possible 47 if ( jQuery.isFunction( this[ match ] ) ) { 48 this[ match ]( context[ match ] ); 49 50 // ...and otherwise set as attributes 51 } else { 52 this.attr( match, context[ match ] ); 53 } 54 } 55 } 56 57 return this; 58 59 // HANDLE: $(#id) 60 } else { 61 elem = document.getElementById( match[2] ); 62 63 // Check parentNode to catch when Blackberry 4.6 returns 64 // nodes that are no longer in the document #6963 65 if ( elem && elem.parentNode ) { 66 // Inject the element directly into the jQuery object 67 this.length = 1; 68 this[0] = elem; 69 } 70 71 this.context = document; 72 this.selector = selector; 73 return this; 74 } 75 76 // HANDLE: $(expr, $(...)) 77 } else if ( !context || context.jquery ) { 78 return ( context || rootjQuery ).find( selector ); 79 80 // HANDLE: $(expr, context) 81 // (which is just equivalent to: $(context).find(expr) 82 } else { 83 return this.constructor( context ).find( selector ); 84 } 85 86 // HANDLE: $(DOMElement) 87 } else if ( selector.nodeType ) { 88 this.context = this[0] = selector; 89 this.length = 1; 90 return this; 91 92 // HANDLE: $(function) 93 // Shortcut for document ready 94 } else if ( jQuery.isFunction( selector ) ) { 95 return rootjQuery.ready( selector ); 96 } 97 98 if ( selector.selector !== undefined ) { 99 this.selector = selector.selector; 100 this.context = selector.context; 101 } 102 103 return jQuery.makeArray( selector, this ); 104 } 105 106 } 107 108 109 })(window)View Code
源碼有點複雜,因為jQuery的選擇器功能也就是對DOM操作得功能非常的強大,我們先弱化jQuery的選擇器功能,讓jQuery只能通過id獲取DOM元素,如,改寫jQuery的源碼如下:
1 (function(window, undefined){ 2 3 var jQuery = function( selector, context ) { 4 return new jQuery.fn.init( selector, context, rootjQuery ); 5 }; 6 7 jQuery.fn = jQuery.prototype = { 8 9 jquery: core_version, // 存放jQuewry版本 10 constructor: jQuery, // 構造函數 11 init: function( selector, context, rootjQuery ) { 12 13 var match, elem; 14 15 // HANDLE: $(""), $(null), $(undefined), $(false) 16 if ( !selector ) { 17 return this; 18 }else { 19 elem = document.getElementById( selector.replace('#','') ); 20 21 // Check parentNode to catch when Blackberry 4.6 returns 22 // nodes that are no longer in the document #6963 23 if ( elem && elem.parentNode ) { 24 // Inject the element directly into the jQuery object 25 this.length = 1; 26 this[0] = elem; 27 } 28 29 this.context = document; 30 this.selector = selector; 31 return this; 32 } 33 34 } 35 36 } 37 38 39 })(window)View Code
由源碼可以知道,返回值是 this 。這個this指的是什麼?如果不知道可以去瞭解一下 new + 構造函數 的知識,其實通過new調用構造函數,構造函數中的this就是指向該構造函數的實例,在這裡也就是指向init構造函數的實例。看一下具體是什麼:
init其實也就是一個json對象,key值為0的value值是原生DOM對象,也就能解析為什麼可以通過$()[0]就能將JQuery對象轉換成原生DOM對象了。 init實例對象還存放了很多信息,如length、選擇器、上下文document,其實在實際的jQuery中,$()傳入的參數不同,init存放的信息也各不相同,自己可以去瞭解一下,比如上圖,$的參數為空和傳入id,init實例的數據就不同,又比如傳入class和id又有所不同。
儘管$方法傳入的參數不同,init實例也有所不同,但是有一點是相同的,也就是都繼承構造函數init的原型對象,如:
我們都知道,jQuery對象有很多方法,如$().attr、$().addClass、$().removeClass等等.......,這些方法哪裡來的呢,很明顯是通過繼承構造函數init原型上的,那麼構造函數init原型上為什麼有這些方法,按理來說構造函數是既然是一個函數,那麼它肯定是繼承了Object原型上的方法,但是Object原型上又沒有這些方法,哪裡來的呢?請往下看。
四、重置構造函數init的原型對象
上jQuery源碼,看構造函數init的原型對象:
還有上面提過的:
也就是這樣:
也就是說構造函數init的實例繼承了jQuery原型上的所有屬性和方法,同時將init實例的構造函數指向jQuery(造假技術一流),如:
在所有的jQuery對象中都可以找到jQuery的版本號和構造函數,同時也能解釋為什麼jQuery對象上面會有這麼多的方法了。
五、jQuery插件機制
jQuery為開發插件提拱了兩個方法,分別是:
jQuery.fn.extend(object); 給jQuery對象添加方法。
jQuery.extend(object); 為擴展jQuery類本身.為類添加新的方法,可以理解為添加靜態方法(工具方法)。
這兩個方法都接受一個參數,類型為Object,Object對應的"名/值對"分別代表"函數或方法體/函數主體"。看源碼:
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays 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 : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; };
可以看到這麼一句:
jQuery.extend = jQuery.fn.extend
同時指向一個函數卻實現不同的功能,可以思考一下為什麼?其實很簡單,加上函數裡面this的指向問題,如果是jQuery.extend({}),函數裡面的this指向的是jQuery,如果是
jQuery.fn.extend({}),函數裡面的this指向jQuery.fn,這樣就能知道到底是往jQuery拓展類本身添加方法還是往jQuery的原型上添加方法同時繼承個jQuery對象了。
方法就是往jQuery.prototype上添加方法,如:
向jquery拓展類本身添加工具方法,如:
六、$.noConflict()
看源碼,這裡jQuery命名空間最底部的一段:
個人覺得這是一種不使用return方式形成的閉包方式,return方式是函數執行將返回值賦值到一個全局變數上,而jQuery的做法是直接在裡面賦值都window上,其作用是一樣的,如果使用return方式,return jQuery到外層全局變數也是可以的,但是,jQuery作者為什麼沒有這麼做呢?其實有很深沉的含義在裡面,window.jQuery和window.$也可能造成變數污染,比如和prototype庫中的window.$形成污染(或者同時引入jQuery的不同版本),那該如何解決呢?那就的靠jQuery.noConflict()來解決了。解決衝突是有前提的:
1、其他使用jQuery、$變數的庫必須在引入jQuery之前引入。(因為如果在jQuery之後引入,就直接覆蓋了jQuery了)
那麼jQuery是如何解決和其他庫的衝突的呢?看源碼:
在jQuery命名空間的頂部先把全局作用域window中的jQuery和$先賦值給jQuery命名空間中的局部變數存放起來(如果不存在,_jQuery和$的值就是undefined),如果存在衝突,那麼再使用jQuery或者$之前,先執行$.noConflict(),可以看源碼這個函數做什麼了:
如果全局作用域中(window上)的$和jQuery命名空間上的jQuery是全等的,那麼將全局作用域中(window上)的$賦值給命名衝突庫中的$(已存放到_$中),如果deep為true,說明jQuery也有衝突,也將jQuery從新賦值給命名衝突庫中的jQuery(已存放到_jQuery中),最後return jQuery,我們可以對jQuery中的$重新賦值,如:
在不存在衝突的情況下執行了$.noConflict()後,$不能再使用,它的值是undefined。至此,可以說jQuery實現了對全局作用域的零變數污染。
七、總結
1、jQuery的本質是一個函數,但是不作為構造函數使用,而是返回 一個構造函數init的實例,也就是jQuery對象。
2、jQuery函數重置了它的原型(prototype對象),重新定義,同時作為返回值的構造函數init的實例的原型也被重置,指向jQuery的原型,所有構造函數init的實例繼承了jQuery原型上的所有屬性和方法。
以上是本人的一下理解,有錯歡迎指出,網上有很多分析jQuery源碼的,更多請參考:
1、https://www.zhihu.com/question/20521802
2、by Aaron:http://www.cnblogs.com/aaronjs/p/3279314.html