實(shí)例詳解jQuery的無new構(gòu)建
jQuery的無new構(gòu)建
jQuery框架的核心就是從HTML文檔中匹配元素并對(duì)其執(zhí)行操作、
回想一下使用 jQuery 的時(shí)候,實(shí)例化一個(gè) jQuery 對(duì)象的方法:
// 無 new 構(gòu)造 $('#test').text('Test'); // 當(dāng)然也可以使用 new var test = new $('#test'); test.text('Test');
大部分人使用 jQuery 的時(shí)候都是使用第一種無 new 的構(gòu)造方式,直接 $('')
進(jìn)行構(gòu)造,這也是 jQuery 十分便捷的一個(gè)地方。
當(dāng)我們使用第一種無 new 構(gòu)造方式的時(shí)候,其本質(zhì)就是相當(dāng)于 new jQuery()
,那么在 jQuery 內(nèi)部是如何實(shí)現(xiàn)的呢?看看:
(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);
沒看懂?沒關(guān)系,我們一步一步分析。
函數(shù)表達(dá)式和函數(shù)聲明
在ECMAScript中,創(chuàng)建函數(shù)的最常用的兩個(gè)方法是函數(shù)表達(dá)式和函數(shù)聲明,兩者期間的區(qū)別是有點(diǎn)暈,因?yàn)镋CMA規(guī)范只明確了一點(diǎn):函數(shù)聲明必須帶有標(biāo)示符(Identifier
)(就是大家常說的函數(shù)名稱),而函數(shù)表達(dá)式則可以省略這個(gè)標(biāo)示符:
//函數(shù)聲明: function 函數(shù)名稱 (參數(shù):可選){ 函數(shù)體 } //函數(shù)表達(dá)式: function 函數(shù)名稱(可選)(參數(shù):可選){ 函數(shù)體 }
所以,可以看出,如果不聲明函數(shù)名稱,它肯定是表達(dá)式,可如果聲明了函數(shù)名稱的話,如何判斷是函數(shù)聲明還是函數(shù)表達(dá)式呢?
ECMAScript是通過上下文來區(qū)分的,如果function foo(){}
是作為賦值表達(dá)式的一部分的話,那它就是一個(gè)函數(shù)表達(dá)式,
如果function foo(){}
被包含在一個(gè)函數(shù)體內(nèi),或者位于程序的最頂部的話,那它就是一個(gè)函數(shù)聲明。
function foo(){} // 聲明,因?yàn)樗浅绦虻囊徊糠? var bar = function foo(){}; // 表達(dá)式,因?yàn)樗琴x值表達(dá)式的一部分 new function bar(){}; // 表達(dá)式,因?yàn)樗莕ew表達(dá)式 (function(){ function bar(){} // 聲明,因?yàn)樗呛瘮?shù)體的一部分 })();
還有一種函數(shù)表達(dá)式不太常見,就是被括號(hào)括住的(function foo(){})
,他是表達(dá)式的原因是因?yàn)槔ㄌ?hào) ()是一個(gè)分組操作符,它的內(nèi)部只能包含表達(dá)式
再來看jQuery源碼:
(function(window, undefined) { /... })(window)
可以將上面的代碼結(jié)構(gòu)分成兩部分:(function(){window, undefined})
和 (window) ,
第1個(gè)()是一個(gè)表達(dá)式,而這個(gè)表達(dá)式本身是一個(gè)匿名函數(shù),
所以在這個(gè)表達(dá)式后面加(window)就表示執(zhí)行這個(gè)匿名函數(shù)并傳入?yún)?shù)window。
原型 prototype
認(rèn)識(shí)一下什么是原型?
在JavaScript中,原型也是一個(gè)對(duì)象,通過原型可以實(shí)現(xiàn)對(duì)象的屬性繼承,JavaScript的對(duì)象中都包含了一個(gè)" [[Prototype]]"
內(nèi)部屬性,這個(gè)屬性所對(duì)應(yīng)的就是該對(duì)象的原型。
對(duì)于"prototype"和"__proto__"這兩個(gè)屬性有的時(shí)候可能會(huì)弄混,"Person.prototype"和"Person.__proto__"是完全不同的。
在這里對(duì)"prototype"和"__proto__"進(jìn)行簡(jiǎn)單的介紹:
1.對(duì)于所有的對(duì)象,都有__proto__屬性,這個(gè)屬性對(duì)應(yīng)該對(duì)象的原型
2.對(duì)于函數(shù)對(duì)象,除了__proto__屬性之外,還有prototype屬性,當(dāng)一個(gè)函數(shù)被用作構(gòu)造函數(shù)來創(chuàng)建實(shí)例時(shí),該函數(shù)的prototype屬性值將被作為原型賦值給所有對(duì)象實(shí)例(也就是設(shè)置實(shí)例的__proto__屬性)
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); }; //調(diào)用 var will = new Person("Will", 28); will.getInfo();//"Will is 28 years old"
閉包
閉包的定義:
當(dāng)一個(gè)內(nèi)部函數(shù)被其外部函數(shù)之外的變量引用時(shí),就形成了一個(gè)閉包。
閉包的作用:
在了解閉包的作用之前,我們先了解一下 javascript中的GC機(jī)制:
在javascript中,如果一個(gè)對(duì)象不再被引用,那么這個(gè)對(duì)象就會(huì)被GC回收,否則這個(gè)對(duì)象一直會(huì)保存在內(nèi)存中。
在上述例子中,B定義在A中,因此B依賴于A,而外部變量 c 又引用了B, 所以A間接的被 c 引用,
也就是說,A不會(huì)被GC回收,會(huì)一直保存在內(nèi)存中。為了證明我們的推理,看如下例子:
function A(){ var count = 0; function B(){ count ++; console.log(count); } return B; } var c = A(); c();// 1 c();// 2 c();// 3
count是A中的一個(gè)變量,它的值在B中被改變,函數(shù)B每執(zhí)行一次,count的值就在原來的基礎(chǔ)上累加1。因此,A中的count一直保存在內(nèi)存中。
這就是閉包的作用,有時(shí)候我們需要一個(gè)模塊中定義這樣一個(gè)變量:希望這個(gè)變量一直保存在內(nèi)存中但又不會(huì)“污染”全局的變量,這個(gè)時(shí)候,我們就可以用閉包來定義這個(gè)模塊
在看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);
我們知道了 什么是閉包:當(dāng)一個(gè)內(nèi)部函數(shù)被其外部函數(shù)之外的變量引用時(shí),就形成了一個(gè)閉包。
jQuery.fn的init 函數(shù)被jQuery 的構(gòu)造函數(shù)調(diào)用了,這里形成了一個(gè)閉包。
構(gòu)造函數(shù)及調(diào)用代碼:
// ... jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); },
問題關(guān)鍵來了。
如何實(shí)現(xiàn)無new構(gòu)建
JavaScript是函數(shù)式語言,函數(shù)可以實(shí)現(xiàn)類,類就是面向?qū)ο缶幊讨凶罨镜母拍?/p>
var aQuery = function(selector, context) { //構(gòu)造函數(shù) } aQuery.prototype = { //原型 name:function(){}, age:function(){} } var a = new aQuery(); a.name();
這是常規(guī)的使用方法,顯而易見jQuery不是這樣玩的
要實(shí)現(xiàn)這樣,那么jQuery就要看成一個(gè)類,那么$()應(yīng)該是返回類的實(shí)例才對(duì)
按照jQuery的抒寫方式
$().ready() $().noConflict()
要實(shí)現(xiàn)這樣,那么jQuery就要看成一個(gè)類,那么$()應(yīng)該是返回類的實(shí)例才對(duì)
所以把代碼改一下:
var aQuery = function(selector, context) { return new aQuery(); } aQuery.prototype = { name:function(){}, age:function(){} }
通過new aQuery(),
雖然返回的是一個(gè)實(shí)例,但是也能看出很明顯的問題,死循環(huán)了!
那么如何返回一個(gè)正確的實(shí)例?
在javascript中實(shí)例this只跟原型有關(guān)系
那么可以把jQuery類當(dāng)作一個(gè)工廠方法來創(chuàng)建實(shí)例,把這個(gè)方法放到aQuery.prototye原型中
var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init:function(selector){ return this; } name:function(){}, age:function(){} }
當(dāng)執(zhí)行aQuery()
返回的實(shí)例:
很明顯aQuery()
返回的是aQuery
類的實(shí)例,那么在init中的this其實(shí)也是指向的aQuery
類的實(shí)例
問題來了init的this指向的是aQuery
類,如果把init函數(shù)也當(dāng)作一個(gè)構(gòu)造器,那么內(nèi)部的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
因?yàn)閠his只是指向aQuery
類的,所以aQuery
的age
屬性是可以被修改的。
這樣看似沒有問題,其實(shí)問題很大的
為什么是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
當(dāng)我調(diào)用 傳入"a"的時(shí)候,修改age=18,及aQuery("a").age 的值為18
但是當(dāng)我 傳入"b"的時(shí)候 并沒又修改 age的值,我也希望得到默認(rèn)age的值20,但是aQuery("b").age 的值為18.
因?yàn)樵?調(diào)用aQuery("a").age 的時(shí)候age被修改了。
這樣的情況下就出錯(cuò)了,所以需要設(shè)計(jì)出獨(dú)立的作用域才行。
jQuery框架分隔作用域的處理
jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); },
很明顯通過實(shí)例init
函數(shù),每次都構(gòu)建新的init
實(shí)例對(duì)象,來分隔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'
又出現(xiàn)一個(gè)新的問題,
age :undefined,
name() :
拋出錯(cuò)誤,無法找到這個(gè)方法,所以很明顯new的init
跟jquery類的this
分離了
怎么訪問jQuery類原型上的屬性與方法?
做到既能隔離作用域還能使用jQuery原型對(duì)象的作用域呢,還能在返回實(shí)例中訪問jQuery的原型對(duì)象?
實(shí)現(xiàn)的關(guān)鍵點(diǎn)
// Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn;
我們?cè)俑囊幌拢?/p>
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);
是不是明白了?
哈哈哈~~~
在簡(jiǎn)單說兩句:
大部分人初看 jQuery.fn.init.prototype = jQuery.fn
這一句都會(huì)被卡主,很是不解。但是這句真的算是 jQuery 的絕妙之處。理解這幾句很重要,分點(diǎn)解析一下:
1)首先要明確,使用 $('xxx')
這種實(shí)例化方式,其內(nèi)部調(diào)用的是 return new jQuery.fn.init(selector, context, rootjQuery)
這一句話,也就是構(gòu)造實(shí)例是交給了 jQuery.fn.init()
方法取完成。
2)將 jQuery.fn.init
的 prototype 屬性設(shè)置為 jQuery.fn
,那么使用 new jQuery.fn.init()
生成的對(duì)象的原型對(duì)象就是 jQuery.fn
,所以掛載到 jQuery.fn
上面的函數(shù)就相當(dāng)于掛載到 jQuery.fn.init()
生成的 jQuery 對(duì)象上,所有使用 new jQuery.fn.init()
生成的對(duì)象也能夠訪問到 jQuery.fn
上的所有原型方法。
3)也就是實(shí)例化方法存在這么一個(gè)關(guān)系鏈
1.jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;
2.new jQuery.fn.init() 相當(dāng)于 new jQuery() ;
3.jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),所以這 2 者是相當(dāng)?shù)?,所以我們可以無 new 實(shí)例化 jQuery 對(duì)象。
總結(jié)
以上就是jQuery的無new構(gòu)建的全部?jī)?nèi)容,希望本文對(duì)大家學(xué)習(xí)jQuery有所幫助。也請(qǐng)大家繼續(xù)支持腳本之家。
相關(guān)文章
jQuery中使用each處理json數(shù)據(jù)
這篇文章主要介紹了jQuery中使用each處理json數(shù)據(jù),非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-04-04教你用jquery實(shí)現(xiàn)iframe自適應(yīng)高度
iframe因?yàn)槟芎途W(wǎng)頁無縫的結(jié)合從而不刷新頁面的情況下更新頁面的部分?jǐn)?shù)據(jù)成為可能,可是 iframe的大小卻不像層那樣可以“伸縮自如”,所以帶來了使用上的麻煩,給iframe設(shè)置高度的時(shí)候多了也不好,少了更是不行,今天我們就來分享2種使用jquery實(shí)現(xiàn)iframe自適應(yīng)高度的代碼2014-06-06jquery拖拽效果完整實(shí)例(附demo源碼下載)
這篇文章主要介紹了jquery拖拽效果實(shí)現(xiàn)方法,詳細(xì)介紹了jQuery實(shí)現(xiàn)拖拽功能的具體步驟與相關(guān)技巧,并附代碼了demo源碼供讀者下載參考,需要的朋友可以參考下2016-01-01基于jQuery實(shí)現(xiàn)的水平和垂直居中的div窗口
在建立網(wǎng)頁布局的時(shí)候,我們經(jīng)常會(huì)面臨一個(gè)問題,就是讓一個(gè)div實(shí)現(xiàn)水平和垂直居中,雖然好幾種方式實(shí)現(xiàn),但是今天介紹時(shí)我最喜歡的方法,通過css和jQuery實(shí)現(xiàn)。2011-08-08