寫作不易,轉(zhuǎn)載請注明出處,謝謝。
文章類別:Javascript基礎(chǔ)(面向初學(xué)者)
前言
在之前的章節(jié)中,我們已經(jīng)不依賴jQuery,單純地用JavaScript封裝了很多方法,這個時候,你一定會想,這些經(jīng)常使用的方法能不能單獨整理成一個js文件呢?
當(dāng)然可以,封裝本來就是干這個用的。放在一個單獨js文件里固然不錯,其實我們也可以單獨整一個js類庫,一方面可以鍛煉一下自己封裝方法的能力,另一方面,也可以將自己學(xué)到的東西做一個整理。
出于這個目的,本文將介紹如何封裝一個簡單的js類庫。
1. 總體設(shè)計
所謂的js庫,其實也就是一個js文件,我思前想后,決定取個名字叫“miniQuery”,是不是山寨的味道十足呢?哈,請不要在意這些小細節(jié)。
大概的設(shè)計如下:
擴展方法的兼容(主要寫一些兼容的擴展方法,比如 forEach 方法等)
工具包定義 (就是之前封裝的utils.js,我們的miniQuery需要依賴這個工具包,為了方便,就干脆寫在一個文件里面了。)
miniQuery定義
2. 擴展方法的兼容
// ------------------------ 基本擴展, 字符串,數(shù)組等---------------------------------//
function extend_base (){
if(!String.prototype.format ){
String.prototype.format = function() {
var e = arguments;
return this.replace(/{(\d+)}/g,function(t, n) {
return typeof e[n] != "undefined" ? e[n] : t
})
};
}
if (!Array.prototype.forEach && typeof Array.prototype.forEach !== "function") {
Array.prototype.forEach = function(callback, context) {
// 遍歷數(shù)組,在每一項上調(diào)用回調(diào)函數(shù),這里使用原生方法驗證數(shù)組。
if (Object.prototype.toString.call(this) === "[object Array]") {
var i,len;
//遍歷該數(shù)組所有的元素
for (i = 0, len = this.length; i < len; i++) {
if (typeof callback === "function" && Object.prototype.hasOwnProperty.call(this, i)) {
if (callback.call(context, this[i], i, this) === false) {
break; // or return;
}
}
}
}
};
}
if(!String.prototype.format ){
Array.isArray = function(obj){
return obj.constructor.toString().indexOf('Array') != -1;
}
}
//待補充 ...
}
我們定義一個extend_base方法,里面主要對js內(nèi)置對象的api做了一些兼容性補充,目前還不完善,只有寥寥幾個方法。當(dāng)然,如果你不考慮IE678的話,那么基本上不需要這一部分了。
定義完成后立即調(diào)用。
extend_base();
2. 工具包整合
// ------------------------ 工具包---------------------------------//
var utils = {
center : function(dom){
dom.style.position = 'absolute';
dom.style.top = '50%';
dom.style.left = '50%';
dom.style['margin-top'] = - dom.offsetHeight / 2 + 'px';
dom.style['margin-left'] = - dom.offsetWidth / 2 + 'px';
},
/** dom相關(guān) * */
isDom : ( typeof HTMLElement === 'object' ) ?
function(obj){
return obj instanceof HTMLElement;
} :
function(obj){
return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
} ,
/** 數(shù)組相關(guān) * */
isArray : function(obj){
return obj.constructor.toString().indexOf('Array') != -1;
}
}
- center :控制dom元素相對于父盒子居中
- isDom :判斷是否為dom元素
- isArray :判斷是否為數(shù)組
3. miniQuery總體設(shè)計
終于到miniQuery了,在寫代碼之前,先簡單說一下自執(zhí)行函數(shù)。
可能你在很多書上,或者下載的源碼里面,經(jīng)常會看到這樣的代碼:
(function(){
})();
這樣子你或許覺得很奇怪,沒事,我們一起來分析。
在 《JavaScript: 零基礎(chǔ)輕松學(xué)閉包(1)》 里面已經(jīng)說過,在js中,你如果把函數(shù)看作一個數(shù)據(jù)類型,和其他語言中的 Integer, Float , String等等一樣,就會理解很多事情了。當(dāng)然,其實在js中,函數(shù)本身就是一個對象,不然的話就不會出現(xiàn)call方法了。因為只有對象才可以調(diào)用方法嘛。不過,大部分情況下,你把函數(shù)理解為數(shù)據(jù)類型就可以了。
匿名函數(shù):
function(){
}
這是一個函數(shù),因為沒有函數(shù)名,所以是一個匿名函數(shù)。你定義了它,如果接下來你不想通過函數(shù)調(diào)用的方式來執(zhí)行它,那么是不是可以直接給它打一個括號來執(zhí)行呢?
像這樣:
function(){
}();
不過,因為js語法的關(guān)系,這樣子是不能執(zhí)行的,你需要用一對圓括號來包一下:
(
function(){
alert("你好!");
}()
) ;
這樣就可以了,下面是另一種寫法:
(
function(){
alert("你好!");
}
)();
這樣也可以,這種寫法會更多一點。它的意思就是說,我不關(guān)心你這個函數(shù)叫什么名字,反正你在被定義的時候就要給我執(zhí)行,這就是所謂的自執(zhí)行函數(shù)。
好,問題來了,怎么加參數(shù)呢?
以前我們習(xí)慣于這么寫:
function say(str){
alert(str);
}
say("你好!");
依葫蘆畫瓢
(
function(str){
alert(str);
}
)("你好!");
OK了。
是不是一樣的意思呢?
沒啥區(qū)別,以前怎么做,現(xiàn)在還怎么做,無非就是一個函數(shù)傳參的事情罷了。
我們將圓括號的位置調(diào)整一下
( function(str){
alert(str);
} )("你好!");
這樣差不多就是最終的版本了,我記得初學(xué)js的時候,看這種代碼很吃力,好像在看外星語言一樣,后來看多了也就習(xí)慣了。
自執(zhí)行函數(shù)就是這么一回事,沒什么大不了的。
有了上面的解釋,以后如果你再遇到這種寫法,就 so easy 啦。
所以,不要再恐懼了,它就是這么回事,沒什么大不了的,我這么后知后覺的人都能寫,你也可以。我花了半年的時間才看明白,我相信你現(xiàn)在只需要幾分鐘。我的意思是,如果你之前不知道這些的話。
那么,什么時候用自執(zhí)行函數(shù)呢?
當(dāng)你覺得某個函數(shù)只需要執(zhí)行一次,而且不需要在其他地方調(diào)用的時候,就用。
你可能會問了,我干嘛要這樣寫啊,反正就執(zhí)行一次,我直接把實現(xiàn)代碼寫在外面不就行了?
原因很簡單,因為那樣的話,你定義的變量就會是全局的,而一般來說我們設(shè)計的原則是盡量不要使用全局變量。
而采用這種方式,我們就形成了一個匿名函數(shù),函數(shù)的定義又會形成閉包,所以比較安全和簡潔。
你可能還會覺得疑惑,我干嘛要這些寫,如果我非要給函數(shù)取一個名字,然后馬上調(diào)用呢?
額,其實我個人認(rèn)為這也是沒有問題的,但是你得費一番心思去給函數(shù)取名字,取 a,b,c,d 這樣的名字肯定是不好的。那么,我私以為,還不如干脆就用匿名函數(shù)算了,省得麻煩。
如果這部分知識你以前就不知道,那么我建議你把這篇文章多看幾遍,反正就是那么回事,沒什么大不了的。我當(dāng)初就是走了很多彎路,也沒有人教我,只有靠自己在那瞎摸索和各種百度,當(dāng)然,現(xiàn)在想想很簡單了。
我們的miniQuery的定義就放在這個自執(zhí)行函數(shù)里面,這樣一來,只要有人調(diào)用了這個js文件,就能調(diào)用miniQuery函數(shù)了。
當(dāng)然,你直接放在外面其實也沒事,因為反正就一個方法,而且這個方法本來就是要暴露出去的。
這邊為了說明自執(zhí)行函數(shù),就硬加進來了。
我們把miniQuery的定義丟進去。
比如,像這樣子的:
(function(){
var miniQuery = function(){
alert('Hello miniQuery!');
}
})();
我們嘗試在外面調(diào)用:
miniQuery();
很遺憾,調(diào)不到。
我們再回顧一下代碼:
(function(){
var miniQuery = function(){
alert('Hello miniQuery!');
}
})();
miniQuery();
原來,miniQuery是存在于一個閉包中的,它可以訪問到父級作用域的變量,但是反過來就不行,除非函數(shù)自己用 return 的方式將私有數(shù)據(jù)暴露出去。這些在之前的關(guān)于閉包的文章里面已經(jīng)解釋過了,這里不再贅述。
解決方法有很多,比如,最簡單的,我們直接把var去掉,這樣就會發(fā)生一次變量提升,miniQuery被升級為全局變量,掛在window對象上面。
(function(){
miniQuery = function(){
alert('Hello miniQuery!');
}
})();
miniQuery();
成了,簡單明了,干干凈凈。
雖然我覺得很有道理,但是我看別人的代碼,他們封裝自己的js庫的時候,幾乎沒有這樣做的,因此我們也采用一種大眾的做法。
即,我們把window作為參數(shù)傳進去,然后手動將miniQuery掛上去。
(function(win){
var miniQuery = function(){
alert('Hello miniQuery!');
}
win.miniQuery = miniQuery;
})(window);
miniQuery();
是不是也可以呢?
如果你覺得每次寫miniQuery太麻煩,那么我們可以給它換一個名字,比如 $
(function(win){
var miniQuery = function(){
alert('Hello miniQuery!');
}
win.$ = miniQuery;
})(window);
$();
這樣就差不多了。
4. miniQuery 包裹對象
我們先弄來一個測試用的網(wǎng)頁:
.wrap {
width:80px;
height:80px;
background:darkslateblue;
margin:20px;
border-radius: 2px;
}
<body>
<div class='boxes'>
<div id='box1' class='wrap'></div>
<div id='box2' class='wrap'></div>
<div id='box3' class='wrap'></div>
</div>
</body>
舉一個例子,現(xiàn)在我們要獲取id為box1的盒子,并把它的背景色改為紅色。
用js代碼,我們會這樣做:
var box2 = document.getElementById('box2');
box2.style.backgroundColor = 'red';
思路很清晰,分為簡單的兩步:
第一步:獲取dom對象。
第二部:設(shè)置其背景色為紅色。
同樣的,我們的 miniQuery 也要這么做,首先得獲取對象,然后進行操作。就好像你做飯,首先得有米面吧。所謂巧婦難為,無米之炊。
于是,我們有了下面的代碼:
var miniQuery = function(selector){
var miniQuery = document.getElementById(selector);
console.log(miniQuery);
}
selector 代表選擇器,它只是一個參數(shù)名字,參數(shù)列表的名稱是可以自己定義的。你寫 aaa , bbb , ccc 都沒問題,只要你愿意的話。
我以前經(jīng)??磩e人寫的代碼,參數(shù)里面有callback,現(xiàn)在我知道是回調(diào)函數(shù)的意思??墒俏乙郧安恢溃缓缶陀X得很困惑,作為一個英語比日語還差的js玩家,我感到很那個啥。
其實無所謂,只是一個名字而已,你寫什么都行,只要符合標(biāo)識符的命名規(guī)范就成。
總有人覺得,看到參數(shù)里邊寫了context(上下文),callback(回調(diào)函數(shù))這樣的詞匯,就覺得很困惑。
不要困惑啦,不要再驚恐啦,它就是一個名稱罷了!
。。。
額,扯遠了,繼續(xù)回來。
我們在外面調(diào)用miniQuery ~
window 上面掛的是 $ , 其實就是 miniQuery
$('box1');
運行結(jié)果
嗯,確實取到了呢。
接下里,我們給dom元素變更背景色為紅色。
var miniQuery = function(selector){
var miniQuery = document.getElementById(selector);
miniQuery.style.backgroundColor = 'red';
}
效果確實出來了。
可是呢,如果用戶過幾天又來個需求,說我要把box1的寬度變?yōu)橹暗膬杀叮阍趺崔k?
總不可能去修改源碼吧!
這時候,我們就可以考慮能不能通過一個什么辦法,我先用miniQuery把你傳進來的東西包裝成dom元素,保存起來返回給你,同時再給你返回一大堆方法,比如改變高度啊,添加背景色啊等等。那么,操作的就是之前保存的元素了。也就是你一開始希望操作的元素。
這是一個很好的想法,我們經(jīng)過代碼的重寫,最終產(chǎn)生了這樣的一個miniQuery函數(shù):
var miniQuery = function(selector){
var miniQuery = document.getElementById(selector);
return {
obj : miniQuery , //將dom元素保存起來,再返回給你
// ------------------------ css 相關(guān) ------------------------//
backgroundColor : function(color){
this.obj.style.backgroundColor = color;
}
}
}
win.$ = miniQuery;
})(window);
我們再調(diào)用一次,看看這回它給我們返回的是什么東東?
var $box = $('box1');
console.log($box);
可見,它給我們返回的是一個json對象,里面有 obj 變量和 backgroundColor 函數(shù)。這樣的好處就是極大的擴展了我們的miniQuery,你給我一個選擇器,我就包起來,然后不僅把它返回給你,而且還給你各種api方法!
于是我們就可以直接調(diào)用 backgroundColor 函數(shù)了。
var $box = $('box1');
$box.backgroundColor('red');
成了。
我們現(xiàn)在返回的,不是一個單純的dom元素,dom元素只是它的一部分??梢哉f,我們返回給用戶的是一個miniQuery對象!
篇幅關(guān)系,先介紹到這里吧。