题目有点大了-.-。
好久没写博客了,这段时间忽然找不到主题了。最近刚刚给自己重构了一下前端表单的功能,通过各处借鉴,并利用了面向对象的方式,有一点点代码,所以拿出来分享一下。
javascript面向对象的方式,我也是通过博客园学了一些,如果你还不是很清楚,可以先看了解一下javascript的Function,Function.prototype。
在写之前,确定了一个主要思路,先写测试。为了图简便,先写了一个简单的单元测试功能,而后开始实现了名称空间,类,类的继承等几项功能。
单元测试功能
主要包括一个测试方法:
test(待测试方法,测试结果)
test(待测试方法,测试结果方法)
例:
function getSum(a,b){
return a + b;
}
test("getSum",getSum(1,2) == 3);
test("getSum",function(msgWrite){
msgWrite("1+2=?");
return getSum(1,2) == 3
});
实现很简单,先贴上代码,各位看官有兴趣自己看咯。
(function (window, body) { var trp = window.document.createElement("div"); if (body) { body.appendChild(trp); } else { window.addEventListener("load", function () { window.document.body.appendChild(trp); }); } function writeResult(panel, msg) { var result = window.document.createElement("div"); result.innerHTML = encodeHTML(msg); panel.appendChild(result); } window.test = function (item, arg) { var itemPanel = window.document.createElement("div"); itemPanel.innerHTML = "<p>" + encodeHTML(item) + "</p>"; trp.appendChild(itemPanel); var msgPanel = window.document.createElement("div"); itemPanel.appendChild(msgPanel); if (typeof (arg) == "boolean") { } else if (typeof (arg) == "function") { try { arg = arg(function (msg) { writeResult(msgPanel, msg); }); } catch (e) { arg = false; writeResult(msgPanel, e); } } writeResult(msgPanel, arg ? "成功" : "失败"); if (arg) { itemPanel.style["background"] = "#0f0"; } else { itemPanel.style["background"] = "#f00"; } }; })(window, window.document.body);
将testcore.js和 功能实现js 以及单元测试js引入到一个html中,打开html即可看到单元测试结果。红色为失败,绿色为成功。
名称空间,类,类的继承的单元测试
写了单元测试,其实就代表了我们本次的需求。下面说下本次的需求。
名称空间
能够注册名称空间,能在该空间下添加类,各空间下可以注册名称相同的类。
test("框架注册", !!wod && !!wod.CLS);
wod为本身核心库的名称空间
CLS为当前的所有类的基类,相当于.net的object。
test("注册名称空间", !!wod.CLS.getNS("mini.ui") && !!mini.ui && wod.CLS.getNS("mini.ui") == mini.ui);
类,类的继承
CLS同时提供核心方法
getNS("名称空间"),如果不存在名称空间,则注册并返回名称空间对象,如果存在,则返回该对象
由于最近项目上用的miniui,所以mini.ui躺枪了
test("注册类", !!wod.CLS.getClass("mini.ui.TextBox") && !!mini.ui.TextBox); mini.ui.TextBox.prototype.getText = function () { return "abc"; }; test("继承类", !!wod.CLS.getClass("mini.ui.SubTextBox", "mini.ui.TextBox") && !!mini.ui.SubTextBox
&& !!new mini.ui.SubTextBox() && mini.ui.SubTextBox.isSubclassOf(mini.ui.TextBox)); test("继承类-继承方法验证1", function (msgWrite) { var sub = new mini.ui.SubTextBox(); return sub.getText && sub.getText() == "abc"; }); test("继承类-继承方法验证2", function (msgWrite) { var subCLS = wod.CLS.getClass({ getText: function () { return "h" + this.parent(); }, hh: function () { return this.getText(); } }, "mini.ui.SubTextBox1", "mini.ui.SubTextBox"); var sub = new subCLS(); return sub.getText && sub.getText() == "habc" && sub.hh; });
重载了四个getClass方法:
1、getClass(className),注册一个className类(默认继承CLS),然后通过prototype给类添加属性及方法
2、getClass(attr,className),注册一个className类(默认继承CLS),将attr的属性及方法附加给className类
3、getClass(className,baseClassName),注册一个className类,继承自baseClassName,然后通过prototype给类添加属性及方法
4、getClass(attr,className,baseClassName),注册一个className类,继承自baseClassName,将attr的属性及方法附加给className类
上面的单元测试说明了本次实现的需求:
1、能注册类,这个类必须注册成功
2、注册的类,能直接通过new 关键字进行实例化,通过property能设置其属性及方法
3、注册的类包含isSubclassOf方法判断是某的类的子类
4、子类的同名方法会重写基类的方法,并在方法内部,通过this.parent(arg)来调用基类的本方法(从impactjs借鉴过来,还是很好用的,实现类似c#base.Method()的功能)
核心库的实现
测试写完了,终于要开始重头戏啦=.=。
对于javascript来说,并没有名称空间,他有的只是对象。为了让他支持名称空间,可以用对象来表示名称空间,如果该空间包括子空间,则再该对象上增加子空间的属性,就像下面一样
var System = new Object();
System.Window = new Object();
所以我们的核心库的名称空间也像这样实现了
var wod = new Object();
=.=好简陋。
然后是类,所以我们的wod.CLS不能是Object啦,他必须是一个Function
wod.CLS = function(){ };
怎么又这么简陋=.=。
在wod.CLS上增加getNS(namespace)方法,通过new Object()的方式,很容易将他实现:
wod.CLS.getNS = function (nsName) { var arr; if (!nsName || ((arr = nsName.split(".")) && arr.length == 0)) return undefined; var base = window; for (var i = 0, length = arr.length; i < length; i++) { if (base[arr[i]]) { } else { base[arr[i]] = new Object(); } base = base[arr[i]]; } return base; };
然后来实现注册类:
wod.CLS.getClass = function(className,baseClassName){ var baseClass = eval("("+baseClassName+")");//这里baseClassName一定是存在的就得到基类的那个function var construct = function(){ };//初始化一个当前的类 construct._super = baseClass;//记录他的基本是baseClass var ns = this.getClassNS(className);//通过className获取NS ns[className.splite('.').pop()] = construct;//将类放到NS上 return construct; };
这样就是一个基本雏形了。
下面需要对齐进行完善,实现isSubclassOf,继承,parent等功能。
由于我们的类是由Function实现的,所以可以增加方法
Function.prototype.isSubclassOf = function (base) { if (this._super == null) return false; if (this._super === base) return true; else { return this._super.isSubclassOf(base); } };
然后是继承,我们可以创建一个baseClass的实例,并将该实例的所有属性放到当前类的prototype上,然后当前类的所有新的实例自然就有了baseClass的所有方法。
construct.prototype = new baseClass();
再来是构造方法,我们经常需要去定义一个类的构造方法,如new Person(personName)的形式。但是我们的注册类的实例方法中,不能自由定义,所以得有变通方法。我的方法就是增加一个_init方法,由各个子类的_init方法去实现自己的构造方法,所以construct变成了这样:
var construct = function(){ this._init.apply(this,arguments);//不管在构造函数里有几个参数,都一个不漏的传送到了_init方法中 }
我们还要使用parent实现类似c#base.Method(),所以每一个类都有parent方法,所以在CLS中增加该方法,并通过construct._super来寻找对应的基类的方法,并调用他:
wod.CLS = function () { this.parent = function () { var cls = this.constructor; var caller = arguments.callee.caller; var finded = false; var result; while (!finded) { for (var tmp in cls.prototype) { if (cls.prototype[tmp] == caller) { if (cls._superpt[tmp] && cls._superpt[tmp] != caller) { result = cls._superpt[tmp].apply(this, arguments); finded = true; } break; } } if (!finded) { cls = cls._super; } } return result; }; };
大功几乎告成。
现在,我们可以任意定义我们的类啦,
写个例子吧:
wod.CLS.getClass({ name:"", eat:function(food){ alert(this.name+' is eating ' + food +'!'); } },'my.eatbase'); wod.CLS.getClass({ name:"cat" },'my.cat'); wod.CLS.getClass({ _init:function(name){ this.name = name||this.name; this.parent(name); }, name:"dog" },'my.dog'); new my.dog().eat(); new my.dog('旺福').eat(); new my.cat().eat();
怎么样呢?
这样就结束啦。大家可以在后面的链接下载下来玩一下。
里面为了将属性优雅的附加到prototype上还用到了mixin和clone方法,可以去了解一下 clone mixin extend。之前看到的网页怎么也搜不到了.......,找到了再放上来。
在prototype中增加对象属性的时候,当使用new 关键字的时候,多个对象会共用prototype中的这一个对象属性,所以在CLS的_init方法使用了clone的方式,给不同的对象设置不同的值。这个问题正好在项目中还引起过一个BUG =.= 。