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