前言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); }