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

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


当前回答

虽然javaScript中不像Java中那样有接口,但你可以用这条消息下的代码来模仿这种行为。因为接口基本上是一个强制契约,你可以自己构建它。

下面的代码来自3个类:接口、父类和子类。

接口中有检查方法和属性是否存在的方法。 父类用于使用Interface类在子类中强制执行所需的方法和属性。 孩子是父母规则强制执行的类别。

在您正确地设置它之后,如果子程序中缺少方法或属性,那么您将在控制台中看到一个错误,如果子程序正确地实现了契约,则什么也没有。

class Interface {  
    checkRequiredMethods(methodNames) {
        setTimeout( () => {
            const loopLength = methodNames.length;
            let i = 0
            for (i; i<loopLength; i++) {
                if (typeof this[methodNames[i]] === "undefined") {
                    this.throwMissingMethod(methodNames[i]);
                }

                else if (typeof this[methodNames[i]] !== "function") {
                    this.throwNotAMethod(methodNames[i]);
                }
            }
        }, 0);
    }

    checkRequiredProperties(propNames) {
        setTimeout( () => {
            const loopLength = propNames.length;
            let i = 0
            for (i; i<loopLength; i++) {
                if (typeof this[propNames[i]] === "undefined") {
                    this.throwMissingProperty(propNames[i]);
                }

                else if (typeof this[propNames[i]] === "function") {
                    this.throwPropertyIsMethod(propNames[i]);
                }
            }
        }, 0);     
    }

    throwMissingMethod(methodName) {
        throw new Error(`error method ${methodName} is undefined`);
    }

    throwNotAMethod(methodName) {
        throw new Error(`error method ${methodName} is not a method`);
    }

    throwMissingProperty(propName) {
        throw new Error(`error property ${propName} is not defined`);
    }

    throwPropertyIsMethod(propName) {
        throw new Error(`error property ${propName} is a method`);
    }
}
class Parent extends Interface {
    constructor() {
        super()

        this.checkRequiredProperties([
            "p1",
            "p2",
            "p3",
            "p4",
            "p5"
        ]);

        this.checkRequiredMethods([ 
            "m1",
            "m2",
            "m3",
            "m4"
        ]);
    }
}
class Child extends Parent {
    p1 = 0;
    p2 = "";
    p3 = false;
    p4 = [];
    p5 = {};

    constructor() {
        super();
    }

    m1() {}
    m2() {}
    m3() {}
    m4() {}
}
new Child()

其他回答

没有,但是它有mixins。 您可以使用抽象子类或mixins作为替代https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#mix-ins

希望任何还在寻找答案的人都能从中找到帮助。

你可以尝试使用代理(这是自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;

如果你想通过instanceof (@Kamaffeather问)检查,你可以像这样把它包装在一个对象中:

class LatLngLiteral {
    constructor(props)
    {
        this.proxy = new Proxy(this, {
            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;
            }
        })
        return this.proxy
    }
}

这可以在不使用Proxy的情况下完成,而是使用类getter和setter:

class LatLngLiteral {
    #latitude;
    #longitude;

    get lat()
    {
        return this.#latitude;
    }

    get lng()
    {
        return this.#longitude;
    }
    
    set lat(val)
    {
        //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(!(0 < val && val < 90)) {
            throw new RangeError('Position is out of range!');
        }
        
        this.#latitude = val
    }
    
    set lng(val)
    {
        //the dec format only accepts numbers
        if(typeof val !== 'number') {
            throw new TypeError('Value must be numeric');
        }

        //longitude is in range between 0 and 180
        if(!(0 < val && val < 180)) {
            throw new RangeError('Position is out of range!');
        }
        
        this.#longitude = val
    }
}

虽然javaScript中不像Java中那样有接口,但你可以用这条消息下的代码来模仿这种行为。因为接口基本上是一个强制契约,你可以自己构建它。

下面的代码来自3个类:接口、父类和子类。

接口中有检查方法和属性是否存在的方法。 父类用于使用Interface类在子类中强制执行所需的方法和属性。 孩子是父母规则强制执行的类别。

在您正确地设置它之后,如果子程序中缺少方法或属性,那么您将在控制台中看到一个错误,如果子程序正确地实现了契约,则什么也没有。

class Interface {  
    checkRequiredMethods(methodNames) {
        setTimeout( () => {
            const loopLength = methodNames.length;
            let i = 0
            for (i; i<loopLength; i++) {
                if (typeof this[methodNames[i]] === "undefined") {
                    this.throwMissingMethod(methodNames[i]);
                }

                else if (typeof this[methodNames[i]] !== "function") {
                    this.throwNotAMethod(methodNames[i]);
                }
            }
        }, 0);
    }

    checkRequiredProperties(propNames) {
        setTimeout( () => {
            const loopLength = propNames.length;
            let i = 0
            for (i; i<loopLength; i++) {
                if (typeof this[propNames[i]] === "undefined") {
                    this.throwMissingProperty(propNames[i]);
                }

                else if (typeof this[propNames[i]] === "function") {
                    this.throwPropertyIsMethod(propNames[i]);
                }
            }
        }, 0);     
    }

    throwMissingMethod(methodName) {
        throw new Error(`error method ${methodName} is undefined`);
    }

    throwNotAMethod(methodName) {
        throw new Error(`error method ${methodName} is not a method`);
    }

    throwMissingProperty(propName) {
        throw new Error(`error property ${propName} is not defined`);
    }

    throwPropertyIsMethod(propName) {
        throw new Error(`error property ${propName} is a method`);
    }
}
class Parent extends Interface {
    constructor() {
        super()

        this.checkRequiredProperties([
            "p1",
            "p2",
            "p3",
            "p4",
            "p5"
        ]);

        this.checkRequiredMethods([ 
            "m1",
            "m2",
            "m3",
            "m4"
        ]);
    }
}
class Child extends Parent {
    p1 = 0;
    p2 = "";
    p3 = false;
    p4 = [];
    p5 = {};

    constructor() {
        super();
    }

    m1() {}
    m2() {}
    m3() {}
    m4() {}
}
new Child()

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

Circle()
Square()
Triangle()

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

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

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

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

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

像这样的抽象接口

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