JavaScript是否具有接口类型(例如Java的“接口”)?

时间:2010-09-14 15:16:46

标签: javascript oop

我正在学习how to make OOP with JavaScript。它是否具有接口概念(例如Java的interface)?

所以我可以创建一个监听器......

16 个答案:

答案 0 :(得分:574)

没有“这个类必须具有这些功能”的概念(即,本身没有接口),因为:

  1. JavaScript继承基于对象,而不是类。在你意识到这一点之前,这不是什么大问题:
  2. JavaScript是一种非常动态类型语言 - 您可以使用正确的方法创建一个对象,这将使其符合接口然后取消定义所有构成它的东西符合。破坏类型系统很容易 - 甚至意外! - 首先尝试制作类型系统是不值得的。
  3. 相反,JavaScript使用所谓的duck typing。 (如果它像鸭子一样走路,像鸭子那样呱呱叫,就JS而言,它就是鸭子。)如果你的对象有quack(),walk()和fly()方法,代码可以在任何预期的地方使用它一个可以行走,嘎嘎叫和飞行的物体,而不需要实现一些“Duckable”接口。接口正是代码使用的一组函数(以及这些函数的返回值),并且使用duck typing,你可以免费获得。

    现在,如果您尝试拨打some_dog.quack(),那并不是说您的代码不会在中途失败;你会得到一个TypeError。坦率地说,如果你告诉狗嘎嘎叫,你会遇到更大的问题;当你把所有的鸭子连成一排时,鸭子的打字效果最好,可以这么说,除非你把它们视为一般动物,否则它们不会让狗和鸭子混在一起。换句话说,即使界面是流动的,它仍然存在;将狗传递给期望它首先嘎嘎叫并飞行的代码通常是错误的。

    但是如果你确定你做的是正确的事情,你可以通过在尝试使用它之前测试特定方法的存在来解决这个问题。像

    这样的东西
    if (typeof(someObject.quack) == "function")
    {
        // This thing can quack
    }
    

    因此,您可以在使用之前检查可以使用的所有方法。不过,语法有点难看。有一种更漂亮的方式:

    Object.prototype.can = function(methodName)
    {
         return ((typeof this[methodName]) == "function");
    };
    
    if (someObject.can("quack"))
    {
        someObject.quack();
    }
    

    这是标准的JavaScript,所以它应该适用于任何值得使用的JS解释器。它具有像英语一样阅读的额外好处。

    对于现代浏览器(即IE 6-8以外的任何浏览器),甚至还有一种方法可以防止该属性出现在for...in中:

    Object.defineProperty(Object.prototype, 'can', {
        enumerable: false,
        value: function(method) {
            return (typeof this[method] === 'function');
        }
    }
    

    问题是IE7对象根本没有.defineProperty,而在IE8中,它据称只适用于主机对象(即DOM元素等)。如果兼容性存在问题,则无法使用.defineProperty。 (我甚至不会提到IE6,因为它在中国之外已经相当无关了。)

    另一个问题是,某些编码样式会假设每个人都编写错误代码,并禁止修改Object.prototype以防有人想盲目使用for...in。如果您关心这个问题,或者正在使用(IMO 已损坏的)代码,请尝试稍微不同的版本:

    function can(obj, methodName)
    {
         return ((typeof obj[methodName]) == "function");
    }
    
    if (can(someObject, "quack"))
    {
        someObject.quack();
    }
    

答案 1 :(得分:66)

通过JavaScript design patterns领取“Dustin Diaz”的副本。有几章专门通过Duck Typing实现JavaScript接口。这也是一个很好的阅读。但不,没有语言本地实现的接口,你必须Duck Type

// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
    var i = 1, methodName;
    while((methodName = arguments[i++])){
        if(typeof obj[methodName] != 'function') {
            return false;
        }
    }
    return true;
}

// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
    //  IT'S A DUCK, do your duck thang
}

答案 2 :(得分:19)

JavaScript(ECMAScript第3版)有一个implements保留字saved up for future use。我认为这完全是为了这个目的,但是,急于将规格排除在门外,他们没有时间来定义如何处理它,所以,目前,浏览器除此之外什么都不做让它坐在那里偶尔抱怨如果你试图用它来做某事。

