總感覺自己的jQuery用的特別流利。分分鐘就能選擇一個DOM元素,讓頁面改頭換面??勺屪约簩憘€東西的時候,又寫的很是別扭。比如要寫一個插件,分分鐘覺得腦子里一團漿糊。從這撿點概念,從那拿點技巧,總是形不成系統的認識。
好了,索性就花點心思把jQuery插件開發梳理一遍,讓腦子里的漿糊變成一碗香噴噴的面。
$.extend() 和 $.fn.extend()
用jquery開發插件,用到的核心方法有兩個:
1、$.extend() —— 為jquery添加靜態方法。如我們經常用到的$.each( object, callback )迭代方法,即靜態方法。
2、$.fn.extend() —— 為jquery添加實例方法。如$('.yunmo').css('color','#000'),選擇一個類名為yunmo的DOM元素,所形成的$('.yunmo')實例應用css()方法。
基本用法如下:
$.extend()
$.extend({
learn: function () {
console.log('I am learning coding');
},
practice: function () {
console.log('I am translating my coding skills into something visible!');
}
});
$.learn();
$.practice();
$.fn.extend()
$.fn.extend({
learn: function () {
console.log('I am learning coding');
},
practice: function () {
console.log('I am translating my coding skills into something visible!');
}
});
//以上定義方式等同于
$.fn.learn = function () {
console.log('I am learning coding');
};
$.fn.practice = function () {
console.log('I am translating my coding skills into something visible!');
};
$('.yunmo').learn();
$('.yunmo').practice();
這里有必要來解釋一下$.extend()方法。
通常,jQuery.extend()函數用于將一個或多個對象的內容合并到目標對象,即該函數可以將一個或多個對象的成員屬性和方法復制到指定的對象上。屬于全局jQuery對象。有以下兩種調用方式。
jQuery.extend( target [, object1 ] [, objectN... ] );
jQuery.extend( [ deep ], target , object1 [, objectN... ] );
- deep:可選/Boolean類型指示是否深度合并對象,默認為false。如果該值為true,且多個對象的某個同名屬性也都是對象,則該"屬性對象"的屬性也將進行合并。
- target:Object類型目標對象,其他對象的成員屬性將被復制到該對象上。
- object1:可選/Object類型第一個被合并的對象。
- objectN: 可選/Object類型第N個被合并的對象。
說明:
- 該函數復制的對象屬性包括方法在內。此外,還會復制對象繼承自原型中的屬性(JS內置的對象除外)。
- 參數deep的默認值為false,你可以為該參數明確指定true值,但不能明確指定false值。簡而言之,第一個參數不能為false值。
- 如果參數為null或undefined,則該參數將被忽略。
-
如果只為$.extend()指定了一個參數,則意味著參數target被省略。此時,target就是jQuery對象本身。通過這種方式,我們可以為全局對象jQuery添加新的函數。
- 如果多個對象具有相同的屬性,則后者會覆蓋前者的屬性值。
var obj1 = {name: 'yun', age: '18', skills: {amount: 10, best: 'coding'}};
var obj2 = {name: 'mo', place: 'China', skills: {amount: 8, worst: 'design'}};
//console.log($.extend(obj1, obj2));
//{name: "mo", age: "18", skills: {worst:"design"}, place: "China"}
console.log($.extend(true, obj1, obj2));
//{name: "mo", age: "18", skills: {amount: 10, best: "coding", worst:"design"}, place: "China"}
以上兩句請互斥打印查看,否則控制臺會緩存上句打印處理,導致第二句打印不符合預期。
如上述說明中所說的:如果只為$.extend()指定了一個參數,則意味著參數target被省略。此時,target就是jQuery對象本身。通過這種方式,我們可以為全局對象jQuery添加新的函數。
即:
$.extend({
learn: function () {
console.log('I am learning coding');
},
practice: function () {
console.log('I am translating my coding skills into something visible!');
}
});
可以看到,通過$.extend()模式只能為jQuery類添加簡單的靜態方法,無法進行DOM操作,因此我們需要借助$.fn.extend()模式。
如前面所述,這種模式有兩種寫法。
即:
$.fn.extend({
learn: function () {
console.log('I am learning coding');
}
});
或
$.fn.learn = function () {
console.log('I am learning coding');
};
為什么呢?我們來看看源碼:
jQuery.fn = jQuery.prototype = {
init:function(selector,context){...};
};
這樣就一目了然了,jQuery.fn其實和jQuery的原型指向的是同一對象。那么$.fn.extend()就相當于對jQuery.prototype對象進行擴展(原型也是對象),又因為只傳遞了一個參數,那么這個target就是jQuery.prototype本身。故兩種寫法都可以。$.fn.extend()的通過把方法擴展到對象的jQuery的prototype上,從而實例化一個jQuery對象時,它就具有了這些方法(得益于原型繼承)。
那么便有如下等式成立:
$.fn.extend(plugin) == $.prototype.extend(plugin) == $.fn.plugin
綜合實例
(function ($) {
var Paint = function (element, options) {
this.$element = $(element);
this.options = options;
};
Paint.defaults = {
tools: 'pencil',
color: 'red'
};
Paint.prototype = {
prepare: function () {
var tools = this.options.tools;
this.$element.html('I am painting with '+ tools);
},
getStarted: function () {
var color = this.options.color;
this.$element.css({'color': color, 'fontSize': '20px'});
},
init: function () {
this.prepare();
this.getStarted();
}
};
$.fn.paint = function (options) {
return this.each(function () { //支持鏈式調用
var opt = $.extend({}, Paint.defaults, options); // 支持自定義參數
var paint = new Paint(this, opt);
paint.init();
})
}
})(jQuery);
$('.yun').paint();
$('.mo').paint({color: 'blue'});
FAQ:
1、為什么要像下面這樣,把插件包在(function($){})(jQuery)里面?
(function($){
//plugin code goes here......
})(jQuery);
答:插件封裝和定義作用域:避免插件內部變量污染全局,不對外暴露插件內部的實現;解耦插件邏輯和運行環境之間的依賴。
2、為何插件要return this.each(function(){})?
答:這樣支持鏈式調用。return this.each(function(){})的this,指代jQuery選擇器返回的集合,通過jQuery.each()方法便能夠遍歷集合中的元素進行相應邏輯處理。而在each方法內部,this則指代普通的DOM元素,需要用$(this)來調用jQuery方法,即在this.$element.html('I am painting with '+ tools)這里有所體現。
若要支持鏈式調用,再加上return即可。因為each()方法的返回值為jQuery類型,返回當前jQuery對象本身,這樣就能把each方法返回的jQuery對象返回,就可以繼續調用jQuery的其他方法了。就像這樣;
$('.mo').paint({color: 'blue'}).html('wooooo, it is overridden by another jQuery method!');
3、為何不直接向下面代碼這樣定義,而要重新new 一個Paint實例?
(function ($) {
$.fn.paint = function (options) {
var defaults = {
name: 'yun',
age: '18'
};
var opt = $.extend({}, defaults, options);
return this.each(function () {
//.....
})
}
})(jQuery);
答:在對象上直接定義重要屬性和私有方法,這樣一來,在對象的方法中,我們可以調用對象的其他私有方法和訪問私有變量。如果以后要擴展新功能,只需向對象添加新的變量和私有方法即可。
上例我們在Paint.prototype原型對象上定義了如prepare和getStarted、init私有方法,并定義了一個init方法來調用其他兩個方法。最后讓$.fn.paint專注于插件的調用,而不用關注插件的內部實現。這樣的代碼簡潔明了,易于維護。