最近看了两本书《精通javascript》(csdn的编辑器出问题了版面只能这样了大家见谅,url:http://www.verycd.com/topics/2753377/)和《javascript语言精粹》(url:http://www.verycd.com/topics/2762001/),让我对javascript这个再熟悉不过的脚本语言有了新的认识。javascript使用简单形式多变这不禁让开发人员放松对码的控制,特别是现在javscript的有很多方便开发库比如jQuery。但是在轻松惬意之时我们的代码就变得换乱不堪、难于维护。大家可以现在打开你最近写的网页代码,看看里面的javascript是不是充斥了$('#mainContain").height()或者getGridSelectIds()这样的代码。反正我的项目里是这样的,现在我终于下定决心要改变这种局面了。我不想因为站点主体div的名称发生了改变而使某一个页面中的布局变得错乱,也不想在一个页面中直接调用另一个嵌入页面的某个方法。并且这个嵌入页面的方法还会去获取外部页面上某些元素的一些属性,而且都是根据id来获取来的。这实在令人很不爽,虽然开发容易了但是维护代价增加了。我采取的解决之道就是让javascript面向对象化。在javascript中实现面向对象这样的文章已经无数了我也没必要在重复写一篇。我只想强调一下oo的思想是很重要的武器,所以在这里只说说如何重构我项目中的代码的。也许这些问题你也遇到过,希望对你也有一点点启发。
第一点:使用匿名函数写法封闭每一个页面中的javascript代码,这样在页面相互嵌入的情况下你不能直接使用其他页面中定义的方法或变量。例如:
<script>
//页面A
(function() {
var name = 'fh';
var sayHello = function() {
return name;
};
})();
//页面B
(function() {
var name = 'lucifer';
alert(sayHello()); //sayHello is not defined
})();
</script>
第二点:使用命名空间约束代码的访问。命名空间我们在c#或者java里已经使用了很长时间了这东西能够很好的防止重名和使代码模块化。下面给出的是我自己写的一个命名空间实现
(function() {
String.prototype.s_trim = function() {
return this.replace(/^\s|\s+$/, '');
}
var namespaces = {};
namespaces.length = 0;
//相同命名空间不重复生成
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;
}
};
//导出命名空间
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();
})();
这里展现了命名空间的创建s_namespace('A')和两种到处方式s_import与s_from().s_import.前一种导出方式只能到处一个具体的命名空间然后你使用这个命名空间的对象来访问其中的成员。而第二种方式是指定导出该命名空间下的某些成员(*是全部,或者sayHello, test)到当前作用域内,所以你可以直接使用sayHello或test。
第三点:使用继承体现来使javascript代码更加模块话。可能是受到上面提到的两本书的影响我并没有使用prototype方式来实现继承,而是使用了函数化方式来实现的。我觉得这种方式看起来更容易理解也很好使用,比如实现私有变量、函数,调用父类构造函数和在覆写父类函数的时候调用该父类方法。好了让我们看看实现的代码:
(function() {
//全站点命名空间
var ns_site = s_namespace('site');
//实现继承
var extend = Function.prototype.extend = function() {
//例如Base.extend(),这时that为Base
var that = this;
//extend.caller就是子类型
that.subClass = extend.caller;
//构造父类的实例对象
var instance = that.apply(that, arguments);
//父类中的方法元信息
var methods = {};
for(var k in instance) {
var v = instance[k];
if(v.constructor === Function) {
methods[k] = v;
}
}
//为了实现类似于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;
};
//基础父类,类似于object,所有的子类都要继承它
var Base = ns_site.Base = function() {
var that = {};
//对象实例的类型信息
var superClass = Base;
//因为使用apply方式调用所以this就是extend方法中的that
var subClass = this.subClass;
while(subClass) {
subClass.superClass = superClass;
//clsObj是实例的类型元信息
that.clsObj = subClass;
superClass = that.clsObj;
subClass = superClass.subClass;
}
//判断一个对象是不是某一类型的实例
that.isInstance = function(cls) {
var clsObj = that.clsObj;
while(clsObj) {
if(clsObj === cls) {
return true;
}
else {
clsObj = clsObj.superClass;
}
}
return false;
};
return that;
};
})();
在这里先说明一下我比较讨厌this关键字,这东西在使用的时候你要充分考虑好作用域上下文它不一定代表什么,所以我采取了that = {}这种创建对象的方式。下面看一下简单的用例:
(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构造函数中调用父类Persiond的构造函数并且覆写getName方法也很简单。不过javascript并不是真正意义上的oo语言所以它还是没办法实现在父类中调用子类覆写方法这个功能(当然如果你有实现的方法请告知我,万分感谢)。最让让我们看看事件机制的实现:
//事件
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;
};
它的实现很简单就是记录一个类中的事件,并只能在类中触发事件这符合事件的定义。并因为使用apply方式调用,所以你可以在事件处理函数中通过this来使用该类的实例它等同于我们平时使用的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();
})();
好了有了这些基础工具我就可以对我的项目中的代码进行重构了最为主要的工作就是把一些公用的组件封装为类似WinForm中的组件。比如jqGrid使我们项目里的一个核心组件并对它做出了一些定制,我就先定义了一个基础类Workspace该类中有getName,init,load,show等方法,然后在封装jqGrid作为一个Grid控件。这样在页面中就可以先添加一个Worksapce然后再往上放Grid等控件,Workspace在show的时候它的子类也就可以init,load后并show出来了。
代码看着不舒服的下源码吧(url: http://download.csdn.net/source/1768189),并推荐firefox和firbug插件来调试js。
如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