使用逻辑创建自己的Object.implement(Interface)方法是可能的,而且非常容易,只要在给定对象中没有实现特定的属性/函数集,就会产生阻碍。

我在object-orientation where use my own notation as follows 上撰写了一篇文章:

// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
    constructor: function(name) {
        Dog.superClass.call(this, name);
    },
    bark: function() {
        alert('woof');
    }
}).implement(Mammal);

有很多方法可以修饰这个特殊的猫,但这是我用于自己的接口实现的逻辑。我发现我更喜欢这种方法,它易于阅读和使用(如上所述)。它确实意味着向Function.prototype添加一个“工具”方法,有些人可能会遇到问题,但我觉得它很有效。

Function.prototype.implement = function() {
    // Loop through each interface passed in and then check 
    // that its members are implemented in the context object (this).
    for(var i = 0; i < arguments.length; i++) {
       // .. Check member's logic ..
    }
    // Remember to return the class being tested
    return this;
}

答案 3 :(得分:12)

JavaScript接口:

虽然JavaScript 具有interface类型,但通常需要它。由于与JavaScript的动态性质和原型继承的使用相关的原因,很难确保跨类的一致接口 - 但是,有可能这样做;经常模仿。

此时,有一些特定方法可以在JavaScript中模拟接口;方法的差异通常满足一些需求,而其他方法则没有得到解决。通常情况下,最强大的方法过于繁琐,并阻碍了实现者(开发人员)。

这是一种接口/抽象类的方法,它不是非常麻烦,是解释性的,可以将抽象内部的实现保持在最低限度,并为动态或自定义方法留下足够的空间:

function resolvePrecept(interfaceName) {
    var interfaceName = interfaceName;
    return function curry(value) {
        /*      throw new Error(interfaceName + ' requires an implementation for ...');     */
        console.warn('%s requires an implementation for ...', interfaceName);
        return value;
    };
}

var iAbstractClass = function AbstractClass() {
    var defaultTo = resolvePrecept('iAbstractClass');

    this.datum1 = this.datum1 || defaultTo(new Number());
    this.datum2 = this.datum2 || defaultTo(new String());

    this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
    this.method2 = this.method2 || defaultTo(new Function('return new Object();'));

};

var ConcreteImplementation = function ConcreteImplementation() {

    this.datum1 = 1;
    this.datum2 = 'str';

    this.method1 = function method1() {
        return true;
    };
    this.method2 = function method2() {
        return {};
    };

    //Applies Interface (Implement iAbstractClass Interface)
    iAbstractClass.apply(this);  // .call / .apply after precept definitions
};

参与者

解析解析器

resolvePrecept功能是实用程序&amp;在抽象类中使用的辅助函数。它的工作是允许对封装的 Precepts(数据和行为)进行定制的实现处理。它可以抛出错误或警告 - 并 - 为Implementor类分配一个默认值。

<强> iAbstractClass

iAbstractClass定义要使用的接口。它的方法需要与其Implementor类达成默契。此接口将每个规则分配给同一个精确的precept命名空间 - 或 - 分配给 Precept Resolver 函数返回的任何内容。但是,默认协议解析为上下文 - 实施者的一项规定。

<强> 实施者

执行者只是“同意”接口(在这种情况下是 iAbstractClass ),并通过使用 Constructor-Hijacking iAbstractClass.apply(this)来应用它。通过定义数据和上面的行为,然后劫持接口的构造函数 - 将Implementor的上下文传递给Interface构造函数 - 我们可以确保将添加Implementor的覆盖,并且该接口将显示警告和默认值。

这是一种非常麻烦的方法,已经为我的团队提供了帮助。我非常适合时间和不同的项目。但是,它确实有一些警告和缺点。

<强>缺点

虽然这有助于在很大程度上实现整个软件的一致性,但 并未实现真正的界面 - 而是模仿它们。虽然明确了定义,默认值和警告或错误 ,但使用的解释是强制执行&amp;由开发人员断言(与JavaScript开发的大部分内容一样)。

这似乎是“JavaScript中的接口”的最佳方法,但是,我希望看到以下内容得到解决:

  • 返回类型的断言
  • 签名断言
  • 冻结delete行动中的对象
  • 在JavaScript社区的特殊性中流行或需要的任何其他内容的断言

