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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3710275/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:48:38  来源:igfitidea点击:

Does JavaScript have the interface type (such as Java's 'interface')?

javascriptoop

提问by Tom Brito

I'm learning how to make OOP with JavaScript. Does it have the interface concept (such as Java's interface)?

我正在学习如何使用 JavaScript 进行 OOP。有没有接口概念(比如Java的interface)?

So I would be able to create a listener...

所以我将能够创建一个监听器......

回答by cHao

There's no notion of "this class must have these functions" (that is, no interfaces per se), because:

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

  1. JavaScript inheritance is based on objects, not classes. That's not a big deal until you realize:
  2. JavaScript is an extremelydynamically typed language -- you can create an object with the proper methods, which would make it conform to the interface, and then undefine all the stuff that made it conform. It'd be so easy to subvert the type system -- even accidentally! -- that it wouldn't be worth it to try and make a type system in the first place.
  1. JavaScript 继承基于对象,而不是类。这没什么大不了的,直到你意识到:
  2. JavaScript 是一种非常动态的类型语言——您可以使用适当的方法创建一个对象,这将使其符合接口,然后取消定义所有使其符合的内容。颠覆类型系统是如此容易——即使是偶然的!-- 首先尝试创建类型系统是不值得的。

Instead, JavaScript uses what's called duck typing. (If it walks like a duck, and quacks like a duck, as far as JS cares, it's a duck.) If your object has quack(), walk(), and fly() methods, code can use it wherever it expects an object that can walk, quack, and fly, without requiring the implementation of some "Duckable" interface. The interface is exactly the set of functions that the code uses (and the return values from those functions), and with duck typing, you get that for free.

相反,JavaScript 使用所谓的鸭子类型。(如果它像鸭子一样走路,像鸭子一样嘎嘎叫,就 JS 而言,它就是一只鸭子。)如果您的对象具有 quack()、walk() 和 fly() 方法,则代码可以在它期望的任何地方使用它一个可以行走、嘎嘎叫和飞行的对象,不需要实现一些“Duckable”接口。接口正是代码使用的函数集(以及这些函数的返回值),通过鸭子输入,您可以免费获得这些。

Now, that's not to say your code won't fail halfway through, if you try to call some_dog.quack(); you'll get a TypeError. Frankly, if you're telling dogs to quack, you have slightly bigger problems; duck typing works best when you keep all your ducks in a row, so to speak, and aren't letting dogs and ducks mingle together unless you're treating them as generic animals. In other words, even though the interface is fluid, it's still there; it's often an error to pass a dog to code that expects it to quack and fly in the first place.

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

But if you're sure you're doing the right thing, you can work around the quacking-dog problem by testing for the existence of a particular method before trying to use it. Something like

但是,如果您确定自己在做正确的事情,则可以通过在尝试使用特定方法之前测试它是否存在来解决嘎嘎狗问题。就像是

if (typeof(someObject.quack) == "function")
{
    // This thing can quack
}

So you can check for all the methods you can use before you use them. The syntax is kind of ugly, though. There's a slightly prettier way:

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

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

if (someObject.can("quack"))
{
    someObject.quack();
}

This is standard JavaScript, so it should work in any JS interpreter worth using. It has the added benefit of reading like English.

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

For modern browsers (that is, pretty much any browser other than IE 6-8), there's even a way to keep the property from showing up in for...in:

对于现代浏览器(即除 IE 6-8 之外的几乎所有浏览器),甚至有一种方法可以防止该属性出现在for...in

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

The problem is that IE7 objects don't have .definePropertyat all, and in IE8, it allegedly only works on host objects (that is, DOM elements and such). If compatibility is an issue, you can't use .defineProperty. (I won't even mention IE6, because it's rather irrelevant anymore outside of China.)

问题是 IE7 对象根本没有.defineProperty,而在 IE8 中,据称它仅适用于主机对象(即 DOM 元素等)。如果兼容性是一个问题,则不能使用.defineProperty. (我什至不会提到 IE6,因为它在china之外已经无关紧要了。)

