最近看了兩本書《精通javascript》(csdn的編輯器出問題了版面只能這樣了大家見諒,url:http://www.verycd.com/topics/2753377/)和《javascript語言精粹》(url:http://www.verycd.com/topics/2762001/),讓我對javascript這個再熟悉不過的腳本語言有了新的認(rèn)識。javascript使用簡單形式多變這不禁讓開發(fā)人員放松對碼的控制,特別是現(xiàn)在javscript的有很多方便開發(fā)庫比如jQuery。但是在輕松愜意之時我們的代碼就變得換亂不堪、難于維護(hù)。大家可以現(xiàn)在打開你最近寫的網(wǎng)頁代碼,看看里面的javascript是不是充斥了$('#mainContain").height()或者getGridSelectIds()這樣的代碼。反正我的項(xiàng)目里是這樣的,現(xiàn)在我終于下定決心要改變這種局面了。我不想因?yàn)檎军c(diǎn)主體div的名稱發(fā)生了改變而使某一個頁面中的布局變得錯亂,也不想在一個頁面中直接調(diào)用另一個嵌入頁面的某個方法。并且這個嵌入頁面的方法還會去獲取外部頁面上某些元素的一些屬性,而且都是根據(jù)id來獲取來的。這實(shí)在令人很不爽,雖然開發(fā)容易了但是維護(hù)代價增加了。我采取的解決之道就是讓javascript面向?qū)ο蠡?。在javascript中實(shí)現(xiàn)面向?qū)ο筮@樣的文章已經(jīng)無數(shù)了我也沒必要在重復(fù)寫一篇。我只想強(qiáng)調(diào)一下oo的思想是很重要的武器,所以在這里只說說如何重構(gòu)我項(xiàng)目中的代碼的。也許這些問題你也遇到過,希望對你也有一點(diǎn)點(diǎn)啟發(fā)。
第一點(diǎn):使用匿名函數(shù)寫法封閉每一個頁面中的javascript代碼,這樣在頁面相互嵌入的情況下你不能直接使用其他頁面中定義的方法或變量。例如:
<script>
//頁面A
(function() {
var name = 'fh';
var sayHello = function() {
return name;
};
})();
//頁面B
(function() {
var name = 'lucifer';
alert(sayHello()); //sayHello is not defined
})();
</script>
第二點(diǎn):使用命名空間約束代碼的訪問。命名空間我們在c#或者java里已經(jīng)使用了很長時間了這東西能夠很好的防止重名和使代碼模塊化。下面給出的是我自己寫的一個命名空間實(shí)現(xiàn)
(function() {
String.prototype.s_trim = function() {
return this.replace(/^\s|\s+$/, '');
}
var namespaces = {};
namespaces.length = 0;
//相同命名空間不重復(fù)生成
var _existNamespace = function(nArr) {
var tmp = namespaces;
for(var i=0; i<nArr.length; ++i) {
var n = nArr[i];
if(!tmp[n]) {
return {flag: false};
}
else {
tmp = tmp[n];
}
}
return {flag: true, ns: tmp};
};
//命名空間
this.s_namespace = function(ns) {
var arr = ns.split('.');
var exist = _existNamespace(arr);
if(exist.flag) {
return exist.ns;
}
else {
for(var i=0; i<arr.length; ++i) {
var n = arr[i];
if(!namespaces[n]) {
if(namespaces.length && (i - 1) >= 0) {
namespaces[arr[i-1]][n] = {};
}
else {
namespaces[n] = {};
}
namespaces.length += 1;
}
}
return _existNamespace(arr).ns;
}
};
//導(dǎo)出命名空間
this.s_import = function(ns) {
var exist = _existNamespace(ns.split('.'));
if(exist.flag) {
return exist.ns;
}
else {
alert('找不到命名空間: ' + ns);
}
};
this.s_from = function(ns) {
var that = {};
var ns = s_import(ns);
that.s_import = function(expr) {
var names = [];
if(expr === '*') {
for(var k in ns) {
names.push(k);
}
}
else {
names = expr.split(',');
}
for(var i=0; i<names.length; ++i) {
var key = names[i].s_trim();
window[key] = ns[key];
}
};
return that;
};
})();
我這里有個命名約定:全局方法以s_開頭,下面看看命名空間的使用:
//頁面A
(function() {
var ns_a = s_namespace('A');
var name = 'fh';
ns_a.sayHello = function() {
return name;
};
ns_a.test = function() {
alert('test');
};
})();
//頁面B
(function() {
var ns_a = s_import('A');
var name = 'lucifer';
alert(ns_a.sayHello()); //sayHello is not defined
})();
//頁面C
(function() {
s_from('A').s_import('*');
alert(sayHello());
})();
//頁面D
(function() {
s_from('A').s_import('sayHello, test');
test();
})();
這里展現(xiàn)了命名空間的創(chuàng)建s_namespace('A')和兩種到處方式s_import與s_from().s_import.前一種導(dǎo)出方式只能到處一個具體的命名空間然后你使用這個命名空間的對象來訪問其中的成員。而第二種方式是指定導(dǎo)出該命名空間下的某些成員(*是全部,或者sayHello, test)到當(dāng)前作用域內(nèi),所以你可以直接使用sayHello或test。
第三點(diǎn):使用繼承體現(xiàn)來使javascript代碼更加模塊話??赡苁鞘艿缴厦嫣岬降膬杀緯挠绊懳也]有使用prototype方式來實(shí)現(xiàn)繼承,而是使用了函數(shù)化方式來實(shí)現(xiàn)的。我覺得這種方式看起來更容易理解也很好使用,比如實(shí)現(xiàn)私有變量、函數(shù),調(diào)用父類構(gòu)造函數(shù)和在覆寫父類函數(shù)的時候調(diào)用該父類方法。好了讓我們看看實(shí)現(xiàn)的代碼:
(function() {
//全站點(diǎn)命名空間
var ns_site = s_namespace('site');
//實(shí)現(xiàn)繼承
var extend = Function.prototype.extend = function() {
//例如Base.extend(),這時that為Base
var that = this;
//extend.caller就是子類型
that.subClass = extend.caller;
//構(gòu)造父類的實(shí)例對象
var instance = that.apply(that, arguments);
//父類中的方法元信息
var methods = {};
for(var k in instance) {
var v = instance[k];
if(v.constructor === Function) {
methods[k] = v;
}
}
//為了實(shí)現(xiàn)類似于base.BaseMethod這種oo常見需求
instance.superMethod = function(name) {
var args = [];
for(var i=1; i<arguments.length; ++i) {
args.push(arguments[i]);
}
return methods[name].apply(instance, args);
};
return instance;
};
//判斷一個類型是不是另一個類型的子類型
var isSubClass = Function.prototype.isSubClass = function(superClass) {
var that = this;
var subClass = that;
while(subClass) {
if(subClass === superClass) {
return true;
}
else {
subClass = subClass.superClass;
}
}
return false;
};
//基礎(chǔ)父類,類似于object,所有的子類都要繼承它
var Base = ns_site.Base = function() {
var that = {};
//對象實(shí)例的類型信息
var superClass = Base;
//因?yàn)槭褂胊pply方式調(diào)用所以this就是extend方法中的that
var subClass = this.subClass;
while(subClass) {
subClass.superClass = superClass;
//clsObj是實(shí)例的類型元信息
that.clsObj = subClass;
superClass = that.clsObj;
subClass = superClass.subClass;
}
//判斷一個對象是不是某一類型的實(shí)例
that.isInstance = function(cls) {
var clsObj = that.clsObj;
while(clsObj) {
if(clsObj === cls) {
return true;
}
else {
clsObj = clsObj.superClass;
}
}
return false;
};
return that;
};
})();
在這里先說明一下我比較討厭this關(guān)鍵字,這東西在使用的時候你要充分考慮好作用域上下文它不一定代表什么,所以我采取了that = {}這種創(chuàng)建對象的方式。下面看一下簡單的用例:
(function() {
s_from('site').s_import('*');
var Persion = function(name, age) {
var _name = name;
var _age = age;
var that = Base.extend();
that.getName = function() {
return _name;
};
return that;
};
var User = function(name, age, pwd) {
var _pwd = pwd;
var that = Persion.extend(name, age);
that.getName = function() {
return 'user\' name is ' + that.superMethod('getName');
};
return that;
}
var user = User('lucifer', 27, 'aa');
alert(user.getName());
alert(user.isInstance(Persion));
alert(User.isSubClass(Base));
})();
在上面的例子中你可以很自然的從User構(gòu)造函數(shù)中調(diào)用父類Persiond的構(gòu)造函數(shù)并且覆寫getName方法也很簡單。不過javascript并不是真正意義上的oo語言所以它還是沒辦法實(shí)現(xiàn)在父類中調(diào)用子類覆寫方法這個功能(當(dāng)然如果你有實(shí)現(xiàn)的方法請告知我,萬分感謝)。最讓讓我們看看事件機(jī)制的實(shí)現(xiàn):
//事件
var Event = ns_site.Event = function(obj) {
var _events = {};
var that = {};
that.on = function(type, fn, params) {
var handle = { fn: fn, params: params };
if(_events[type]) {
_events[type].push(handle);
}
else {
_events[type] = [handle];
}
return that;
};
that.fire = function(type) {
if(_events[type]) {
var arr = _events[type];
for(var i=0; i<arr.length; ++i) {
var handle = arr[i];
var fn = handle.fn;
if(typeof(fn) === 'string') {
fn = obj[fn];
}
if(fn) {
fn.apply(obj, handle.params || [type]);
}
}
}
return that;
};
return that;
};
它的實(shí)現(xiàn)很簡單就是記錄一個類中的事件,并只能在類中觸發(fā)事件這符合事件的定義。并因?yàn)槭褂胊pply方式調(diào)用,所以你可以在事件處理函數(shù)中通過this來使用該類的實(shí)例它等同于我們平時使用的sender。事例代碼:
(function() {
s_from('site').s_import('*');
var Form = function(id) {
var that = Base.extend();
var _id = id;
var _event = Event(that);
that.getId = function() {
return _id;
};
that.loadEvent = function(fn) {
_event.on('load', fn);
}
that.show = function() {
_event.fire('load');
}
that.myLoad = function() {
alert(_id + ' is loading.');
}
_event.on('load', 'myLoad');
return that;
};
var form1 = Form('form1');
form1.loadEvent(function() {
alert('hello ' + this.getId());
});
form1.show();
})();
好了有了這些基礎(chǔ)工具我就可以對我的項(xiàng)目中的代碼進(jìn)行重構(gòu)了最為主要的工作就是把一些公用的組件封裝為類似WinForm中的組件。比如jqGrid使我們項(xiàng)目里的一個核心組件并對它做出了一些定制,我就先定義了一個基礎(chǔ)類Workspace該類中有g(shù)etName,init,load,show等方法,然后在封裝jqGrid作為一個Grid控件。這樣在頁面中就可以先添加一個Worksapce然后再往上放Grid等控件,Workspace在show的時候它的子類也就可以init,load后并show出來了。
代碼看著不舒服的下源碼吧(url: http://download.csdn.net/source/1768189),并推薦firefox和firbug插件來調(diào)試js。
如對本文有疑問,請?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會為你解答!! 點(diǎn)擊進(jìn)入論壇