那就是说,我希望这对我和我的团队有所帮助。

答案 4 :(得分:5)

希望,任何仍在寻找答案的人都会觉得有帮助。

您可以尝试使用代理(自ECMAScript 2015以来的标准):https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

latLngLiteral = new Proxy({},{
    set: function(obj, prop, val) {
        //only these two properties can be set
        if(['lng','lat'].indexOf(prop) == -1) {
            throw new ReferenceError('Key must be "lat" or "lng"!');
        }

        //the dec format only accepts numbers
        if(typeof val !== 'number') {
            throw new TypeError('Value must be numeric');
        }

        //latitude is in range between 0 and 90
        if(prop == 'lat'  && !(0 < val && val < 90)) {
            throw new RangeError('Position is out of range!');
        }
        //longitude is in range between 0 and 180
        else if(prop == 'lng' && !(0 < val && val < 180)) {
            throw new RangeError('Position is out of range!');
        }

        obj[prop] = val;

        return true;
    }
});

然后你可以很容易地说:

myMap = {}
myMap.position = latLngLiteral;

答案 5 :(得分:3)

您需要Java中的接口,因为它是静态类型的,并且在编译期间应该知道类之间的契约。在JavaScript中它是不同的。 JavaScript是动态类型的;这意味着当你得到对象时,你可以检查它是否有一个特定的方法并调用它。

答案 6 :(得分:2)

Javascript没有接口。但它可以是鸭型,可以在这里找到一个例子:

http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html

答案 7 :(得分:2)

如果您想使用转换编译器,那么您可以尝试使用TypeScript。它支持草案ECMA功能,类似于coffeescript或babel等语言。

在TypeScript中,您的界面可能如下所示:

interface IMyInterface {
    id: number; // TypeScript types are lowercase
    name: string;
    callback: (key: string; value: any; array: string[]) => void;
    type: "test" | "notATest"; // so called "union type"
}

你不能做什么:

  • 为类型值
  • 定义RegExp模式
  • 定义类似字符串长度的验证
  • 数字范围

答案 8 :(得分:1)

我知道这是一个旧的,但我最近发现自己需要越来越多的东西来获得一个方便的API来检查对象的接口。所以我写了这个:https://github.com/tomhicks/methodical

也可以通过NPM获得:npm install methodical

它基本上完成了上面提到的所有操作,并提供了一些更严格的选项,并且无需加载if (typeof x.method === 'function')样板。

希望有人发现它很有用。

答案 9 :(得分:1)

JavaScript中没有本机接口, 有几种模拟接口的方法。我已经写了一个能做到的包

您可以看到植入物here

答案 10 :(得分:1)

这是一个古老的问题,不过,这个话题永远不会停止困扰我。

由于这里和整个网络上的许多答案都集中在“强制”界面上,我想提出一个替代的观点:

当我使用行为相似的多个类(即实现一个接口)时,我感觉到缺少接口最多。

例如,我有一个电子邮件生成器,期望接收电子邮件部分工厂,“知道”如何生成部分的内容和HTML。因此,他们都需要使用某种getContent(id)getHtml(content)方法。

我能想到的最接近接口的模式(尽管仍然是一种解决方法)使用的类将获得2个参数,它将定义2个接口方法。

此模式的主要挑战是方法必须是static,或者是要获取实例本身的参数才能访问其属性。但是,在某些情况下,我发现这种权衡值得麻烦。

class Filterable {
  constructor(data, { filter, toString }) {
    this.data = data;
    this.filter = filter;
    this.toString = toString;
    // You can also enforce here an Iterable interface, for example,
    // which feels much more natural than having an external check
  }
}

const evenNumbersList = new Filterable(
  [1, 2, 3, 4, 5, 6], {
    filter: (lst) => {
      const evenElements = lst.data.filter(x => x % 2 === 0);
      lst.data = evenElements;
    },
    toString: lst => `< ${lst.data.toString()} >`,
  }
);

console.log('The whole list:    ', evenNumbersList.toString(evenNumbersList));
evenNumbersList.filter(evenNumbersList);
console.log('The filtered list: ', evenNumbersList.toString(evenNumbersList));

答案 11 :(得分:0)

像这样的抽象界面

