jQuery的無new構建
jQuery框架的核心就是從HTML文檔中匹配元素並對其執行操作、
回想一下使用 jQuery 的時候,實例化一個 jQuery 對象的方法:
// 無 new 構造 $('#test').text('Test'); // 當然也可以使用 new var test = new $('#test'); test.text('Test');
大部分人使用 jQuery 的時候都是使用第一種無 new 的構造方式,直接 $('')
進行構造,這也是 jQuery 十分便捷的一個地方。
當我們使用第一種無 new 構造方式的時候,其本質就是相當於 new jQuery()
,那麼在 jQuery 內部是如何實現的呢?看看:
(function(window, undefined) { var // ... jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, jQuery.fn = jQuery.prototype = { init: function(selector, context, rootjQuery) { // ... } } jQuery.fn.init.prototype = jQuery.fn; })(window);
沒看懂?沒關系,我們一步一步分析。
函數表達式和函數聲明
在ECMAScript中,創建函數的最常用的兩個方法是函數表達式和函數聲明,兩者期間的區別是有點暈,因為ECMA規范只明確了一點:函數聲明必須帶有標示符(Identifier
)(就是大家常說的函數名稱),而函數表達式則可以省略這個標示符:
//函數聲明: function 函數名稱 (參數:可選){ 函數體 } //函數表達式: function 函數名稱(可選)(參數:可選){ 函數體 }
所以,可以看出,如果不聲明函數名稱,它肯定是表達式,可如果聲明了函數名稱的話,如何判斷是函數聲明還是函數表達式呢?
ECMAScript是通過上下文來區分的,如果function foo(){}
是作為賦值表達式的一部分的話,那它就是一個函數表達式,
如果function foo(){}
被包含在一個函數體內,或者位於程序的最頂部的話,那它就是一個函數聲明。
function foo(){} // 聲明,因為它是程序的一部分 var bar = function foo(){}; // 表達式,因為它是賦值表達式的一部分 new function bar(){}; // 表達式,因為它是new表達式 (function(){ function bar(){} // 聲明,因為它是函數體的一部分 })();
還有一種函數表達式不太常見,就是被括號括住的(function foo(){})
,他是表達式的原因是因為括號 ()是一個分組操作符,它的內部只能包含表達式
再來看jQuery源碼:
(function(window, undefined) { /... })(window)
可以將上面的代碼結構分成兩部分:(function(){window, undefined})
和 (window) ,
第1個()是一個表達式,而這個表達式本身是一個匿名函數,
所以在這個表達式後面加(window)就表示執行這個匿名函數並傳入參數window。
原型 prototype
認識一下什麼是原型?
在JavaScript中,原型也是一個對象,通過原型可以實現對象的屬性繼承,JavaScript的對象中都包含了一個" [[Prototype]]"
內部屬性,這個屬性所對應的就是該對象的原型。
對於"prototype"和"__proto__"這兩個屬性有的時候可能會弄混,"Person.prototype"和"Person.__proto__"是完全不同的。
在這裡對"prototype"和"__proto__"進行簡單的介紹:
1.對於所有的對象,都有__proto__屬性,這個屬性對應該對象的原型
2.對於函數對象,除了__proto__屬性之外,還有prototype屬性,當一個函數被用作構造函數來創建實例時,該函數的prototype屬性值將被作為原型賦值給所有對象實例(也就是設置實例的__proto__屬性)
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); }; //調用 var will = new Person("Will", 28); will.getInfo();//"Will is 28 years old"
閉包
閉包的定義:
當一個內部函數被其外部函數之外的變量引用時,就形成了一個閉包。
閉包的作用:
在了解閉包的作用之前,我們先了解一下 javascript中的GC機制:
在javascript中,如果一個對象不再被引用,那麼這個對象就會被GC回收,否則這個對象一直會保存在內存中。
在上述例子中,B定義在A中,因此B依賴於A,而外部變量 c 又引用了B, 所以A間接的被 c 引用,
也就是說,A不會被GC回收,會一直保存在內存中。為了證明我們的推理,看如下例子:
function A(){ var count = 0; function B(){ count ++; console.log(count); } return B; } var c = A(); c();// 1 c();// 2 c();// 3
count是A中的一個變量,它的值在B中被改變,函數B每執行一次,count的值就在原來的基礎上累加1。因此,A中的count一直保存在內存中。
這就是閉包的作用,有時候我們需要一個模塊中定義這樣一個變量:希望這個變量一直保存在內存中但又不會“污染”全局的變量,這個時候,我們就可以用閉包來定義這個模塊
在看jQuery源碼:
(function(window, undefined) { var // ... jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, jQuery.fn = jQuery.prototype = { init: function(selector, context, rootjQuery) { // ... } } jQuery.fn.init.prototype = jQuery.fn; })(window);
我們知道了 什麼是閉包:當一個內部函數被其外部函數之外的變量引用時,就形成了一個閉包。
jQuery.fn的init 函數被jQuery 的構造函數調用了,這裡形成了一個閉包。
構造函數及調用代碼:
// ... jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); },
問題關鍵來了。
如何實現無new構建
JavaScript是函數式語言,函數可以實現類,類就是面向對象編程中最基本的概念
var aQuery = function(selector, context) { //構造函數 } aQuery.prototype = { //原型 name:function(){}, age:function(){} } var a = new aQuery(); a.name();
這是常規的使用方法,顯而易見jQuery不是這樣玩的
要實現這樣,那麼jQuery就要看成一個類,那麼$()應該是返回類的實例才對
按照jQuery的抒寫方式
$().ready() $().noConflict()
要實現這樣,那麼jQuery就要看成一個類,那麼$()應該是返回類的實例才對
所以把代碼改一下:
var aQuery = function(selector, context) { return new aQuery(); } aQuery.prototype = { name:function(){}, age:function(){} }
通過new aQuery(),
雖然返回的是一個實例,但是也能看出很明顯的問題,死循環了!
那麼如何返回一個正確的實例?
在javascript中實例this只跟原型有關系
那麼可以把jQuery類當作一個工廠方法來創建實例,把這個方法放到aQuery.prototye原型中
var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init:function(selector){ return this; } name:function(){}, age:function(){} }
當執行aQuery()
返回的實例:
很明顯aQuery()
返回的是aQuery
類的實例,那麼在init中的this其實也是指向的aQuery
類的實例
問題來了init的this指向的是aQuery
類,如果把init函數也當作一個構造器,那麼內部的this要如何處理?
var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { this.age = 18 return this; }, name: function() {}, age: 20 } aQuery().age //18
因為this只是指向aQuery
類的,所以aQuery
的age
屬性是可以被修改的。
這樣看似沒有問題,其實問題很大的
為什麼是new jQuery.fn.init?
看如下代碼:
var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { if(selector=="a") this.age = 18 return this; }, name: function() {}, age: 20 } aQuery("a").age //18 aQuery("b").age //18
當我調用 傳入"a"的時候,修改age=18,及aQuery("a").age 的值為18
但是當我 傳入"b"的時候 並沒又修改 age的值,我也希望得到默認age的值20,但是aQuery("b").age 的值為18.
因為在 調用aQuery("a").age 的時候age被修改了。
這樣的情況下就出錯了,所以需要設計出獨立的作用域才行。
jQuery框架分隔作用域的處理
jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); },
很明顯通過實例init
函數,每次都構建新的init
實例對象,來分隔this
,避免交互混淆
我們修改一下代碼:
var aQuery = function(selector, context) { return new aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { if(selector=="a") this.age = 18 return this; }, name: function() {}, age: 20 } aQuery("a").age //18 aQuery("b").age //undefined aQuery("a").name() //Uncaught TypeError: Object [object Object] has no method 'name'
又出現一個新的問題,
age :undefined,
name() :
拋出錯誤,無法找到這個方法,所以很明顯new的init
跟jquery類的this
分離了
怎麼訪問jQuery類原型上的屬性與方法?
做到既能隔離作用域還能使用jQuery原型對象的作用域呢,還能在返回實例中訪問jQuery的原型對象?
實現的關鍵點
// Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn;
我們再改一下:
var aQuery = function(selector, context) { return new aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { if(selector=="a") this.age = 18 return this; }, name: function() { return age; }, age: 20 } aQuery.prototype.init.prototype = aQuery.prototype; aQuery("a").age //18 aQuery("b").age //20 aQuery("a").name() //20
最後在看一下jQuery源碼:
(function(window, undefined) { var // ... jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, jQuery.fn = jQuery.prototype = { init: function(selector, context, rootjQuery) { // ... } } jQuery.fn.init.prototype = jQuery.fn; })(window);
是不是明白了?
哈哈哈~~~
在簡單說兩句:
大部分人初看 jQuery.fn.init.prototype = jQuery.fn
這一句都會被卡主,很是不解。但是這句真的算是 jQuery 的絕妙之處。理解這幾句很重要,分點解析一下:
1)首先要明確,使用 $('xxx')
這種實例化方式,其內部調用的是 return new jQuery.fn.init(selector, context, rootjQuery)
這一句話,也就是構造實例是交給了 jQuery.fn.init()
方法取完成。
2)將 jQuery.fn.init
的 prototype 屬性設置為 jQuery.fn
,那麼使用 new jQuery.fn.init()
生成的對象的原型對象就是 jQuery.fn
,所以掛載到 jQuery.fn
上面的函數就相當於掛載到 jQuery.fn.init()
生成的 jQuery 對象上,所有使用 new jQuery.fn.init()
生成的對象也能夠訪問到 jQuery.fn
上的所有原型方法。
3)也就是實例化方法存在這麼一個關系鏈
1.jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;
2.new jQuery.fn.init() 相當於 new jQuery() ;
3.jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),所以這 2 者是相當的,所以我們可以無 new 實例化 jQuery 對象。
總結
以上就是jQuery的無new構建的全部內容,希望本文對大家學習jQuery有所幫助。也請大家繼續支持百度。