Javascript面向?qū)ο蠡A(chǔ)以及接口和繼承類的實(shí)現(xiàn)
在開始設(shè)計(jì)模式的書寫之前,有必要對(duì)Javascript面向?qū)ο蟮母拍钕茸鰝€(gè)介紹,那么這篇文章就以面向?qū)ο蠡A(chǔ)作為起點(diǎn)吧。
理論知識(shí)
1. 首先Javascript是弱類型語言,它定義變量時(shí)不必聲明類型,如var Person = new Person(),它的變量類型為“var”,現(xiàn)在的C# 3.0也引進(jìn)了這種匿名類型的概念,弱類型的變量產(chǎn)生了極大的靈活性,因?yàn)镴avascript會(huì)根據(jù)需要來進(jìn)行類型轉(zhuǎn)換。所以這也決定了它采用了晚綁定的方法,即在運(yùn)行后才知道變量的類型;
2. 面向?qū)ο蟾拍畈槐囟嗾f,封裝,繼承,多態(tài);
3. Javascript對(duì)象的類型主要分為三種:本地對(duì)象,如String,Array,Date等;內(nèi)置對(duì)象,如Global,Math等;宿主對(duì)象,是指傳統(tǒng)面向?qū)ο蟪绦蛟O(shè)計(jì)中的作用域,如公有,保護(hù),私有,靜態(tài)等等。
主要內(nèi)容
1. 現(xiàn)在讓我們來看看Javascript怎樣創(chuàng)建對(duì)象的:
function Man() {
//
}
Man.prototype.getNickName = function() {
return "Leepy";
};
var man = new Man();
var name = man.getNickName();
|
這樣就創(chuàng)建了最簡單的類和對(duì)象,其中我們可以把function Man() {} 看作是Man類的構(gòu)造函數(shù),getNickName()看作是Man類的方法,準(zhǔn)確說可以“當(dāng)作”是Man類的公共方法;為什么要說是當(dāng)作呢?那是因?yàn)槠鋵?shí)Javascript實(shí)際上并沒有一個(gè)私有共有的劃分,因此開發(fā)者們自己指定了這樣的規(guī)約,那么規(guī)約是什么樣的呢?我這里把Man類的清單完整地列出來:
function Man() {
// 私有靜態(tài)屬性
var Sex = "男";
//私有靜態(tài)方法
function checkSex() {
return (Sex == "男");
}
//私有方法
this._getSex = function() {
//調(diào)用私有靜態(tài)方法
if(checkSex())
return "男";
else
return "女";
}
//私有方法
this.getFirstName = function() {
return "Li";
};
//私有方法
this.getLastName = function() {
return "Ping";
};
}
//公共方法
Man.prototype.getNickName = function() {
return "Leepy";
};
//公共方法
Man.prototype.getFullName = function() {
return this.getFirstName() + " " + this.getLastName();
};
//公共方法
Man.prototype.getSex = function() {
//調(diào)用私有方法
return this._getSex();
};
//公共靜態(tài)方法
Man.say = function() {
return "Happy new year!";
}
這樣的類是否看起來和傳統(tǒng)的類很相似了呢?
2.接下來這個(gè)是本篇的一個(gè)重點(diǎn),就是用Javascript如何設(shè)計(jì)一個(gè)接口,然后讓類繼承于它。
首先,先讓我們看傳統(tǒng)的C#語言是如何設(shè)計(jì)接口的吧:
public interface Person
{
string GetName();
void SetName(string name);
}
public class Man : Person
{
private string _name;
public string GetName()
{
return _name;
}
public void SetName(string name)
{
_name = name;
}
}
|
接口中可以聲明屬性、方法、事件和類型(Structure),(但不能聲明變量),但是并不能設(shè)置這些成員的具體值,也就是說,只能定義,不能給它里面定義的東西賦值,而接口作為它的繼承類或者派生類的規(guī)約,繼承類或者它的派生類能夠共同完成接口屬性、方法、事件和類型的具體實(shí)現(xiàn),因?yàn)檫@里GetName(),SetName(),不管是方法名還是屬性調(diào)用順序上都是要保持一致的;
那么有了這樣的一個(gè)基于接口的思想,我們設(shè)計(jì)Javascript的接口類的時(shí)候也需要考慮到這個(gè)規(guī)范。我先從主JS文件調(diào)用端開始說起:
var Person = new Interface("Person", [["getName", 0], ["setName", 1]]);
|
其中Interface類是稍后要說的接口類,第一個(gè)參數(shù)"Person"是接口類的名稱,第二個(gè)參數(shù)是個(gè)二維數(shù)組,"getName"是接口方法的名稱,"0"是該方法所帶的參數(shù)個(gè)數(shù)(因?yàn)镴avascript是弱語言,所以類型是不確定的,所以只要記住參數(shù)個(gè)數(shù)就好,"0"可以省略不寫),"setName"同理。這樣一個(gè)接口定義好了。怎樣使用它呢?
function Man()
{
this.name = "";
Interface.registerImplements(this, Person);
}
Man.prototype.getName = function() {
return this.name;
};
Man.prototype.setName = function(name) {
this.name = name;
};
|
看到Man的構(gòu)造函數(shù)里面包含
Interface.registerImplements(this, Person);
它是用來將實(shí)例化的this對(duì)象繼承于Person接口,然后繼承類對(duì)接口的方法進(jìn)行實(shí)現(xiàn)。
代碼看起來是不是很清晰和簡單呢,那么現(xiàn)在要開始介紹真正的核心代碼Interface.js了:
先看Interface的構(gòu)造函數(shù)部分
unction Interface(name, methods)
{
if(arguments.length != 2) {
throw new Error("接口構(gòu)造函數(shù)含" + arguments.length + "個(gè)參數(shù), 但需要2個(gè)參數(shù).");
}
this.name = name;
this.methods = [];
if(methods.length < 1) {
throw new Error("第二個(gè)參數(shù)為空數(shù)組.");
}
for(var i = 0, len = methods.length; i < len; i++) {
if(typeof methods[i][0] !== 'string') {
throw new Error("接口構(gòu)造函數(shù)第一個(gè)參數(shù)必須為字符串類型.");
}
if(methods[i][1] && typeof methods[i][1] !== 'number') {
throw new Error("接口構(gòu)造函數(shù)第二個(gè)參數(shù)必須為整數(shù)類型.");
}
if(methods[i].length == 1) {
methods[i][1] = 0;
}
this.methods.push(methods[i]);
}
};
剛才看到了var Person = new Interface("Person", [["getName", 0], ["setName", 1]]);,這里將兩個(gè)參數(shù)分別保存起來;
#p#
調(diào)用方法部分:
Interface.registerImplements = function(object) {
if(arguments.length < 2) {
throw new Error("接口的實(shí)現(xiàn)必須包含至少2個(gè)參數(shù).");
}
for(var i = 1, len = arguments.length; i < len; i++) {
var interface = arguments[i];
if(interface.constructor !== Interface) {
throw new Error("從第2個(gè)以上的參數(shù)必須為接口實(shí)例.");
}
for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
var method = interface.methods[j][0];
if(!object[method] || typeof object[method] !== 'function' || object[method].getParameters().length != interface.methods[j][1]) {
throw new Error("接口的實(shí)現(xiàn)對(duì)象不能執(zhí)行" + interface.name + "的接口方法" + method + ",因?yàn)樗也坏交蛘卟黄ヅ?");
}
}
}
};
剛才這句Interface.registerImplements(this, Person);,實(shí)際上這里是把this對(duì)象的方法名以及參數(shù)個(gè)數(shù)與剛Person保存的methods逐一進(jìn)行比較,如果找不到或者不匹配,就警告錯(cuò)誤;其中object[method].getParameters().length,調(diào)用了如下的代碼:
Function.prototype.getParameters = function() {
var str = this.toString();
var paramString = str.slice(str.indexOf('(') + 1, str.indexOf(')')).rep |
getParrameters()方法作為Function對(duì)象的一個(gè)擴(kuò)展,功能是取得方法含有的參數(shù)數(shù)組;
Interface.js完整的代碼如下:
Interface.js文件
function Interface(name, methods)
{
if(arguments.length != 2) {
throw new Error("接口構(gòu)造函數(shù)含" + arguments.length + "個(gè)參數(shù), 但需要2個(gè)參數(shù).");
}
this.name = name;
this.methods = [];
if(methods.length < 1) {
throw new Error("第二個(gè)參數(shù)為空數(shù)組.");
}
for(var i = 0, len = methods.length; i < len; i++) {
if(typeof methods[i][0] !== 'string') {
throw new Error("接口構(gòu)造函數(shù)第一個(gè)參數(shù)必須為字符串類型.");
}
if(methods[i][1] && typeof methods[i][1] !== 'number') {
throw new Error("接口構(gòu)造函數(shù)第二個(gè)參數(shù)必須為整數(shù)類型.");
}
if(methods[i].length == 1) {
methods[i][1] = 0;
}
this.methods.push(methods[i]);
}
};
Interface.registerImplements = function(object) {
if(arguments.length < 2) {
throw new Error("接口的實(shí)現(xiàn)必須包含至少2個(gè)參數(shù).");
}
for(var i = 1, len = arguments.length; i < len; i++) {
var interface = arguments[i];
if(interface.constructor !== Interface) {
throw new Error("從第2個(gè)以上的參數(shù)必須為接口實(shí)例.");
}
for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
var method = interface.methods[j][0];
if(!object[method] || typeof object[method] !== 'function' || object[method].getParameters().length != interface.methods[j][1]) {
throw new Error("接口的實(shí)現(xiàn)對(duì)象不能執(zhí)行" + interface.name + "的接口方法" + method + ",因?yàn)樗也坏交蛘卟黄ヅ?");
}
}
}
};
Function.prototype.getParameters = function() {
var str = this.toString();
var paramString = str.slice(str.indexOf('(') + 1, str.indexOf(')')).replace(/\s*/g,''); //取得參數(shù)字符串
try
{
return (paramString.length == 0 ? [] : paramString.split(','));
}
catch(err)
{
throw new Error("函數(shù)不合法!");
}
}
好了該創(chuàng)建一個(gè)html頁面來試試效果了:
<script type="text/javascript">
function test()
{
var man = new Man();
man.setName("Leepy");
alert(man.getName());
}
</script>
<input type="button" value="click" onclick="test();" />
|
最終結(jié)果為:"Leepy"的彈出框。
這里還有一點(diǎn)要強(qiáng)調(diào),如果接口上的方法沒有在繼承類上得到完全實(shí)現(xiàn),或者方法參數(shù)個(gè)數(shù)不匹配,那么就會(huì)提示錯(cuò)誤。
3. 如果我要一個(gè)類繼承于另一個(gè)類該怎么做呢,繼續(xù)看例子,這里我再定義一個(gè)SchoolBoy(男學(xué)生)類:
function SchoolBoy(classNo, post)
{
Man.call(this);
this._chassNo = classNo;
this._post = post;
}
SchoolBoy.prototype = new Man();
SchoolBoy.prototype.getName = function() {
return "Mr " + this.name;
}
SchoolBoy.prototype.setName = function(name) {
this.name = name + "'s";
}
|
其中Man.call(this);實(shí)際上是將Man中的關(guān)鍵字this賦值于SchoolBoy對(duì)象中去,那么SchoolBoy就擁有了Man構(gòu)造函數(shù)中的name屬性了。
SchoolBoy.prototype = new Man();實(shí)際上是把Man的prototype賦值給SchoolBoy.prototype,那么SchoolBoy就有了Man類中的方法。
而后面跟著的getName(),setName(),實(shí)際上是覆蓋了前面繼承于Man類中的方法了。
然后看看效果:
var schoolboy = new SchoolBoy("三年二班", "班長");
schoolboy.setName("周杰倫");
alert(schoolboy.getName());
|
最后結(jié)果為:"Mr 周杰倫's"的彈出框。
【編輯推薦】



















