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

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


当前回答

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

其他回答

试试这个方法:将接口描述为一个类,并使用@implements JSDoc来显示给定的类实现了定义的接口。如果它没有实现某些属性,你会在类名上看到红色的弯曲线。我用VSCode进行了测试。

// @ts-check

// describe interface using a class
class PlainInterface {
    size = 4;
    describe() {}
    show(){ }
}

/**
 * @implements  PlainInterface 
 */
class ConcretePlain {
    size = 4;
    describe() {
        console.log('I am described')
    }
    show(){
        console.log('I am shown')
    }
}       

const conc = new ConcretePlain();
conc.describe();

像这样的抽象接口

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()

这是一个老问题,但这个话题一直困扰着我。

由于这里和网络上的许多答案都专注于“强制”界面,我想提出另一种观点:

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

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

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

这种模式的主要挑战是,方法要么必须是静态的,要么必须获取实例本身作为参数,以便访问其属性。然而,在有些情况下,我发现这种权衡是值得的。

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));

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

Circle()
Square()
Triangle()

如果你想知道多态是如何工作的,David krugman linsky的书MFC是很棒的(为c++编写的)。

在这些类中实现draw()方法,将这些类的实例推入数组中,并在迭代数组的循环中调用draw()方法。这是完全正确的。你可以说你隐式地实现了一个抽象类。它不存在于现实中,但在你的脑海中,你做到了,Javascript没有问题。真正的接口的不同之处在于,你必须实现所有的接口方法,在这种情况下,这是不需要的。

接口是一个契约。您必须实现所有的方法。只有让它是静态的,你才需要这样做。

把Javascript这样的语言从动态变成静态是有问题的。静止是不可取的。有经验的开发人员对Javascript的动态特性没有问题。

所以我不清楚使用Typescript的原因。如果你将NodeJS和Javascript结合使用,你可以建立非常高效和经济的企业网站。Javascript/NodeJS/MongoDB组合已经是伟大的赢家。

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

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

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