首先附上jquery組件開發的網站:http://www.poluoluo.com/jzxy/201406/277886.html
jquery組件開發保證chainability,通過返回this.each();
jQuery.fn.test2= function(){
this.css("background","#ff0");//這里面的this為jquery對象,而不是dom對象
return this.each(function(){ //遍歷匹配的元素,此處的this表示為jquery對象,而不是dom對象
alert("this"+this+this.innerHTML); //提示當前對象的dom節點名稱,這里的this關鍵字都指向一個不同的DOM元素(每次都是一個不同的匹配元素)。
});
};
this.css(),this.each()里面的this為jquery對象,但是alert里面this為dom對象.
為什么要return this.each()對象,所以這樣就可以繼續鏈式操作了。
js組件寫法
參考鏈接:http://blog.csdn.net/bingqingsuimeng/article/details/44451481
組件寫的不多,使勁回憶以前用到的組件,完全用js開發的組件,主要有兩部分,tag名稱定義和組件api,具體的實現過程不記得了。
先給出一個計算輸入字符數組件的原始寫法:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>test</title> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script> $(function() { var input = $('#J_input'); //用來獲取字數 function getNum(){ return input.val().length; } //渲染元素 function render(){ var num = getNum(); //沒有字數的容器就新建一個 if ($('#J_input_count').length == 0) { input.after('<span id="J_input_count"></span>'); }; $('#J_input_count').html(num+'個字'); } //監聽事件 input.on('keyup',function(){ render(); }); //初始化,第一次渲染 render(); }) </script> </head> <body> <input type="text" id="J_input"/> </body> </html>
網上查了js寫組件的過程,在結合自己的實踐工,主要使用了三種寫組件的方式:
1.作用域隔離
使用對象字面量定義一個對象,調用對象的函數方法;
var textCount = { input:null, init:function(config){ this.input = $(config.id); this.bind(); //這邊范圍對應的對象,可以實現鏈式調用 return this; }, bind:function(){ var self = this; this.input.on('keyup',function(){ self.render(); }); }, getNum:function(){ return this.input.val().length; }, //渲染元素 render:function(){ var num = this.getNum(); if ($('#J_input_count').length == 0) { this.input.after('<span id="J_input_count"></span>'); }; $('#J_input_count').html(num+'個字'); } } $(function() { //在domready后調用 textCount.init({id:'#J_input'}).render(); })
這樣寫的優點是:所有的功能都在一個變量下面。代碼更清晰,並且有統一的入口調用方法
缺點:沒有私有的概念,所有的方法都是公開的,例如getNum和bind方法應該是私有的方法,但是其他代碼可以訪問和更改他們,當代碼量特別特別多的時候,很容易出現變量重復,或被修改的問題。
為了私有化方法,出現的閉包的寫法。
2.閉包寫法
var TextCount = (function(){ //私有方法,外面將訪問不到 var _bind = function(that){ that.input.on('keyup',function(){ that.render(); }); } var _getNum = function(that){ return that.input.val().length; } var TextCountFun = function(config){ } TextCountFun.prototype.init = function(config) { this.input = $(config.id); _bind(this); return this; }; TextCountFun.prototype.render = function() { var num = _getNum(this); if ($('#J_input_count').length == 0) { this.input.after('<span id="J_input_count"></span>'); }; $('#J_input_count').html(num+'個字'); }; //返回構造函數 return TextCountFun; })(); $(function() { new TextCount().init({id:'#J_input'}).render(); })
這種寫法把所有的東西都放在一個自動執行的閉包模塊中,所以不受外界的影響,並且只對外返回了TextCountFun的構造函數,生成的對象只能訪問到init,render方法。這種寫法已經滿足絕大多數的需求了。事實上大部分的jQuery插件都是這種寫法。
3.面向對象方式
上面的寫法已經可以滿足大部分需求,但是當一個頁面特別復雜,我們需要做一套組件。僅僅用這個就不行了。首先的問題就是,這種寫法太靈活了,寫單個組件還可以。如果我們需要做一套風格相近的組件,而且是多個人同時在寫。那真的是噩夢。
在編程的圈子里,面向對象一直是被認為最佳的編寫代碼方式。比如Java,就是因為把面向對象發揮到了極致,所以多個人寫出來的代碼都很接近,維護也很方便。但是很不幸的是,javascript不支持class類的定義。但是我們可以模擬。
先實現一個簡單的js類,作為base類。
var Class = (function() { var _mix = function(r, s) { for (var p in s) { if (s.hasOwnProperty(p)) { r[p] = s[p] } } } var _extend = function() { //開關 用來使生成原型時,不調用真正的構成流程init this.initPrototype = true var prototype = new this() this.initPrototype = false var items = arguments.slice() || [] var item //支持混入多個屬性,並且支持{}也支持 Function while (item = items.shift()) { _mix(prototype, item.prototype || item) } // 這邊是返回的類,其實就是我們返回的子類 function SubClass() { if (!SubClass.initPrototype && this.init) this.init.apply(this, arguments)//調用init真正的構造函數 } // 賦值原型鏈,完成繼承 SubClass.prototype = prototype // 改變constructor引用 SubClass.prototype.constructor = SubClass // 為子類也添加extend方法 SubClass.extend = _extend return SubClass } //超級父類 var Class = function() {} //為超級父類添加extend方法 Class.extend = _extend })()
基類的使用方式為:
/繼承超級父類,生成個子類Animal,並且混入一些方法。這些方法會到Animal的原型上。 //另外這邊不僅支持混入{},還支持混入Function var Animal = Class.extend({ init:function(opts){ this.msg = opts.msg this.type = "animal" }, say:function(){ alert(this.msg+":i am a "+this.type) } }) //繼承Animal,並且混入一些方法 var Dog = Animal.extend({ init:function(opts){ //並未實現super方法,直接簡單使用父類原型調用即可 Animal.prototype.init.call(this,opts) //修改了type類型 this.type = "dog" } }) //new Animal({msg:'hello'}).say() new Dog({msg:'hi'}).say()
其實就是重新覆蓋或者定義了基類中的抽象出的方法。
本文實例請參考原文鏈接,
抽象出base
可以看到,我們的組件有些方法,是大部分組件都會有的。
- 比如init用來初始化屬性。
- 比如render用來處理渲染的邏輯。
- 比如bind用來處理事件的綁定。
抽象出這三個方法,都按照這個約定寫,開發大規模組件庫就變得更加規范,相互之間配合也更容易。
事實上,這邊的init,bind,render就已經有了點生命周期的影子,但凡是組件都會具有這幾個階段,初始化,綁定事件,以及渲染。當然這邊還可以加一個destroy銷毀的方法,用來清理現場。
在組件開發中,引入事件機制 ,以便向外部報出組件當前狀態。
觀察者模式
想象一下base是個機器人會說話,他會一直監聽輸入的字數並且匯報出去(通知)。而你可以把耳朵湊上去,聽着他的匯報(監聽)。發現字數超過5個字了,你就做些操作。
//輔組函數,獲取數組里某個元素的索引 index var _indexOf = function(array,key){ if (array === null) return -1 var i = 0, length = array.length for (; i < length; i++) if (array[i] === item) return i return -1 } var Event = Class.extend({ //添加監聽 on:function(key,listener){ //this.__events存儲所有的處理函數 if (!this.__events) { this.__events = {} } if (!this.__events[key]) { this.__events[key] = [] } if (_indexOf(this.__events,listener) === -1 && typeof listener === 'function') { this.__events[key].push(listener) } return this }, //觸發一個事件,也就是通知 fire:function(key){ if (!this.__events || !this.__events[key]) return var args = Array.prototype.slice.call(arguments, 1) || [] var listeners = this.__events[key] var i = 0 var l = listeners.length for (i; i < l; i++) { listeners[i].apply(this,args) } return this }, //取消監聽 off:function(key,listener){ if (!key && !listener) { this.__events = {} } //不傳監聽函數,就去掉當前key下面的所有的監聽函數 if (key && !listener) { delete this.__events[key] } if (key && listener) { var listeners = this.__events[key] var index = _indexOf(listeners, listener) (index > -1) && listeners.splice(index, 1) } return this; } }) var a = new Event() //添加監聽 test事件 a.on('test',function(msg){ 