Javascript面向對象基礎以及接口和繼承類的實現
在開始設計模式的書寫之前,有必要對Javascript面向對象的概念先做個介紹,那么這篇文章就以面向對象基礎作為起點吧。
理論知識
1. 首先Javascript是弱類型語言,它定義變量時不必聲明類型,如var Person = new Person(),它的變量類型為“var”,現在的C# 3.0也引進了這種匿名類型的概念,弱類型的變量產生了極大的靈活性,因為Javascript會根據需要來進行類型轉換。所以這也決定了它采用了晚綁定的方法,即在運行后才知道變量的類型;
2. 面向對象概念不必多說,封裝,繼承,多態;
3. Javascript對象的類型主要分為三種:本地對象,如String,Array,Date等;內置對象,如Global,Math等;宿主對象,是指傳統面向對象程序設計中的作用域,如公有,保護,私有,靜態等等。
主要內容
1. 現在讓我們來看看Javascript怎樣創建對象的:
function Man() {
//
}
Man.prototype.getNickName = function() {
return "Leepy";
};
var man = new Man();
var name = man.getNickName();
|
這樣就創建了最簡單的類和對象,其中我們可以把function Man() {} 看作是Man類的構造函數,getNickName()看作是Man類的方法,準確說可以“當作”是Man類的公共方法;為什么要說是當作呢?那是因為其實Javascript實際上并沒有一個私有共有的劃分,因此開發者們自己指定了這樣的規約,那么規約是什么樣的呢?我這里把Man類的清單完整地列出來:
function Man() {
// 私有靜態屬性
var Sex = "男";
//私有靜態方法
function checkSex() {
return (Sex == "男");
}
//私有方法
this._getSex = function() {
//調用私有靜態方法
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();
};
//公共靜態方法
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),(但不能聲明變量),但是并不能設置這些成員的具體值,也就是說,只能定義,不能給它里面定義的東西賦值,而接口作為它的繼承類或者派生類的規約,繼承類或者它的派生類能夠共同完成接口屬性、方法、事件和類型的具體實現,因為這里GetName(),SetName(),不管是方法名還是屬性調用順序上都是要保持一致的;
那么有了這樣的一個基于接口的思想,我們設計Javascript的接口類的時候也需要考慮到這個規范。我先從主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("接口的實現對象不能執行" + 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("接口的實現對象不能執行" + 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("函數不合法!");
}
}
好了該創建一個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. 如果我要一個類繼承于另一個類該怎么做呢,繼續看例子,這里我再定義一個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"的彈出框。
【編輯推薦】


















