寫做不易,轉載請註明出處,謝謝。javascript
文章類別:Javascript基礎(面向初學者)css
在以前的章節中,咱們已經不依賴jQuery,單純地用JavaScript封裝了不少方法,這個時候,你必定會想,這些常用的方法能不能單獨整理成一個js文件呢?html
固然能夠,封裝原本就是幹這個用的。放在一個單獨js文件裏當然不錯,其實咱們也能夠單獨整一個js類庫,一方面能夠鍛鍊一下本身封裝方法的能力,另外一方面,也能夠將本身學到的東西作一個整理。java
出於這個目的,本文將介紹如何封裝一個簡單的js類庫。(固然,只是開一個頭,熟悉一下js基礎而已。實際使用的話我感受徹底沒有必要,由於jQuery已經很強大了,直接使用第三方的就能夠)node
所謂的js庫,其實也就是一個js文件,我思前想後,決定取個名字叫「miniQuery」,是否是山寨的味道十足呢?哈,請不要在乎這些小細節。ajax
大概的設計以下:django
擴展方法的兼容(主要寫一些兼容的擴展方法,好比 forEach 方法等)json
工具包定義 (就是以前封裝的utils.js,咱們的miniQuery須要依賴這個工具包,爲了方便,就乾脆寫在一個文件裏面了。)api
miniQuery定義數組
// ------------------------ 基本擴展, 字符串,數組等---------------------------------// 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) { // 遍歷數組,在每一項上調用回調函數,這裏使用原生方法驗證數組。 if (Object.prototype.toString.call(this) === "[object Array]") { var i,len; //遍歷該數組全部的元素 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內置對象的api作了一些兼容性補充,目前還不完善,只有寥寥幾個方法。固然,若是你不考慮IE678的話,那麼基本上不須要這一部分了。
定義完成後當即調用。
extend_base();
// ------------------------ 工具包---------------------------------// 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相關 * */ isDom : ( typeof HTMLElement === 'object' ) ? function(obj){ return obj instanceof HTMLElement; } : function(obj){ return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'; } , /** 數組相關 * */ isArray : function(obj){ return obj.constructor.toString().indexOf('Array') != -1; } }
終於到miniQuery了,在寫代碼以前,先簡單說一下自執行函數。
可能你在不少書上,或者下載的源碼裏面,常常會看到這樣的代碼:
(function(){ })();
這樣子你或許以爲很奇怪,沒事,咱們一塊兒來分析。
在js中,你若是把函數看做一個數據類型,和其餘語言中的 Integer, Float , String等等同樣,就會理解不少事情了。固然,其實在js中,函數自己就是一個對象,否則的話就不會出現call方法了。由於只有對象才能夠調用方法嘛。不過,大部分狀況下,你把函數理解爲數據類型就能夠了。
匿名函數:
function(){ }
這是一個函數,由於沒有函數名,因此是一個匿名函數。你定義了它,若是接下來你不想經過函數調用的方式來執行它,那麼是否是能夠直接給它打一個括號來執行呢?
像這樣:
function(){ }();
不過,由於js語法的關係,這樣子是不能執行的,你須要用一對圓括號來包一下:
(
function(){
alert("你好!"); }() ) ;
這樣就能夠了,下面是另外一種寫法:
(
function(){
alert("你好!"); } )();
這樣也能夠,這種寫法會更多一點。它的意思就是說,我不關心你這個函數叫什麼名字,反正你在被定義的時候就要給我執行,這就是所謂的自執行函數。
好,問題來了,怎麼加參數呢?
之前咱們習慣於這麼寫:
function say(str){ alert(str); } say("你好!");
依葫蘆畫瓢
(
function(str){ alert(str); } )("你好!");
OK了。
是否是同樣的意思呢?
沒啥區別,之前怎麼作,如今還怎麼作,無非就是一個函數傳參的事情罷了。
咱們將圓括號的位置調整一下
( function(str){ alert(str); } )("你好!");
這樣差很少就是最終的版本了,我記得初學js的時候,看這種代碼很吃力,好像在看外星語言同樣,後來看多了也就習慣了。
自執行函數就是這麼一回事,沒什麼大不了的。
有了上面的解釋,之後若是你再遇到這種寫法,就 so easy 啦。
因此,不要再恐懼了,它就是這麼回事,沒什麼大不了的,我這麼後知後覺的人都能寫,你也能夠。我花了半年的時間纔看明白,我相信你如今只須要幾分鐘。個人意思是,若是你以前不知道這些的話。
那麼,何時用自執行函數呢?
當你以爲某個函數只須要執行一次,並且不須要在其餘地方調用的時候,就用。
你可能會問了,我幹嗎要這樣寫啊,反正就執行一次,我直接把實現代碼寫在外面不就好了?
緣由很簡單,由於那樣的話,你定義的變量就會是全局的,而通常來講咱們設計的原則是儘可能不要使用全局變量。
而採用這種方式,咱們就造成了一個匿名函數,函數的定義又會造成閉包,因此比較安全和簡潔。
你可能還會以爲疑惑,我幹嗎要這些寫,若是我非要給函數取一個名字,而後立刻調用呢?
額,其實我我的認爲這也是沒有問題的,可是你得費一番心思去給函數取名字,取 a,b,c,d 這樣的名字確定是很差的。那麼,我私覺得,還不如干脆就用匿名函數算了,免得麻煩。
若是這部分知識你之前就不知道,那麼我建議你把這篇文章多看幾遍,反正就是那麼回事,沒什麼大不了的。我當初就是走了不少彎路,也沒有人教我,只有靠本身在那瞎摸索和各類百度,固然,如今想一想很簡單了。
咱們的miniQuery的定義就放在這個自執行函數裏面,這樣一來,只要有人調用了這個js文件,就能調用miniQuery函數了。
固然,你直接放在外面其實也沒事,由於反正就一個方法,並且這個方法原本就是要暴露出去的。
這邊爲了說明自執行函數,就硬加進來了。
咱們把miniQuery的定義丟進去。
好比,像這樣子的:
(function(){ var miniQuery = function(){ alert('Hello miniQuery!'); } })();
咱們嘗試在外面調用:
miniQuery();
很遺憾,調不到。
咱們再回顧一下代碼:
(function(){ var miniQuery = function(){ alert('Hello miniQuery!'); } })(); miniQuery();
原來,miniQuery是存在於一個閉包中的,它能夠訪問到父級做用域的變量,可是反過來就不行,除非函數本身用 return 的方式將私有數據暴露出去。這些在以前的關於閉包的文章裏面已經解釋過了,這裏再也不贅述。
解決方法有不少,好比,最簡單的,咱們直接把var去掉,這樣就會發生一次變量提高,miniQuery被升級爲全局變量,掛在window對象上面。
(function(){ miniQuery = function(){ alert('Hello miniQuery!'); } })(); miniQuery();
成了,簡單明瞭,乾乾淨淨。
雖然我以爲頗有道理,可是我看別人的代碼,他們封裝本身的js庫的時候,幾乎沒有這樣作的,所以咱們也採用一種大衆的作法。
即,咱們把window做爲參數傳進去,而後手動將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); $();
這樣就差很少了。
咱們先弄來一個測試用的網頁:
.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>
舉一個例子,如今咱們要獲取id爲box1的盒子,並把它的背景色改成紅色。
用js代碼,咱們會這樣作:
var box2 = document.getElementById('box2'); box2.style.backgroundColor = 'red';
思路很清晰,分爲簡單的兩步:
第一步:獲取dom對象。
第二部:設置其背景色爲紅色。
一樣的,咱們的 miniQuery 也要這麼作,首先得獲取對象,而後進行操做。就好像你作飯,首先得有米麪吧。所謂巧婦難爲,無米之炊。
因而,咱們有了下面的代碼:
var miniQuery = function(selector){ var miniQuery = document.getElementById(selector); console.log(miniQuery); }
selector 表明選擇器,它只是一個參數名字,參數列表的名稱是能夠本身定義的。你寫 aaa , bbb , ccc 都沒問題,只要你願意的話。
我之前常常看別人寫的代碼,參數裏面有callback,如今我知道是回調函數的意思。但是我之前不知道,而後就以爲很困惑,做爲一個英語比日語還差的js玩家,我感到很那個啥。
其實無所謂,只是一個名字而已,你寫什麼都行,只要符合標識符的命名規範就成。
總有人以爲,看到參數裏邊寫了context(上下文),callback(回調函數)這樣的詞彙,就以爲很困惑。
不要困惑啦,不要再驚恐啦,它就是一個名稱罷了!
。。。
額,扯遠了,繼續回來。
咱們在外面調用miniQuery ~
window 上面掛的是 $ , 其實就是 miniQuery
$('box1');
運行結果
嗯,確實取到了呢。
接下里,咱們給dom元素變動背景色爲紅色。
var miniQuery = function(selector){ var miniQuery = document.getElementById(selector); miniQuery.style.backgroundColor = 'red'; }
效果確實出來了。
但是呢,若是用戶過幾天又來個需求,說我要把box1的寬度變爲以前的兩倍,你怎麼辦?
總不可能去修改源碼吧!
這時候,咱們就能夠考慮能不能經過一個什麼辦法,我先用miniQuery把你傳進來的東西包裝成dom元素,保存起來返回給你,同時再給你返回一大堆方法,好比改變高度啊,添加背景色啊等等。那麼,操做的就是以前保存的元素了。也就是你一開始但願操做的元素。
這是一個很好的想法,咱們通過代碼的重寫,最終產生了這樣的一個miniQuery函數:
var miniQuery = function(selector){ var miniQuery = document.getElementById(selector); return { obj : miniQuery , //將dom元素保存起來,再返回給你 // ------------------------ css 相關 ------------------------// backgroundColor : function(color){ this.obj.style.backgroundColor = color; } } } win.$ = miniQuery; })(window);
咱們再調用一次,看看這回它給咱們返回的是什麼東東?
var $box = $('box1'); console.log($box);
可見,它給咱們返回的是一個json對象,裏面有 obj 變量和 backgroundColor 函數。這樣的好處就是極大的擴展了咱們的miniQuery,你給我一個選擇器,我就包起來,而後不只把它返回給你,並且還給你各類api方法!
因而咱們就能夠直接調用 backgroundColor 函數了。
var $box = $('box1'); $box.backgroundColor('red');
成了。
咱們如今返回的,不是一個單純的dom元素,dom元素只是它的一部分。能夠說,咱們返回給用戶的是一個miniQuery對象!
通過改進,我已經陸陸續續地給miniQuery添加了不少方法,大部分是模擬的jQuery:
順便弄了兩個小型的組件,一個是按鈕,另外一個是簡單的數據列表。
按鈕使用:
<link rel="stylesheet" type="text/css" href="css/mui.css"/>
<div class='box'></div>
var $box = $('.box').eq(0); $box.linkbutton();
按鈕的樣式就出來了,而後咱們來設置按鈕的屬性。
var $box = $('.box').eq(0); $box.linkbutton({ text : '保存' , click : function(){ alert('保存成功!'); } });
按鈕的大小也自動變大了。
對應的css:
mui.css
.linkbutton { padding: .4em .9em; /*em的好處就是隨着父元素的字體大小而變化,當該元素的字體變化時,會自適應*/ border: 1px solid rgba(0,0,0,.1); background-color: #ac0; border-radius: .2em; box-shadow: 0 1px 5px rgba(0,0,0,.5); color: #fff; text-shadow: 0 -.06em .24em rgba(0,0,0,.5); /*將陰影設置爲半透明,就無所謂底色了,都能很好地適應*/ font-size: 130%; line-height: 1.5; /*行高是字號的1.5倍*/ display:inline-block; cursor:pointer; font-family: "微軟雅黑"; }
數據列表簡單演示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="css/mui.css"/> <script type="text/javascript" src="js/miniQuery.js"></script> </head> <body> <a id='btn0'></a> <div id='grid0'></div> </body> <script> $('#btn0').linkbutton({ text : '測試' , click : function(){ if(grid0.getSize() < 1){ alert('請選擇一條數據!'); return; } alert('您選擇的是' + JSON.stringify(grid0.getSelected())); } }); var grid0 = mui.get('#grid0').dataGrid({ header : [ {name:'ID' , width:10 , type : 'checkColumn' } , {name:'標題' , type : 'column' , field : 'title'} , {name:'分類' , type : 'column' , field : 'type'} , {name:'做者' , type : 'column' , field : 'author'} , {name:'時間' , type : 'column' , field : 'time'} , ] , }); grid0.load([ {title : '111' , type : 'A' , author : '張三' , time : '2015'} , {title : '222' , type : 'B' , author : '李四' , time : '2015'} , {title : '333' , type : 'C' , author : '王五' , time : '2015'} , {title : '444' , type : 'D' , author : '趙六' , time : '2015'} , ]); </script> </html>
固然,好多組件都還不夠完善,我主要也是本身嘗試一下,不過並不打算再拓展了。
本身作個小類庫主要用於學習,之後仍是用jQuery吧。
附錄A
"use strict"; /** * miniQuery 和 工具類庫 * 版本 1.1 (修正了一部分Bug,增長了一些方法) * 做者:剽悍一小兔 */ // ------------------------ 基本擴展, 字符串,數組等---------------------------------// 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) { // 遍歷數組,在每一項上調用回調函數,這裏使用原生方法驗證數組。 if (Object.prototype.toString.call(this) === "[object Array]") { var i,len; //遍歷該數組全部的元素 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(); // ------------------------ 工具包---------------------------------// 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相關 * */ isDom : ( typeof HTMLElement === 'object' ) ? function(obj){ return obj instanceof HTMLElement; } : function(obj){ return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'; } , /** 數組相關 * */ isArray : function(obj){ return obj.constructor.toString().indexOf('Array') != -1; } } // ------------------------ miniQuery.js ---------------------------------// ;(function(win){ var miniQuery = function(selector){ var miniQuery = null; var length = 0; var children = []; if(!selector) return; /** 1. 傳入的是id * */ if(selector.toString().indexOf('#') != -1) { selector = selector.replace('#',''); miniQuery = document.getElementById(selector); } /** 2. 傳入的是class * */ else if(selector.toString().indexOf('.') != -1){ selector = selector.replace('.',''); miniQuery = document.getElementsByClassName(selector); } /** 3. 傳入的是dom元素 * */ else if(utils.isDom(selector)){ miniQuery = selector; } /** 4. 傳入的是標籤 * */ else if(typeof selector === 'string'){ miniQuery = document.getElementsByTagName(selector); return miniQuery; } if(!miniQuery) return; //若是本類庫包裝不了,就返回 if(miniQuery.length){ //若是是一個類數組元素的話,就獲取他的長度 length = miniQuery.length; }else{ length = 1; //這種狀況,說明成功包裹了元素,可是該元素仍是存在的,就將長度設定爲1 } children = miniQuery.children; //取得全部的孩子節點 return { /** 屬性區 */ obj : miniQuery, //返回的dom元素 index : 0 , //默認的角標(假如 miniquery 是一個類數組的話) length : length, //元素的個數(假如 miniquery 是一個類數組的話) children : children,//全部孩子節點 /** 方法區 */ // ------------------------ dom 相關 ---------------------------------// /**獲取dom對象自己,返回純粹的dom元素,而非miniQuery元素*/ getObj : function(){ return this.obj; } , /**獲取元素的長度*/ size : function(){ return this.length; } , /** 假如 miniquery 是一個類數組的話,用於返回其中一個元素 */ eq : function(index){ if(length > 0) { return $(this.obj[index]); //eq返回的仍是miniQuery對象 }else{ return null; } } , /** 得到第一個匹配元素 */ first : function(){ return $(this.obj[0]); } , /** 得到最後一個匹配元素 */ last : function(){ return $(this.obj[this.length - 1]); } , /** 得到最後一個匹配元素 */ getChildren : function(){ return this.obj.children; } , /** 得到某一個孩子節點 */ getChild : function(i){ return $(this.children[i]); } , /** 得到父節點 */ getParent : function(){ return $(this.obj.parentElement); } , /** 得到上一個節點 */ previous : function(){ var parent = this.getParent(); var children = parent.children; for(var i = 0; i < children.length; i++){ if(this.obj == children[i]) { return $(children[i - 1]); } } return null; } , /** 得到下一個節點 */ next : function(){ var parent = this.getParent(); var children = parent.children; for(var i = 0; i < children.length; i++){ if(this.obj == children[i]) { return $(children[i + 1]); } } return null; } , findClassDom :