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

Tom*_*ito 301 javascript oop

我正在学习如何使用JavaScript制作OOP.它是否具有接口概念(例如Java interface)?

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

cHa*_*Hao 600

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

  1. JavaScript继承基于对象,而不是类.在你意识到这一点之前,这不是什么大问题:
  2. JavaScript是一种极其动态类型的语言 - 您可以使用适当的方法创建一个对象,这将使其符合接口,然后取消定义使其符合的所有内容.破坏类型系统很容易 - 甚至意外! - 首先尝试制作类型系统是不值得的.

相反,JavaScript使用所谓的duck typing.(如果它像鸭子一样走路,像鸭子那样呱呱叫,就JS而言,它就是鸭子.)如果你的对象有quack(),walk()和fly()方法,代码可以在任何预期的地方使用它一个可以行走,嘎嘎叫和飞行的物体,而不需要实现一些"Duckable"接口.接口正是代码使用的函数集(以及这些函数的返回值),并且使用duck typing,您可以免费获得.

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

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

if (typeof(someObject.quack) == "function")
{
    // This thing can quack
}
Run Code Online (Sandbox Code Playgroud)

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

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

if (someObject.can("quack"))
{
    someObject.quack();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

问题是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();
}
Run Code Online (Sandbox Code Playgroud)

  • 它并不像它那样可怕.`for ... in`是 - 并且一直以来 - 充满了这样的危险,任何人都这样做,至少没有考虑到有人加入了'Object.prototype`(这种文章自己承认的并不罕见)会看到他们的代码在别人的手中打破. (7认同)
  • 嘿嘿......喜欢“坦率地说,如果你告诉狗嘎嘎叫,你的问题会稍微大一点。很好的类比表明语言不应该试图避免愚蠢。那总是一场失败的战斗。-斯科特 (3认同)
  • http://erik.eae.net/archives/2005/06/06/22.13.54/ Object.prototype是verboten (2认同)
  • @entonio:我认为内置类型的延展性是一个*特性*而不是一个问题。这是使 shims/polyfills 可行的很大一部分。没有它,我们要么用可能不兼容的子类型包装所有内置类型,要么等待通用浏览器支持(这可能永远不会出现,当浏览器不支持某些东西时,人们不使用它,因为浏览器不支持它)不支持)。因为可以修改内置类型,我们可以改为添加许多尚不存在的函数。 (2认同)
  • 定义接口是预期行为的文档。这个答案的重点是如果缺少一种或多种方法可能会出现什么问题。但是,传达特定实施/策略应该/需要做什么的好处却没有得到解决。javascript 中是否有一种可接受的方式来定义任何合规实现所期望提供的函数和/或属性集? (2认同)

BGe*_*sen 69

拿起Dustin Diaz的" JavaScript设计模式 " 副本.有几章专门通过Duck Typing实现JavaScript接口.这也是一个很好的阅读.但不,没有语言本地实现的接口,你必须鸭类型.

// 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
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*las 19

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

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

我写了一篇关于面向对象 的文章,使用我自己的符号如下:

// 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);
Run Code Online (Sandbox Code Playgroud)

有很多方法可以修饰这个特殊的猫,但这是我用于自己的接口实现的逻辑.我发现我更喜欢这种方法,它易于阅读和使用(如上所述).它确实意味着添加一个"工具"方法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;
}
Run Code Online (Sandbox Code Playgroud)

  • @StevendeSalas:呃.当你停止尝试将它当作面向类的语言时,JS实际上会非常干净.模仿类,接口等所需的所有垃圾......这些都会让你的大脑受到伤害.原型?一旦你停止对抗它们,真的很简单. (10认同)
  • 这种语法真的伤害了我的大脑,但这里的实现非常有趣. (3认同)
  • 当来自更清洁的OO语言实现时,Javascript必然会这样做(伤害大脑). (2认同)

Cod*_*ody 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
};
Run Code Online (Sandbox Code Playgroud)

参与者

解析器解析器

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

iAbstractClass

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

实施者

实现者只是简单地"同意"接口(在本例中为iAbstractClass)并通过使用Constructor-Hijacking来应用它:iAbstractClass.apply(this).通过定义上面的数据和行为,然后劫持 Interface的构造函数 - 将Implementor的上下文传递给Interface构造函数 - 我们可以确保将添加Implementor的覆盖,并且该接口将显示警告和默认值.

这是一个非常麻烦的方法,它为我和我的团队提供了很好的时间和不同的项目.但是,它确实有一些警告和缺点.

缺点

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

这似乎是"JavaScript中的接口"的最佳方法,但是,我希望看到以下解决方案:

  • 断言类型的断言
  • 签名的断言
  • delete操作中冻结对象
  • 断言JavaScript社区的特殊性中普遍存在或需要的任何其他内容

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


小智 6

像这样的抽象接口

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

创建一个实例:

function MyType() {
  this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
Run Code Online (Sandbox Code Playgroud)

并使用它

let x = new MyType()
x.print()
Run Code Online (Sandbox Code Playgroud)


Ale*_*ort 5

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

  • 不,Java实际上需要它们,以便它可以为在编译时实现接口的类构建vtable。声明一个类实现了一个接口,指示编译器构建一个小的结构,该结构包含指向该接口所需的所有方法的指针。否则,它将必须在运行时按名称分派(就像动态类型的语言一样)。 (3认同)
  • 实际上,您不需要 Java 中的接口,确保对象具有特定的 API 是一种安全措施,因此您可以将它们替换为其他实现。 (2认同)
  • @entonio:调度并不像看起来那样动态。由于多态性,直到运行时为止,实际的方法通常都不为人所知,但是字节码并没有说“ invoke yourMethod”。它说“调用Superclass.yourMethod”。JVM无法在不知道要查找哪个类的情况下调用方法。在链接期间,它可能会将`yourMethod`放置在`Superclass`'的vtable中的条目#5上,并且对于每个具有自己的`yourMethod'的子类`,只是将该子类的条目#5指向适当的实现。 (2认同)

sha*_*ich 5

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

您可以尝试使用代理服务器(这是ECMAScript 2015以来的标准设置):https : //developer.mozilla.org/zh-CN/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;
    }
});
Run Code Online (Sandbox Code Playgroud)

然后,您可以轻松地说:

myMap = {}
myMap.position = latLngLiteral;
Run Code Online (Sandbox Code Playgroud)


sha*_*ich 5

当您想使用转编译器时,您可以尝试一下 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"
}
Run Code Online (Sandbox Code Playgroud)

你不能做什么: