前言
最近在折騰jQuery插件,寫成插件的目的就是爲了實現功能與項目相分離,使得這個代碼在下一個項目中能直接引用不出錯。這使得我們在寫插件的時候,就得考慮清楚,怎麼寫才能使得插件能夠通用、靈活度高、可配置、兼容性好、易用性高、耦合度低等。
接下來就對以下幾種寫法進行分析,前兩個是jQuery插件,後面2個是以對象的形式開發,都類似。而且寫法也很多,我們要懂得這樣寫的利弊。另一篇基礎文章:
寫法一
插件主體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | (function($, window){ // 初始態定義 var _oDialogCollections = {}; // 插件定義 $.fn.MNDialog = function (_aoConfig) { // 默認參數,可被重寫 var defaults = { // string sId : "", // num nWidth : 400, // bollean bDisplayHeader : true, // object oContentHtml : "", // function fCloseCallback : null }; var _oSelf = this, $this = $(this); // 插件配置 this.oConfig = $.extend(defaults, _aoConfig); // 初始化函數 var _init = function () { if (_oDialogCollections) { // 對於已初始化的處理 // 如果此時已經存在彈框,則remove掉再添加新的彈框 } // 初始化彈出框數據 _initData(); // 事件綁定 _loadEvent(); // 加載內容 _loadContent(); } // 私有函數 var _initData = function () {}; var _loadEvent = function () {}; var _loadContent = function () { // 內容(分字符和函數兩種,字符爲靜態模板,函數爲異步請求後組裝的模板,會延遲,所以特殊處理) if($.isFunction(_oSelf.oConfig.oContentHtml)) { _oSelf.oConfig.oContentHtml.call(_oSelf, function(oCallbackHtml) { // 便於傳帶參函數進來並且執行 _oSelf.html(oCallbackHtml); // 有回調函數則執行 _oSelf.oConfig.fLoadedCallback && _oSelf.oConfig.fLoadedCallback.call(_oSelf, _oSelf._oContainer$); }); } else if ($.type(_oSelf.oConfig.oContentHtml) === "string") { _oSelf.html(_oSelf.oConfig.oContentHtml); _oSelf.oConfig.fLoadedCallback && _oSelf.oConfig.fLoadedCallback.call(_oSelf, _oSelf._oContainer$); } else { console.log("彈出框的內容格式不對,應爲function或者string。"); } }; // 內部使用參數 var _oEventAlias = { click : 'D_ck', dblclick : 'D_dbl' }; // 提供外部函數 this.close = function () { _close(); } // 啓動插件 _init(); // 鏈式調用 return this; }; // 插件結束 })(jQuery, window); |
調用
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
MNDialog
=
$("#header").MNDialog({
sId
:
"#footer", //覆蓋默認值
fCloseCallback
:
dialog,//回調函數
oContentHtml
:
function(_aoCallback){
_aoCallback(_oEditGrpDlgView.el);
}
}
});
//
調用提供的函數
MNDialog.close;
function
dialog(){
}
|
點評
1. 自調用匿名函數
1 2 3 | (function($, window) { // jquery code })(jQuery, window); |
用處:通過定義一個匿名函數,創建了一個“私有”的命名空間,該命名空間的變量和方法,不會破壞全局的命名空間。這點非常有用也是一個JS框架必須支持的功能,jQuery被應用在成千上萬的JavaScript程序中,必須確保jQuery創建的變量不能和導入他的程序所使用的變量發生衝突。
2. 匿名函數爲什麼要傳入window
通過傳入window變量,使得window由全局變量變爲局部變量,當在jQuery代碼塊中訪問window時,不需要將作用域鏈回退到頂層作用域,這樣可以更快的訪問window;這還不是關鍵所在,更重要的是,將window作爲參數傳入,可以在壓縮代碼時進行優化,看看jquery.min.js:
1
|
(function(a,b){})(jQuery,
window);
//
jQuery被優化爲a, window 被優化爲 b
|
3. 全局變量this定義
1 2 | var _oSelf = this, $this = $(this); |
使得在插件的函數內可以使用指向插件的this
4. 插件配置
1
|
this.oConfig
=
$.extend(defaults,
_aoConfig);
|
設置默認參數,同時也可以再插件定義時傳入參數覆蓋默認值
5. 初始化函數
一般的插件會有init初始化函數並在插件的尾部初始化
6. 私有函數、公有函數
私有函數:插件內使用,函數名使用”_”作爲前綴標識
共有函數:可在插件外使用,函數名使用”this.”作爲前綴標識,作爲插件的一個方法供外部使用
7. return this
最後返回jQuery對象,便於jQuery的鏈式操作
寫法二
主體結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | (function($){ $.fn.addrInput = function(_aoOptions){ var _oSelf = this; _oSelf.sVersion = 'version-1.0.0'; _oSelf.oConfig = { nInputLimitNum : 9 }; // 插件配置 $.extend(_oSelf.oConfig, _aoOptions); // 調用這個對象的方法,傳遞this $.fn.addrInput._initUI.call(_oSelf, event); $.fn.addrInput._initEvents.call(_oSelf); // 提供外部函數 this.close = function () { _close(); } //返回jQuery對象,便於Jquery的鏈式操作 return _oSelf; } $.fn.addrInput._initUI = function(event){ var _oSelf = this, _oTarget = $(event.currentTarget); } $.fn.addrInput._initEvents = function(){} })(window.jQuery); |
點評
1. 美觀
插件的方法寫在外部,並通過在插件主體傳遞this的方式調用
2. 定義插件版本號
不過在這裏還是沒有用到
3. 關於call
這裏的第一個參數爲傳遞this,後面的均爲參數
語法:
1
|
call([thisObj[,arg1[,
arg2[,
[,.argN]]]]])
|
定義:調用一個對象的一個方法,以另一個對象替換當前對象。
說明:call 方法可以用來代替另一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。如果沒有提供 thisObj 參數,那麼 Global 對象被用作 thisObj。
4. 關於”this”
在插件的方法中,可能有用到指向插件的this、和指向事件觸發的this,所以事件觸發的this用event來獲取:event.cuerrntTarget
如:
html代碼
1 2 3 | <div id="wrapper"> <a href="#" id="inner">click here!</a> </div> |
js代碼
1
2
3
4
5
6
7
8
9
10
|
$('#wrapper').click(function(e)
{
console.log('#wrapper');
console.log(e.currentTarget);
console.log(e.target);
});
$('#inner').click(function(e)
{
console.log('#inner');
console.log(e.currentTarget);
console.log(e.target);
});
|
結果輸出
1 2 3 4 5 6 | #inner <a href="#" id="inner">click here!</a> <a href="#" id="inner">click here!</a> #wrapper <div id="wrapper"><a href="#" id="inner">click here!</a></div> <a href="#" id="inner">click here!</a> |
寫法三(原生寫法)
主體結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
var
MemberCard
=
function(_aoOption){
//
配置(默認是從外面傳進來)
_aoOption
||
(_aoOption
=
{})
;
//
初始化函數
_init(this);
}
var
_init
=
function(_aoSelf)
{
//
函數執行
_initData(_aoSelf);
//
調用對象的私有方法
_aoSelf._timedHide();
}
var
_initData
=
function
(
_aoSelf
)
{}
//
私有方法
MemberCard.prototype._timedHide
=
function(_aoOptions)
{
var
_oSelf
=
this;
clearTimeout(this.iHideTimer);
//
使用underscore.js的extend方法來實現屬性覆蓋
var
oDefault
=
extend(
{
nHideTime:
300
},
_aoOptions
);
_oSelf.iHideTimer
=
setTimeout(
function(){
//
調用對象的共有方法
_oSelf.hide();
},
oDefault.nHideTime);
}
//
公有方法
MemberCard.prototype.hide
=
function(_aoOptions)
{}
|
使用
1 2 | var oColleagueCard = new MemberCard({ nHideTime: 200 }); oColleagueCard.hide(); |
點評
1. 關於屬性覆蓋(對象深拷貝)
原生函數實現方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function
getType(o){
return
((_t
=
typeof(o))
==
"object"
?
o==null
&&
"null"
||
Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
}
function
extend(destination,source){
for(var
p
in
source){
if(getType(source[p])=="array"||getType(source[p])=="object"){
destination[p]=getType(source[p])=="array"?[]:{};
arguments.callee(destination[p],source[p]);
}else{
destination[p]=source[p];
}
}
}
|
demo:
1 2 3 4 5 6 | var test={a:"ss",b:[1,2,3],c:{d:"css",e:"cdd"}}; var test1={}; extend(test1,test); test1.b[0]="change"; //改變test1的b屬性對象的第0個數組元素 alert(test.b[0]); //不影響test,返回1 alert(test1.b[0]); //返回change |
基於jQuery的實現方法
1
|
jQuery.extend([deep],
target,
object1,
[objectN]);
|
用一個或多個其他對象來擴展一個對象,返回被擴展的對象。
如果不指定target,則給jQuery命名空間本身進行擴展。這有助於插件作者爲jQuery增加新方法。 如果第一個參數設置爲true,則jQuery返回一個深層次的副本,遞歸地複製找到的任何對象。否則的話,副本會與原對象共享結構。 未定義的屬性將不會被複制,然而從對象的原型繼承的屬性將會被複制。
demo:
1 2 | var options = {id: "nav", class: "header"} var config = $.extend({id: "navi"}, options); //config={id: "nav", class: "header"} |
2. 關於this
這個對象的所有方法的this都指向這個對象,所以就不需要重新指定
寫法四
主體結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
function
EditorUtil()
{
this._editorContent
=
$(
'#editor_content'
);
this._picBtn
=
$(
'#project_pic'
);
this.ieBookmark
=
null;
}
EditorUtil.prototype
=
{
consturctor:
EditorUtil,
noteBookmark:
function()
{
},
htmlReplace:
function(
text
)
{
if(
typeof
text
===
'string'
)
{
return
text.replace(
/[<>"&]/g,
function( match, pos, originalText ) {
switch(
match ) {
case
'<':
return
'<';
case
'>':
return
'>';
case
'&':
return
'&';
case
'"':
return
'"';
}
});
}
return
'';
},
init:
function() {
this._memBtn.bind(
'click',
function(
event
)
{
$(".error_content").hide();
return
false;
});
}
};
//
初始化富文本編輯器
var
editor
=
new
EditorUtil();
editor.init();
|
點評
寫法四和寫法三其實都差不多,但是你們有沒有看出其中的不一樣呢?
1. 兩種都是利用原型鏈給對象添加方法
寫法三:
1 2 | MemberCard.prototype._timedHide MemberCard.prototype.hide |
寫法四:
1
2
3
4
5
|
EditorUtil.prototype
=
{
consturctor:
EditorUtil,
noteBookmark:
function(){},
htmlReplace:
function(){}
}
|
細看寫法四利用“對象直接量”的寫法給EditorUtil對象添加方法,和寫法三的區別在於寫法四這樣寫會造成consturctor屬性的改變
constructor屬性:始終指向創建當前對象的構造函數
每個函數都有一個默認的屬性prototype,而這個prototype的constructor默認指向這個函數。如下例所示:
1 2 3 4 5 6 7 8 9 10 11 12 | function Person(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; }; var p = new Person("ZhangSan"); console.log(p.constructor === Person); // true console.log(Person.prototype.constructor === Person); // true // 將上兩行代碼合併就得到如下結果 console.log(p.constructor.prototype.constructor === Person); // true |
當時當我們重新定義函數的prototype時(注意:和上例的區別,這裏不是修改而是覆蓋),constructor屬性的行爲就有點奇怪了,如下示例:
1
2
3
4
5
6
7
8
9
10
11
12
|
function
Person(name)
{
this.name
=
name;
};
Person.prototype
=
{
getName:
function()
{
return
this.name;
}
};
var
p
=
new
Person("ZhangSan");
console.log(p.constructor
===
Person); //
false
console.log(Person.prototype.constructor
===
Person);
//
false
console.log(p.constructor.prototype.constructor
===
Person);
//
false
|
爲什麼呢?
原來是因爲覆蓋Person.prototype時,等價於進行如下代碼操作:
1 2 3 4 5 | Person.prototype = new Object({ getName: function() { return this.name; } }); |
而constructor屬性始終指向創建自身的構造函數,所以此時Person.prototype.constructor === Object,即是:
1
2
3
4
5
6
7
8
9
10
11
12
|
function
Person(name)
{
this.name
=
name;
};
Person.prototype
=
{
getName:
function()
{
return
this.name;
}
};
var
p
=
new
Person("ZhangSan");
console.log(p.constructor
===
Object); //
true
console.log(Person.prototype.constructor
===
Object);
//
true
console.log(p.constructor.prototype.constructor
===
Object);
//
true
|
怎麼修正這種問題呢?方法也很簡單,重新覆蓋Person.prototype.constructor即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function
Person(name)
{
this.name
=
name;
};
Person.prototype
=
new
Object({
getName:
function()
{
return
this.name;
}
});
Person.prototype.constructor
=
Person;
var
p
=
new
Person("ZhangSan");
console.log(p.constructor
===
Person); //
true
console.log(Person.prototype.constructor
===
Person);
//
true
console.log(p.constructor.prototype.constructor
===
Person);
//
true
|
轉自 http://www.xuanfengge.com/jquery-plug-in-written-summary-and-summary-of-writing-object-oriented-manner.html