const MyInterface = {
  serialize: () => {throw "must implement serialize for MyInterface types"},
  print: () => console.log(this.serialize())
}

创建实例:

function MyType() {
  this.serialize = () => "serialized "
}
MyType.prototype = MyInterface

并使用它

let x = new MyType()
x.print()

答案 12 :(得分:0)

寻找解决方案来模仿界面影响尽可能小也让我感到困惑。

一种解决方案可能是制作工具:

/**
@parameter {Array|object} required : method name list or members types by their name
@constructor
*/
let Interface=function(required){
    this.obj=0;
    if(required instanceof Array){
        this.obj={};
        required.forEach(r=>this.obj[r]='function');
    }else if(typeof(required)==='object'){
        this.obj=required;
    }else {
        throw('Interface invalid parameter required = '+required);
    }
};
/** check constructor instance
@parameter {object} scope : instance to check.
@parameter {boolean} [strict] : if true -> throw an error if errors ar found.
@constructor
*/
Interface.prototype.check=function(scope,strict){
    let err=[],type,res={};
    for(let k in this.obj){
        type=typeof(scope[k]);
        if(type!==this.obj[k]){
            err.push({
                key:k,
                type:this.obj[k],
                inputType:type,
                msg:type==='undefined'?'missing element':'bad element type "'+type+'"'
            });
        }
    }
    res.success=!err.length;
    if(err.length){
        res.msg='Class bad structure :';
        res.errors=err;
        if(strict){
            let stk = new Error().stack.split('\n');
            stk.shift();
            throw(['',res.msg,
                res.errors.map(e=>'- {'+e.type+'} '+e.key+' : '+e.msg).join('\n'),
                '','at :\n\t'+stk.join('\n\t')
            ].join('\n'));

        }
    }
    return res;
};

使用示例:

// create interface tool
let dataInterface=new Interface(['toData','fromData']);
// abstract constructor
let AbstractData=function(){
    dataInterface.check(this,1);// check extended element
};
// extended constructor
let DataXY=function(){
    AbstractData.apply(this,[]);
    this.xy=[0,0];
};
DataXY.prototype.toData=function(){
    return [this.xy[0],this.xy[1]];
};

// should throw an error because 'fromData' is missing
let dx=new DataXY();

带课程

class AbstractData{
    constructor(){
        dataInterface.check(this,1);
    }
}
class DataXY extends AbstractData{
    constructor(){
        super();
        this.xy=[0,0];
    }
    toData(){
        return [this.xy[0],this.xy[1]];
    }
}

这仍然需要一些性能,并且需要依赖 Interface 类,但是可以用于调试或打开api。

答案 13 :(得分:0)

这是旧的,但我实现了在没有转译器的情况下在 ES6 上使用的接口。

https://github.com/jkutianski/ES6-Interfaces

答案 14 :(得分:0)

通过接口,您可以实现一种多态性。 Javascript不需要需要接口类型来处理这个和其他interface的东西。为什么? Javascript 是一种动态类型语言。以具有相同方法的类数组为例:

Circle()
Square()
Triangle()

在这些类中实现方法 draw() 将这些类的实例推送到数组中,并在迭代数组的循环中调用 draw() 方法。这是完全有效的。您可以说您隐式实现了 abstract class。它在现实中并不存在,但在你的脑海中你做到了,Javascript 没有问题。与真实接口的区别在于您必须实现所有接口方法,而在这种情况下不需要。

你可以做很多事情!我认为需要的是有人写一本关于这样的东西的书,因为如果你小心处理 Javascript,它提供了很多可能性。 ES6 摇滚!无需使用本地语言等进行 webassembly,Javascript 前景广阔。

注意:由于 Javascript 的动态特性,Typescript 的使用是有问题的。为什么你会改变语言的性质。 C# 添加了 dynamic 关键字来做一些动态的事情,但不会使语言变得动态。因为 Javascript 本质上是动态的,所以你很容易出错,但它的动态特性也是一个非常强的点。它不是一种适合傻瓜的语言,但对所有语言都有效。

接口就是契约。您将必须实现所有方法。只有通过静态方式才能做到这一点,这违背了 Typescript 的性质。

答案 15 :(得分:-1)

您可以使用 Abstract sub-classss 或 mixin 作为接口 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#mix-ins