前言java
javaScript中並無內置的相似java,C++的建立或實現接口的方法。也沒有內置的方法能夠用於判斷一個對象是否實現了與另外一個對象相同的方法,這使得對象很難互換使用。好在javaScrip有出色的靈活性,能讓我咱們模仿這些特性。數組
咱們按照一下的順序來逐步認識接口:函數
1.什麼是接口?工具
2.javaScript模仿接口的三種方法。性能
3.接口的利弊測試
4.一個示例this
什麼是接口prototype
接口提供了一種用以說明一個對象應該具備那些方法的手段,但並不規定這些方法應該如何實現。調試
比如一個廚師(chef),會三種菜 foodA,foodB,foodC, 咱們很容易從語義上理解,這個廚師能夠完成三種菜,但cooker並無實現這三種菜的方法。code
從代碼上看相似於:
var chef= ['foodA','foodB','foodC'];
而不是:
var chef= { foodA:function(){ //foodA實現方法 }, foodB:function(){ //foodB實現方法 }, foodC:function(){ //foodC實現方法 } }
這樣咱們能夠利用接口這個工具,按照對象提供的特性對他們進行分組。
例如:一些對象存在着很大的差別,可是他們都實現了setName這個方法,就能夠互換使用這些對象
var baseName = { name:'bird', someAttr:function(fn){ this.name = fn; } } //這三個對象的差異很大,但均可以實現setName的方法 objA = { a:function(){}, b:function(){}, setName:function(newName){ return newName; } } objB = { c:function(){}, d:function(){}, setName:function(newName){ return newName; } } objC = { e:function(){}, f:function(){}, setName:function(newName){ return newName; } } //能夠互換使用這些對象,完成相同的效果 baseName.someAttr(objA.setName('dog')); console.log(baseName.name);//dog baseName.someAttr(objB.setName('dog')); console.log(baseName.name);//dog baseName.someAttr(objC.setName('dog')); console.log(baseName.name);//dog
還可使用接口來開發不一樣類之間的共同性。若是把本來要求以一個特定類爲參數的函數改成要求以一個特定的接口爲參數的函數,那麼任何實現了該接口的對象均可以做爲參數傳遞給它。若是理解了上面的例子,這個應該很容易理解。
模仿接口的三種方法
註釋法
// 經過註釋的方法描述接口
// 這種方法模仿其餘面嚮對象語言中的作法,
// 使用interface(接口) 和 implements(實現) 關鍵字
// 但把他們放在註釋中,以避免引發語法錯誤
/* Interface ChefOne{ function foodA(){} function foodB(){} function foodC(){} } Interface ChefTwo { function foodD(){} function foodE(){} function foodF(){} }*/ //implement the ChefOne Interface 實現ChefOne接口 var ChefOne = function() {}; CookerOne.prototype.foodA = function() {}; CookerOne.prototype.foodB = function() {}; CookerOne.prototype.foodC = function() {}; //implement the ChefTwo Interface 實現ChefTwo接口 var ChefOne = function() {}; CookerOne.prototype.foodD = function() {}; CookerOne.prototype.foodE = function() {}; CookerOne.prototype.foodF = function() {};
註釋描述接口,並無去檢查是否真的實現了接口,對接口的約定靠的都是認爲的把控。雖然這不是一個好的方法,可是它易於實現,不須要額外的類和函數。不會影響文件的大小和執行速度。可是由於沒有報錯機制,對測試和調試沒有幫助。
屬性檢查法
全部的類都明確的聲明瞭本身實現哪些接口,能夠針對這些聲明進行檢查。
接口自身仍然能夠註釋。
//有一個類TopChef 它自稱實現了兩個接口 chefOne chefTwo var TopChef = function() { this.implementInterfaces = ['chefOne', 'chefTwo']; this.foodA = function() {}; this.foodC = function() {}; } //檢查是否實現接口 function ensureImplements(obj) { for(var i = 1; i < arguments.length; i++) { var interfaceName = arguments[i]; var interfaceFound = false; for(var j = 0; j < obj.implementInterfaces.length; j++) { if(obj.implementInterfaces[j] === interfaceName) { interfaceFound = true; break; } } if(!interfaceFound) { return false } } return true; } var topChef = new TopChef(); if(ensureImplements(topChef, 'chefOne', 'chefTwo')) { console.log('All interfaces were implement'); //全部的接口已經實現 } else { console.log('An interface was not implement'); //沒有實現全部的接口 }
這種方法並無確保類真正的實現了本身聲稱的接口,若是上面代碼中 chefOne 有方法 foodA,chefTwo有方法foodC, 可是後期修改了TopChef的所實現的方法後,好比添加或者刪除一個屬性,仍然能經過檢測,可是在接口中並不存在這個方法。很容易埋下隱患,這種錯誤也很難排插。
鴨式辨型法
這個名稱來自James Whitcomb Riley 的名言:‘’像鴨子同樣走路而且嘎嘎叫的就是鴨子‘’。
所以,能夠理解爲,若是對象具備與接口定義的方法同名的全部方法,就能夠認爲實現了這個接口。
//一個輔助函數構造接口,傳入接口名稱和屬性 function Interface(interfaceName, mothodArr) { if(arguments.length < 2) { throw new Error('必須傳入接口名稱,接口方法兩個參數'); } this.name = interfaceName; //接口的名稱 this.mothodArr = []; //接口方法的數組 for(var i = 0; i < mothodArr.length; i++) { if(typeof mothodArr[i] !== "string") { throw new Error("接口方法(" + mothodArr[i] + ")參數類型必須爲字符串"); } this.mothodArr.push(mothodArr[i]); } } //在interface上擴展接口檢測的方法 Interface.ensureImplements = function(obj) { if(arguments.length < 2) { throw new Error("至少傳入兩個參數"); } //從第二個參數循環,即爲定義的接口 for(var i = 1; i < arguments.length; i++) { var inter = arguments[i]; //拿到其中的一個接口 //判斷接口的構造函數是否正確 if(inter.constructor !== Interface) { throw new Error(inter + "的構造函數必須是Interface"); } //循環接口中定義的方法,與實現接口的對象中的方法作比較 for(var j = 0; j < inter.mothodArr.length; j++) { var mothod = inter.mothodArr[j]; //若是 方法不存在,或者不是一個函數 即終止程序 if(!obj[mothod] || typeof obj[mothod] !== "function") { throw new Error("接口" + inter.name + "沒有實現" + mothod + "方法"); } } } console.log("全部接口的全部方法已經實現") } //定義兩個接口 var gimMan = new Interface('gimMan', ['add', 'max', 'min']); var givMan = new Interface('givMan', ['set']); function CommMan() { this.add = function() { console.log(1) }; this.max = function() { console.log(2) }; this.min = function() { console.log(3) }; // this.set = function() { console.log(4) }; Interface.ensureImplements(this, gimMan, givMan); }
var c = new CommMan(); //全部接口的全部方法已經實現
和以上兩種方式不一樣,這種方法不須要註釋,並且檢測過程的大部分是能夠強制的。若是漏掉了任何一個方法都會報錯,並且會拋出相對有用的錯誤信息。
這種方法中,並不聲明本身實現了哪些接口,所以沒有自我描述性。它須要的是一個輔助類,和一個輔助函數。但這種方法是最經常使用,也是最完善的一種。
接口的利弊
利:
既定的一批接口具備自我描述性,並能促進代碼的重用。
若是熟悉一個接口,就知道了全部實現它的類,從而有可能重用現有的類。
接口能讓代碼百年的更穩固。若是接口添加了一個操做,可是類中並無實現它,很顯然會獲得一個錯誤。
弊:
接口會對性能形成必定影響。可是在項目生產環境中,能夠去掉接口這部分代碼。
javaScript不像其餘語言中有接口的概念,沒法根除是否實現着接口這個問題,仍是須要你們的相互遵照。
示例
//有一個類它有轉換或者處理字符串的方法 //參數gstr 是getString 方法的一個實例 //咱們最基本的實現方法以下 var stringConvert = function(gstr){ if(!(gstr instanceof getString)){ throw new Error(gstr+'不是getString的實例'); } this.gstr = gstr; } stringConvert.prototype.A = function(someString){ return this.gstr.mothed1(someString); } stringConvert.prototype.B = function(someString){ return this.gstr.mothed2(someString); }
//這種寫法會對gstr參數進行檢查,可是不能保證mothed1 ,mothed2兩個方法都已經實現
//並且若是有另外一個類 getString2 更好的實現了這兩種方法,也會由於instance of 檢測不能使用
//所以咱們可使用接口來代替instance of 像下面這樣更好的實現
//用到上面的鴨式辨型法
//首先定義接口類 var getStr = new Interface('getStr',['mothed1','mothed2']); var stringConvert = function(gstr){ Interface.ensureImplements(gstr,getStr); this.gstr = gstr; } stringConvert.prototype.A = function(someString){ return this.gstr.mothed1(someString); } stringConvert.prototype.B = function(someString){ return this.gstr.mothed2(someString); }