Another issue is that some coding styles like to assume that everyone writes bad code, and prohibit modifying Object.prototypein case someone wants to blindly use for...in. If you care about that, or are using (IMO broken) code that does, try a slightly different version:

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

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

if (can(someObject, "quack"))
{
    someObject.quack();
}

回答by BGerrissen

Pick up a copy of 'JavaScript design patterns' by Dustin Diaz. There's a few chapters dedicated to implementing JavaScript interfaces through Duck Typing. It's a nice read as well. But no, there's no language native implementation of an interface, you have to Duck Type.

获取一份Dustin Diaz所著的“ JavaScript 设计模式” 。有几章专门介绍通过 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
}

回答by Steven de Salas

JavaScript (ECMAScript edition 3) has an implementsreserved word saved up for future use. I think this is intended exactly for this purpose, however, in a rush to get the specification out the door they didn't have time to define what to do with it, so, at the present time, browsers don't do anything besides let it sit there and occasionally complain if you try to use it for something.

JavaScript(ECMAScript 版本 3)有一个implements保留字供将来使用。我认为这正是为此目的而设计的,但是,急于将规范发布出去,他们没有时间定义如何处理它,因此,目前,浏览器除了让它坐在那里,如果您尝试将其用于某些目的,偶尔会抱怨。

It is possible and indeed easy enough to create your own Object.implement(Interface)method with logic that baulks whenever a particular set of properties/functions are not implemented in a given object.

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

I wrote an article on object-orientationwhere 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);

There are many ways to skin this particular cat, but this is the logic I used for my own Interface implementation. I find I prefer this approach, and it is easy to read and use (as you can see above). It does mean adding an 'implement' method to Function.prototypewhich some people may have a problem with, but I find it works beautifully.

有很多方法可以给这只特定的猫剥皮,但这是我用于我自己的接口实现的逻辑。我发现我更喜欢这种方法,而且它易于阅读和使用(如您所见)。这确实意味着添加一个“实现”方法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;
}

回答by Cody

JavaScript Interfaces:

JavaScript 接口:

Though JavaScript does nothave the interfacetype, it is often times needed. For reasons relating to JavaScript's dynamic nature and use of Prototypical-Inheritance, it is difficult to ensure consistent interfaces across classes -- however, it is possible to do so; and frequently emulated.

虽然JavaScript并没有interface型,它往往是需要时间。由于与 JavaScript 的动态特性和原型继承的使用相关的原因,很难确保跨类的接口一致——然而,这样做是可能的;并且经常被模仿。

At this point, there are handfuls of particular ways to emulate Interfaces in JavaScript; variance on approaches usually satisfies some needs, while others are left unaddressed. Often times, the most robust approach is overly cumbersome and stymies the implementor (developer).

在这一点上,有一些特殊的方法可以在 JavaScript 中模拟接口;方法上的差异通常可以满足某些需求,而其他需求则没有得到解决。很多时候,最健壮的方法过于繁琐并且阻碍了实现者(开发者)。

Here is an approach to Interfaces / Abstract Classes that is not very cumbersome, is explicative, keeps implementations inside of Abstractions to a minimum, and leaves enough room for dynamic or custom methodologies:

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

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

Participants

参与者

Precept Resolver

戒律解析器

The resolvePreceptfunction is a utility & helper function to use inside of your Abstract Class. Its job is to allow for customized implementation-handling of encapsulated Precepts (data & behavior). It can throw errors or warn -- AND -- assign a default value to the Implementor class.

resolvePrecept函数是一个实用程序和辅助函数,可在您的Abstract Class 中使用。它的工作是允许对封装的Precepts(数据和行为)进行定制的实现处理。它可以抛出错误或警告——并且——为Implementor 类分配一个默认值。

iAbstractClass

抽象类

The iAbstractClassdefines the interface to be used. Its approach entails a tacit agreement with its Implementor class. This interface assigns each preceptto the same exact precept namespace -- OR -- to whatever the Precept Resolverfunction returns. However, the tacit agreement resolves to a context-- a provision of Implementor.

