第二章 jQuery技術解密(一)
2.2 jQuery 原型技術分解
任何複雜的技術都是從最簡單的問題開始的,如果你被 jQuery 幾千行龐雜結構的原始碼所困惑,那麼建議你閱讀本節內容,我們將探索 jQuery 是如何從最簡單的問題開始,並逐步實現羽翼漸豐的演變過程,從 jQuery 核心技術的還原過程來理解 jQuery 框架的搭建原理。
2.2.1 起源 -- 原型繼承
用過 JavaScript 的讀者都會明白,在 JavaScript 指令碼中到處都是函式,函式可以歸置程式碼段,把相對獨立的功能封裝在一個函式包中。函式也可以實現類,這個類是物件導向程式設計中最基本的概念,也是最高抽象,定義一個類就相當於製作了一個模型,然後藉助這個模型複製無數的例項。
例如,下面的程式碼就可以定義最初的 jQuery 類,類名就是 jQuery ,你可以把它視為一個函式,函式名是 jQuery 。當然,你也可以把它視為一個物件,物件名就是 jQuery 。與其他物件導向的程式語言相比,JavaScript 對於這個概念的界定好像很隨意,這降低了程式設計的門檻,反之也降低了 JavaScript 作為程式語言的層次。
<script language="javascript" type="text/javascript">
var jQuery = function(){
// 函式體
};
</script>
上面建立了一個空的函式,好像什麼都不能夠做,這個函式實際上就是所謂的建構函式。建構函式在面嚮物件語言中是類的一個特殊方法,用來建立類。在 JavaScript 中,你可以把任何函式都視為建構函式,這沒有什麼不可以的,這樣不會傷害程式碼本身。
所有類都有最基本的功能,如繼承、派生和重寫等。JavaScript 很奇特,它通過為所有函式繫結一個 prototype 屬性,由這個屬性指向一個原型物件,原型物件中可以定義類的繼承屬性和方法等。所以,對於上面的空類,可以繼續擴充套件原型,其程式碼如下。
<script language="javascript" type="text/javascript">
var jQuery = function(){};
jQuery.prototype = {
// 擴充套件的原型物件
};
</script>
原型物件是 JavaScript 實現繼承的基本機制。如果你覺得 jQuery.prototype 名稱太長,沒有關係,我們可以為其重新命名,如 fn ,當然你可以隨便命名。如果直接命名 fn ,則表示該名稱屬性 Window 物件,即全域性變數名。更安全的方法是為 jQuery 類定義一個公共屬性, jQuery.fn ,然後把 jQuery 的原型物件傳遞給這個公共屬性,實現程式碼如下。
<script language="javascript" type="text/javascript">
jQuery.fn = jQuery.prototype = {
// 擴充套件的原型物件
};
</script>
這裡的 jQuery.fn 相當於 jQuery.prototype 的別名,方便以後使用,它們指向同一個引用。因此若要呼叫 jQuery 的原型方法,直接使用 jQuery.fn 公共屬性即可,不需要直接引用 jQuery.prototype ,當然直接使用 jQuery.prototype 也是可以的。既然原型物件可以使用別名,jQuery 類也可以起個別名,我們可以使用 $ 符號來引用它,程式碼如下。
var $ = jQuery = function(){};
現在模仿 jQuery 框架原始碼,給它新增兩個成員,一個是原型屬性 jquery ,一個是原型方法 size(),其程式碼如下。
<script language="javascript" type="text/javascript">
var $ = jQuery = function(){};
jQuery.fn = jQuery.prototype = {
jquery: "1.3.2", // 原型屬性
size: function(){// 原型方法
return this.length;
}
};
</script>
2.2.2 生命 -- 返回例項
當我們為 jQuery 新增了兩個原型成員:jquery 屬性和 size() 方法之後,這個框架最基本的樣子就孕育出來了。但是該如何呼叫 jquery 屬性和 size() 方法呢?
也許,你可以採用如下方法呼叫:
<script language="javascript" type="text/javascript">
var my$ = new $(); // 例項化
alert(my$.jquery);// 呼叫屬性,返回 "1.3.2"
alert(my$.size());// 呼叫方法,返回 undefined
</script>
但是,jQuery 不是這樣呼叫的。它模仿類似下面的方法進行呼叫。$().jquery;
$().size();
也就是說,jQuery 沒有使用 new 運算子將 jQuery 類例項化,而是直接呼叫 jQuery() 函式,然後在這個函式後面直接呼叫 jQuery 的原型方法。這是怎麼實現的呢?
如果你模仿 jQuery 框架的用法執行下面的程式碼,瀏覽器會顯示編譯錯誤。這說明上面這個案例程式碼還不是真正的 jQuery 技術原型。
alert($().jquery);
alert($().size());
也就是說,我們應該把 jQuery 看做一個類,同時也應該把它視為一個普通函式,並讓這個函式的返回值為 jQuery 類的例項。因此,下面這種結構模型才是正確的。
- <scripttype="text/javascript">
- var$=jQuery=function(){
- returnnewjQuery();//返回類的例項
- };
- jQuery.fn=jQuery.prototype={
- jquery:"1.3.2",//原型屬性
- size:function(){//原型方法
- returnthis.length;
- }
- };
- alert($().jquery);
- alert($().size());
- </script>
那麼如何返回一個 jQuery 例項呢?
回憶一下,當使用 var my$ = new $(); 建立 jQuery 類的例項時,this 關鍵字就指向物件 my$ ,因此 my$ 例項物件就獲得了 jQuery.prototype 包含的原型屬性或方法,這些方法內的 this 關鍵字就會自動指向 my$ 例項物件。換句話說,this 關鍵字總是指向類的例項。
因此,我們可以這樣嘗試:在 jQuery 中使用一個工廠方法來建立一個例項 (就是 jQuery.fn),把這個方法放在 jQuery.prototype 原型物件中,然後在 jQuery() 函式中返回這個原型方法的呼叫。程式碼如下所示。
- <scripttype="text/javascript">
- var$=jQuery=function(){
- returnjQuery.fn.init();//呼叫原型init()
- };
- jQuery.fn=jQuery.prototype={
- init:function(){//在初始化原型方法中返回例項的引用
- returnthis;
- },
- jquery:"1.3.2",//原型屬性
- size:function(){//原型方法
- returnthis.length;
- }
- };
- alert($().jquery);//呼叫屬性,返回"1.3.2"
- alert($().size());//呼叫方法,返回undefined
- </script>
2.2.3 學步 -- 分隔作用域
我們已經初步實現了讓 jQuery() 函式能夠返回 jQuery 類的例項,下面繼續思考:init() 方法返回的是 this 關鍵字,該關鍵字引用的是 jQuery 類的例項,如果在 init() 函式中繼續使用 this 關鍵字,也就是說,假設我們把 init() 函式也視為一個構造器,則其中的 this 該如何理解和處理?
例如,在下面示例中,jQuery 原型物件中包含一個 length 屬性,同時 init() 從一個普通的函式轉身變成了構造器,它也包含一個 length 屬性和一個 test() 方法。執行該示例,我們可以看到,this 關鍵字引用了 init() 函式作用域所在的物件, 此時它訪問 length 屬性時,返回0. 而 this 關鍵字也能夠訪問上一級物件 jQuery.fn 物件的作用域,所以 $().jquery 返回 "1.3.2" 。但是呼叫 $().size() 方法時,返回的是 0, 而不是 1 。
- <scripttype="text/javascript">
- var$=jQuery=function(){
- returnjQuery.fn.init();//呼叫原型init()
- };
- jQuery.fn=jQuery.prototype={
- init:function(){//在初始化原型方法中返回例項的引用
- this.length=0;
- this.test=function(){
- returnthis.length;
- }
- returnthis;
- },
- jquery:"1.3.2",//原型屬性
- length:1,
- size:function(){//原型方法
- returnthis.length;
- }
- };
- alert($().jquery);//返回"1.3.2"
- alert($().test());//返回0
- alert($().size());//返回0
- </script>
<script type="text/javascript">
var $ = jQuery = function(){
return new jQuery.fn.init(); // 例項化 init 初始化型別,分隔作用域
};
</script>
這樣就可以把 init() 構造器中的 this 和 jQuery.fn 物件中的 this 關鍵字隔離開來,避免相互混淆。但是,這種方式也會帶來另一個問題:無法訪問 jQuery.fn 物件的屬性或方法。例如,在下面的示例中,訪問 jQuery.fn 原型物件的 jquery 屬性和 size() 方法時就會出現這個問題。
- <scripttype="text/javascript">
- var$=jQuery=function(){
- returnnewjQuery.fn.init();//例項化init初始化型別,分隔作用域
- };
- jQuery.fn=jQuery.prototype={
- init:function(){//在初始化原型方法中返回例項的引用
- this.length=0;
- this.test=function(){
- returnthis.length;
- }
- returnthis;
- },
- jquery:"1.3.2",//原型屬性
- length:1,
- size:function(){//原型方法
- returnthis.length;
- }
- };
- alert($().jquery);//返回undefined
- alert($().test());//返回0
- alert($().size());//丟擲異常
- </script>
2.2.4 生長 -- 跨域訪問
如何做到既能夠分隔初始化構造器函式與 jQuery 原型物件的作用域,又能夠在返回例項中訪問 jQuery 原型物件呢?
jQuery 框架巧妙地通過原型傳遞解決了這個問題,它把 jQuery.fn 傳遞給 jQuery.fn.init.prototype ,也就是說用 jQuery 的原型物件覆蓋 init 構造器的原型物件,從而實現跨域訪問,其程式碼如下所示。
- <scripttype="text/javascript">
- var$=jQuery=function(){
- returnnewjQuery.fn.init();//例項化init初始化型別,分隔作用域
- };
- jQuery.fn=jQuery.prototype={
- init:function(){//在初始化原型方法中返回例項的引用
- this.length=0;
- this.test=function(){
- returnthis.length;
- }
- returnthis;
- },
- jquery:"1.3.2",//原型屬性
- length:1,
- size:function(){//原型方法
- returnthis.length;
- }
- };
- jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型物件覆蓋init的原型物件
- alert($().jquery);//返回"1.3.2"
- alert($().test());//返回0
- alert($().size());//返回0
- </script>
這是一招妙棋,new jQuery.fn.init() 建立的新物件擁有 init 構造器的 prototype 原型物件的方法,通過改變 prototype 指標的指向,使其指向 jQuery 類的 prototype ,這樣建立出來的物件就繼承了 jQuery.fn 原型物件定義的方法。
2.2.5 成熟 -- 選擇器
jQuery 返回的是 jQuery 物件,jQuery 物件是一個類陣列的物件,本質上它就是一個物件,但是它擁有陣列的長度和下標,卻沒有繼承陣列的方法。
很顯然,上面幾節的講解都是建立在一種空理論基礎上的,目的是希望讀者能夠理解 jQuery 框架的核心構建過程。下面,我們就嘗試為 jQuery() 函式傳遞一個引數,並讓它返回一個 jQuery 物件。
jQuery() 函式包含兩個引數 selector 和 context ,其中 selector 表示選擇器,而 context 表示選擇的內容範圍,它表示一個 DOM 元素。為了簡化操作,我們假設選擇器的型別僅限定為標籤選擇器。實現的程式碼如下所示。
- <div></div>
- <div></div>
- <div></div>
- <scripttype="text/javascript">
- var$=jQuery=function(selector,context){//定義類
- returnnewjQuery.fn.init(selector,context);//返回選擇器的例項
- };
- jQuery.fn=jQuery.prototype={//jQuery類的原型物件
- init:function(selector,context){//定義選擇器構造器
- selector=selector||document;//設定預設值為document
- context=context||document;//設定預設值為document
- if(selector.nodeType){//如果選擇符為節點物件
- this[0]=selector;//把引數節點傳遞給例項物件的陣列
- this.length=1;//並設定例項物件的length屬性,定義包含的元素個數
- this.context=selector;//設定例項的屬性,返回選擇範圍
- returnthis;//返回當前例項
- }
- if(typeofselector==="string"){//如果選擇符是字串
- vare=context.getElementsByTagName(selector);//獲取指定名稱的元素
- for(vari=0;i<e.length;i++){//遍歷元素集合,並把所有元素填入到當前例項陣列中
- this[i]=e[i];
- }
- this.length=e.length;//設定例項的length屬性,即定義包含的元素個數
- this.context=context;//設定例項的屬性,返回選擇範圍
- returnthis;//返回當前例項
- }else{
- this.length=0;//否則,設定例項的length屬性值為0
- this.context=context;//設定例項的屬性,返回選擇範圍
- returnthis;//返回當前例項
- }
- },
- jquery:"1.3.2",//原型屬性
- size:function(){//原型方法
- returnthis.length;
- }
- };
- jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型物件覆蓋init的原型物件
- alert($("div").size());//返回3
- </script>
相關文章
- 第二章 jQuery技術解密 (二)jQuery解密
- 第二章 jQuery技術解密 (四)jQuery解密
- 第二章 jQuery技術解密 (三)jQuery解密
- 第二章 jQuery技術解密 (五)jQuery解密
- 第二章 jQuery技術解密 (六)jQuery解密
- 第二章 jQuery技術解密 (七)jQuery解密
- 解密|一文帶你看懂外掛技術解密
- 騰訊萬億級 Elasticsearch 技術解密Elasticsearch解密
- ChatGPT軟體技術棧解密ChatGPT解密
- Oracle Flashback 技術大解密Oracle解密
- 精通struts技術第二章(4) (轉)
- 精通struts技術第二章(5) (轉)
- 解密數倉的SQL ON ANYWHERE技術解密SQL
- 社交軟體紅包技術解密(十二):解密抖音春節紅包背後的技術設計與實踐解密
- 有獎書評活動:《京東技術解密》解密
- 第二章投資技術《第五節 背離》
- 讀《JavaScript核心技術開發解密》筆記JavaScript解密筆記
- 解密阿里巴巴安全技術體系解密阿里
- 【技術解密】SequoiaDB分散式儲存原理解密分散式
- [解密] DNA儲存技術究竟牛在哪裡?解密
- [原創]京東技術解密讀書筆記解密筆記
- jQuery第二章知識點jQuery
- jQuery第二章選擇器jQuery
- 讀《圖解密碼技術》(三):金鑰、隨機數和應用技術圖解解密密碼隨機
- 第二章投資技術《第三節 K線秘籍》
- vivo營銷自動化技術解密|開篇解密
- jQuery第二章課後作業jQuery
- 地球上最大的PHP站點後端技術解密PHP後端解密
- 解密阿里巴巴的技術發展路徑解密阿里
- 防禦網路威脅UTM技術解密(圖示)解密
- jQuery的沒落和技術發展的一般規律jQuery
- “聲討”高雲的《jQuery技術內幕》jQuery
- 個人練習前端技術使用Bootstrap、JQuery、thymeleaf前端bootjQuery
- Python技術分享:教你如何解密隔壁WiFi密碼Python解密WiFi密碼
- 解密負載均衡技術和負載均衡演算法解密負載演算法
- 打造“雲邊一體化”,時序時空資料庫TSDB技術原理深度解密資料庫解密
- 技術面試(一)面試
- 全面解密QQ紅包技術方案:架構、技術實現、移動端優化、創新玩法等解密架構優化