1、對象的構建
// 方法一 function ajquery (name) { this.name = name; this.sayName = function() { return this.name; } return this; } // 方法二 function ajquery(name) { this.name = name; } ajquery.prototype = { sayName : function () { return this.name; } }
上面是兩種建立類的方式,雖然最後實現的效果是一致的可是在性能上確實不一致的,當咱們實例化三個ajquery對象的時候,對於方法一,每個實例都有一個sayName方法,這樣就浪費了內存,增長了開銷,方法二則是吧sayName放到了原型鏈上了,這樣每個實例對象都能共享這個方法了(是一個不錯的選擇呢),只是經過scope鏈接到原型鏈上查找,這樣就無形之中也就是多了一層做用域鏈的查找;javascript
jquery的對象在性能上考慮,因此就必須採用原型鏈了呢css
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); } jQuery.fn = jQuery.prototype = { init:function(){ return this }, jquery: version, constructor: jQuery, // ……………… } var a = $() ;
2、 jquery中的對象的構造器的分離
new一個實例的過程html
1. 建立一個空對象 2. 將構造函數的做用域賦給這個對象,這個this就指向了這個對象了 3. 執行構造函數中的代碼 4. 返回這個對象
new主要是吧原型鏈跟實例的this鏈接起來java
var $$ = ajquery = function (selector) { this.selector = selector; return this; } ajquery.fn = ajquery.prototype = { getSelector: function () { return this.selector; }, constructor: ajquery } var a = new $$('aaa'); a.getSelector();
var $$ = ajquery = function (selector) { if(!(this instanceof ajquery)) { return new ajquery(selector); } this.selector = selector; return this; }
var $$ = ajquery = function(selector) { return new ajquery.fn.init(selector); } ajquery.fn = ajquery.prototype = { name: 'meils', init: function (selector){ console.log(this); }, constructor: ajquery }
init是ajQuery原型上做爲構造器的一個方法,那麼其this就不是ajQuery了,因此this就徹底引用不到ajQuery的原型了,因此這裏經過new把init方法與ajQuery給分離成2個獨立的構造器。jquery
3、 方法的鏈式調用
$('input[type="button"]') .eq(0).click(function() { alert('點擊我!'); }).end().eq(1) .click(function() { $('input[type="button"]:eq(0)').trigger('click'); }).end().eq(2) .toggle(function() { $('.aa').hide('slow'); }, function() { $('.aa').show('slow'); }); // end()方法是將最近的篩選操做還原爲前一步操做的狀態
看這個代碼的結構,咱們或多或少都能猜到其含義:數組
☑ 找出type類型爲button的input元素安全
☑ 找到第一個按鈕,並綁定click事件處理函數app
☑ 返回全部按鈕,再找到第二個jquery插件
☑ 爲第二個按鈕綁定click事件處理函數異步
☑ 爲第三個按鈕綁定toggle事件處理函數
jquery的核心理念是寫的少,辦的多。
jquery.fn.init = function() { return this; } jquery.fn.get = function() { return this; }
// 經過返回this來實現鏈式操做,由於返回當前實例的this,從而又能夠訪問本身的原型了,這樣的就節省代碼量,提升代碼的效率,代碼看起來更優雅。可是這種方法有一個問題是:全部對象的方法返回的都是對象自己,也就是說沒有返回值,因此這種方法不必定在任何環境下都適合。
雖然Javascript是無阻塞語言,可是他並非沒阻塞,而是不能阻塞,因此他須要經過事件來驅動,異步來完成一些本須要阻塞進程的操做,這樣處理只是同步鏈式,除了同步鏈式還有異步鏈式,異步鏈式jQuery從1.5開始就引入了Promise,jQuery.Deferred後期再討論。
4、插件接口的設計
jQUery爲插件的編寫提供了兩個接口,一種時 $.extend()
將其做爲靜態方法處理,另外一種時將方法掛在到 $.fn
上,做爲jquery的原型對象的方法。
fn與jQuery實際上是2個不一樣的對象,在以前有講解:jQuery.extend 調用的時候,this是指向jQuery對象的(jQuery是函數,也是對象!),因此這裏擴展在jQuery上。而jQuery.fn.extend 調用的時候,this指向fn對象,jQuery.fn 和jQuery.prototype指向同一對象,擴展fn就是擴展jQuery.prototype原型對象。這裏增長的是原型方法,也就是對象方法了。因此jQuery的API中提供了以上2個擴展函數。
5、插件開發
jquery插件的開發模式一共有三種。
第一種方式僅僅只是在$的命名空間下建立了一個靜態方法,能夠直接使用$來調用執行便可。而不須要使用$('selector')來選中DOM對象再來調用方法。
第三種方法是用來編寫更高級的插件使用的,通常不使用。
第二種方法是咱們平時開發所,最經常使用的一種形式,稍後咱們將仔細學習該方法
$.extend({ sayName: function(name) { console.log(name); } }) $.sayName('meils'); // 直接使用 $.sayName 來調用 // 經過$.extend()向jQuery添加了一個sayHello函數,而後經過$直接調用。到此你能夠認爲咱們已經完成了一個簡單的jQuery插件了。
這種方法用來定義一些輔助方法,仍是有用的。
$.extend({ log: function(message) { var now = new Date(), y = now.getFullYear(), m = now.getMonth() + 1, //!JavaScript中月分是從0開始的 d = now.getDate(), h = now.getHours(), min = now.getMinutes(), s = now.getSeconds(), time = y + '/' + m + '/' + d + ' ' + h + ':' + min + ':' + s; console.log(time + ' My App: ' + message); } }) $.log('initializing...'); //調用
1、基本使用
$.fn.setColor = function() { this.css('color', 'red'); } //這裏的this指向調用咱們這個方法的那個對象
2、each()遍歷
咱們也能夠對咱們獲取到的元素的集合的每個元素使用咱們的方法。
$.fn.addUrl = function() { this.css('css', 'red'); this.each(function() { $(this).append('i'); }) }
jquery選擇器
選中的是一個集合,所以能夠$.each()
來遍歷每個元素,在each
內部,this
`指的就是每個DOM元素了,若是須要調用jquery的方法,那麼就須要在用$
來包裝一下了;
3、鏈式調用
$.fn.setSelector = function() { this.css('color', 'red'); return this.each(function() { }) }
4、參數的接收
咱們會使用$.extend()來處理咱們參數,由於這個方法若是傳入多個參數後,他會把這些合併到第一個上。若是存在有同名參數,那麼遵循後面的覆蓋前面的。
// 利用這一點,咱們能夠在插件裏定義一個保存插件參數默認值的對象,同時將接收來的參數對象合併到默認對象上,最後就實現了用戶指定了值的參數使用指定的值,未指定的參數使用插件默認值。 $.fn.setStyle = function(options) { var default = { color: 'red', fontSize: '15px' } var setting = $.extend(default, optioins); return this.css({ 'color': setting.color, 'fontSize': setting.fontSize }) }
上面的參數處理有一點不足,咱們把default也改變了,萬一咱們接下來還要使用該怎麼辦呢,因此咱們還須要在作修改
$.fn.setStyle = function(options) { var default = { color: 'red', fontSize: '15px' } var setting = $.extend({}, default, optioins); return this.css({ 'color': setting.color, 'fontSize': setting.fontSize }) }
5、面向對象開發插件
你可能須要一個方法的時候就去定義一個function,當須要另一個方法的時候,再去隨便定義一個function,一樣,須要一個變量的時候,毫無規則地定義一些散落在代碼各處的變量。
仍是老問題,不方便維護,也不夠清晰。固然,這些問題在代碼規模較小時是體現不出來的。
// 若是將須要的重要變量定義到對象的屬性上,函數變成對象的方法,當咱們須要的時候經過對象來獲取,一來方便管理,二來不會影響外部命名空間,由於全部這些變量名還有方法名都是在對象內部。 var beautify = function(ele, option) { this.$element = this.ele; this.default = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' } this.ops = $.extend({}, default, option); } beautify.prototype = { setStyle : function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }) // return this對象,實現鏈式調用 } } $.fn.myPlugin = function(option) { var beautify = new beautify(this, option); beautify.setStyle(); } /// 使用 $('a').myPlugin({ 'color': '#2C9929', 'fontSize': '20px' });
6、解決命名衝突
由於隨着你代碼的增多,若是有意無心在全局範圍內定義一些變量的話,最後很難維護,也容易跟別人寫的代碼有衝突。
好比你在代碼中向全局window對象添加了一個變量status用於存放狀態,同時頁面中引用了另外一個別人寫的庫,也向全局添加了這樣一個同名變量,最後的結果確定不是你想要的。因此不到萬不得已,通常咱們不會將變量定義成全局的。
一個最好的方法就是始終使用自調用的匿名函數來包裹你的代碼,這樣,就能夠徹底放心的使用本身的變量了。
絕對不會有命名衝突。
;(function() { var beautify = function(ele, option) { this.$element = this.ele; this.default = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' } this.ops = $.extend({}, default, option); } beautify.prototype = { setStyle : function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }) // return this對象,實現鏈式調用 } } $.fn.myPlugin = function(option) { var beautify = new beautify(this, option); beautify.setStyle(); } })();
var foo=function(){ //別人的代碼 }//注意這裏沒有用分號結尾 //開始咱們的代碼。。。 (function(){ //咱們的代碼。。 alert('Hello!'); })();
因爲前面的代碼沒有加;
號 , 而後咱們的插件加載出錯了,因此咱們在咱們插件的最開始加一個;
號來強制前面的結束。
咱們能夠將一些系統變量傳入咱們的插件,這樣能夠縮短變量的做用域鏈,將其做爲咱們插件內部的局部變量來使用,這樣能夠大大提升咱們的速度和性能。
;(function($, window, document, undefined) { var beautify = function(ele, option) { this.$element = this.ele; this.default = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' } this.ops = $.extend({}, default, option); } beautify.prototype = { setStyle : function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }) // return this對象,實現鏈式調用 } } $.fn.myPlugin = function(option) { var beautify = new beautify(this, option); beautify.setStyle(); } })(jQuery, window,document,);
一個安全,結構良好,組織有序的插件編寫完成。
6、版本回溯
jquery能過夠方便的獲取到DOM元素,而且可以遍歷它們。你知道這些背後的原理嗎?
經過對sizzle的分析,咱們知道jquery獲取到的是一個jquery對象,是一個包裝容器。
你會看到在上面有一個prevObject
對象。
在jquery內部維護着一個jquery對象棧,遍歷方法每一次遍歷都會找到一個新的元素,每個元素都是一個jquery對象,而後jquery會把這些元素都放到這個棧中。(入棧)
$('ul').find('li'); // 這句話能夠拆分,第一個jquery對象是$('ul'); 第二個jquery對象是$('ul').find('li'); // 首先將$('ul')入棧 // 而後將li集合(類數組對象)入棧
由於棧中的每個元素都是一個jquery對象,每個jquery對象都有着三個屬性,分別時context
、selector
、prevObject
, prevObject
屬性就是指向對象棧中的前一個元素的。
這個prevObject
就比較有意思了。它指向前一個元素。
舉例子了:
<ul id="aaron"> parent <li>child</li> </ul> var aaron = $("#aaron"); aaron.find('li').click(function(){ alert(1); //1 }) // 若是咱們想在返回父級 aaron.find('li').click(function(){ alert(1); //1 }).end().click(function() { alert(2); }) // end()方法就是進行了回溯,
jquery爲咱們操縱對象棧提供了兩個有用的方法
這兩個方法都是進行版本的回退。
返回最近的一次篩選的上一個狀態。回到最近的一個"破壞性"操做以前。即,將匹配的元素列表變爲前一次的狀態。
<p><span>Hello</span>,how are you?</p> $('p').find('span').end(); // 選取全部的p元素,查找並選取span子元素,而後再回過來選取p元素
end()
方法返回的就是一個prevObject
;
<ul> <li>list item 1</li> <li>list item 2</li> <li class="third-item">list item 3</li> <li>list item 4</li> <li>list item 5</li> </ul> $('li.third-item').nextAll().addBank() .css(); // 初始選擇位於第3項,初始化堆棧集合只包含這項。調用.nextAll() 後將第4和第5項推入堆棧。最後,調用.addBack() 合併這兩個組元素在一塊兒,建立一個jQuery對象,指向全部三個項元素(按照文檔中的順序):{[<li.third-item>,<li>,<li> ]} // [1] li.third-item 入棧 // [2] 四、5入棧 // [3] addBank 合併重組
.addBack()方法致使前一組遍歷堆棧中的DOM元素被添加到當前組。
來舉例子嘍
// First Example $("div.before-addback").find("p").addClass("background"); // p元素添加背景 // Second Example $("div.after-addback").find("p").addBack().addClass("background"); // 選中的p元素以及前一個元素合併到棧中,所有添加背景
利用這個DOM元素棧能夠減小重複的查詢和遍歷的操做,而減小重複操做也正是優化jQuery代碼性能的關鍵所在。
在對對象棧的操做中,用到了一個pushStack()
pushStack 生成了一個新 jQuery 對象 ret,ret 的 prevObject 屬性是指向調用 pushStack 函數的那個 jQuery 對象的,這樣就造成了一個棧鏈,其它原型方法如 find、nextAll、filter 等均可以調用 pushStack 函數,返回一個新的 jQuery 對象並維持對象棧。
jQuery.fn.pushStack = function (elems) { // 將 elems 合併到 jQuery 對象中 var ret = jQuery.merge(this.constructor(), elems); // 實現對象棧 ret.prevObject = this; // 返回 return ret; }
jQuery.fn.end = function() { return this.prevObject || this.constructor; // 若是存在以前的jquery對象就返回它,若是不存在上一個,就返回當前的jQuery對象。 }
jQuery.fn.addBank = function(selector) { return this.add(selector == null ? this.prevObject : this.prevObject.filter(selector)); // 若是參數爲空,就把當前的jquery對象的上一個jQuery對象一塊兒合併爲一個新的對象。若是指定了參數,那麼就在上一個對象中查找這個對象。 }
<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> var li = $('ul').find('li'); console.log(li); //結果以下圖