我正在学习如何用JavaScript制作面向对象编程。是否有接口概念(如Java的接口)?

这样我就可以创建一个监听器。


当前回答

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

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

其他回答

找到一个尽可能低影响的模拟接口的解决方案也困扰着我。

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

/**
@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。

JavaScript接口:

虽然JavaScript没有接口类型,但经常需要它。由于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函数是一个在抽象类中使用的实用工具和帮助函数。它的工作是允许对封装的戒律(数据和行为)进行定制的实现处理。它可以抛出错误或警告——并且——将默认值赋给实现者类。

iAbstractClass

iAbstractClass定义要使用的接口。它的方法需要与其实现者类达成默契。这个接口将每个戒律分配给相同的戒律名称空间——或——给戒律解析器函数返回的任何东西。但是,这个默契会分解为一个上下文——实现者的一个规定。

实现者

实现者只是“同意”一个接口(在本例中是iAbstractClass),并通过构造器劫持来应用它:iAbstractClass.apply(this)。通过定义上面的数据和行为,然后劫持接口的构造函数——将实现者的上下文传递给接口构造函数——我们可以确保将添加实现者的重写,并且该接口将解释警告和默认值。

这是一种非常简单的方法,在不同的项目过程中,对我和我的团队都非常有用。然而,它确实有一些警告和缺点。

缺点

虽然这有助于在很大程度上实现整个软件的一致性,但它并没有实现真正的接口——而是模拟它们。尽管定义、默认值、警告或错误都被解释了,但使用的解释是由开发人员强制执行和断言的(就像大多数JavaScript开发一样)。

这似乎是“JavaScript中的接口”的最佳方法,然而,我希望看到以下问题得到解决:

返回类型的断言 签名的断言 冻结删除操作中的对象 JavaScript社区的特殊性中任何其他普遍或需要的断言

也就是说,我希望这对你有帮助,就像我和我的团队一样。

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

Js没有接口,但typescript有!

当你想使用转换器时,你可以尝试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模式 定义验证,比如字符串长度 数量范围 等。