iAbstractClass定义要使用的接口。它的方法需要与它的Implementor 类达成默契。该接口将每个戒律分配给相同的戒律命名空间——或者——分配给戒律解析器函数返回的任何内容。然而,默契解决了一个上下文——一个Implementor 的规定。

Implementor

实施者

The Implementor simply 'agrees' with an Interface (iAbstractClassin this case) and applies it by the use of Constructor-HiHymaning: iAbstractClass.apply(this). By defining the data & behavior above, and then hiHymaningthe Interface's constructor -- passing Implementor's context to the Interface constructor -- we can ensure that Implementor's overrides will be added, and that Interface will explicate warnings and default values.

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

This is a very non-cumbersome approach which has served my team & I very well for the course of time and different projects. However, it does have some caveats & drawbacks.

这是一种非常不麻烦的方法,在时间和不同项目的过程中,它为我的团队和我提供了很好的服务。但是,它确实有一些警告和缺点。

Drawbacks

缺点

Though this helps implement consistency throughout your software to a significant degree, it does not implement true interfaces-- but emulates them. Though definitions, defaults, and warnings or errors areexplicated, the explication of use is enforced & assertedby the developer (as with much of JavaScript development).

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

This is seemingly the best approach to "Interfaces in JavaScript", however, I would love to see the following resolved:

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

  • Assertions of return types
  • Assertions of signatures
  • Freeze objects from deleteactions
  • Assertions of anything else prevalent or needed in the specificity of the JavaScript community
  • 返回类型的断言
  • 签名的断言
  • delete动作冻结对象
  • 对 JavaScript 社区特殊性中普遍存在或需要的任何其他内容的断言

That said, I hope this helps you as much as it has my team and I.

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

回答by Alex Reitbort

You need interfaces in Java since it is statically typed and the contract between classes should be known during compilation. In JavaScript it is different. JavaScript is dynamically typed; it means that when you get the object you can just check if it has a specific method and call it.

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

回答by shaedrich

Hope, that anyone who's still looking for an answer finds it helpful.

希望,任何仍在寻找答案的人都会发现它有帮助。

You can try out using a Proxy (It's standard since ECMAScript 2015): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

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

Then you can easily say:

然后你可以很容易地说:

myMap = {}
myMap.position = latLngLiteral;

回答by shaedrich

When you want to use a transcompiler, then you could give TypeScript a try. It supports draft ECMA features (in the proposal, interfaces are called "protocols") similar to what languages like coffeescript or babel do.

当您想使用转编译器时,您可以尝试一下 TypeScript。它支持草案 ECMA 特性(在提案中,接口被称为“协议”),类似于像 coffeescript 或 babel 这样的语言所做的。

In TypeScript your interface can look like:

在 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"
}

What you can't do:

你不能做什么:

回答by Amit Wagner

there is no native interfaces in JavaScript, there are several ways to simulate an interface. i have written a package that does it

JavaScript 中没有原生接口,有几种方法可以模拟接口。我写了一个包

you can see the implantation here

你可以在这里看到植入

回答by Reinsbrain

Javascript does not have interfaces. But it can be duck-typed, an example can be found here:

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

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

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

回答by GalAbra

This is an old question, nevertheless this topic never ceases to bug me.

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

As many of the answers here and across the web focus on "enforcing" the interface, I'd like to suggest an alternative view:

由于此处和网络上的许多答案都集中在“强制执行”界面上,因此我想提出另一种观点:

I feel the lack of interfaces the most when I'm using multiple classes that behave similarly (i.e. implement an interface).

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

For example, I have an Email Generatorthat expects to receive Email Sections Factories, that "know" how to generate the sections' content and HTML. Hence, they all need to have some sort of getContent(id)and getHtml(content)methods.

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

The closest pattern to interfaces (albeit it's still a workaround) I could think of is using a class that'll get 2 arguments, which will define the 2 interface methods.

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

The main challenge with this pattern is that the methods either have to be static, or to get as argument the instance itself, in order to access its properties. However there are cases in which I find this trade-off worth the hassle.

这种模式的主要挑战是方法要么必须是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));