我正在学习如何用JavaScript制作面向对象编程。是否有接口概念(如Java的接口)?
这样我就可以创建一个监听器。
我正在学习如何用JavaScript制作面向对象编程。是否有接口概念(如Java的接口)?
这样我就可以创建一个监听器。
当前回答
通过接口,您可以实现一种多态方式。Javascript不需要接口类型来处理这个和其他接口的事情。为什么?Javascript是一种动态类型语言。以具有相同方法的类数组为例:
Circle()
Square()
Triangle()
如果你想知道多态是如何工作的,David krugman linsky的书MFC是很棒的(为c++编写的)。
在这些类中实现draw()方法,将这些类的实例推入数组中,并在迭代数组的循环中调用draw()方法。这是完全正确的。你可以说你隐式地实现了一个抽象类。它不存在于现实中,但在你的脑海中,你做到了,Javascript没有问题。真正的接口的不同之处在于,你必须实现所有的接口方法,在这种情况下,这是不需要的。
接口是一个契约。您必须实现所有的方法。只有让它是静态的,你才需要这样做。
把Javascript这样的语言从动态变成静态是有问题的。静止是不可取的。有经验的开发人员对Javascript的动态特性没有问题。
所以我不清楚使用Typescript的原因。如果你将NodeJS和Javascript结合使用,你可以建立非常高效和经济的企业网站。Javascript/NodeJS/MongoDB组合已经是伟大的赢家。
其他回答
JavaScript中没有本机接口, 有几种方法可以模拟接口。我已经写了一个包来做这件事
你可以看到这里的植入
这是一个老问题,但这个话题一直困扰着我。
由于这里和网络上的许多答案都专注于“强制”界面,我想提出另一种观点:
当我使用行为相似的多个类(即实现一个接口)时,我感到最缺乏接口。
例如,我有一个电子邮件生成器,期望接收电子邮件节工厂,“知道”如何生成节的内容和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没有接口类型,但经常需要它。由于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社区的特殊性中任何其他普遍或需要的断言
也就是说,我希望这对你有帮助,就像我和我的团队一样。
没有“这个类必须有这些函数”的概念(也就是说,本身没有接口),因为:
JavaScript继承基于对象,而不是类。这没什么大不了的,直到你意识到: JavaScript是一种非常动态类型的语言——您可以使用适当的方法创建一个对象,这将使它符合接口,然后取消定义所有使它符合接口的东西。颠覆类型系统是如此容易——甚至是意外地!一开始就尝试创建一个类型系统是不值得的。
相反,JavaScript使用所谓的鸭子类型。(如果它像鸭子一样走路,像鸭子一样嘎嘎叫,在JS看来,它就是一只鸭子。)如果您的对象具有quack()、walk()和fly()方法,那么代码可以在任何期望对象能够walk、quack和fly的地方使用它,而不需要实现某种“Duckable”接口。接口正是代码使用的函数集(以及这些函数的返回值),通过duck键入,您可以免费获得这些功能。
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.
但是,如果您确信您所做的事情是正确的,那么您可以在尝试使用特定方法之前通过测试它的存在来解决嘎嘎狗问题。类似的
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
所以你可以在使用之前检查所有你能用的方法。不过,语法有点难看。有一种稍微漂亮一点的方法:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
这是标准的JavaScript,所以它应该适用于任何值得使用的JS解释器。它还有像英语一样阅读的额外好处。
对于现代浏览器(也就是说,除了IE 6-8之外的任何浏览器),甚至有一种方法可以防止该属性在For…in中显示:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
问题是IE7对象根本没有. defineproperty,而在IE8中,它据称只作用于宿主对象(即DOM元素等)。如果兼容性是个问题,就不能使用. defineproperty。(我甚至不会提到IE6,因为它在中国以外的地方已经无关紧要了。)
另一个问题是,一些编码风格喜欢假设每个人都写不好的代码,并禁止修改Object。原型,以防有人盲目地使用for…in。如果你关心这个,或者正在使用(IMO坏了)的代码,尝试一个稍微不同的版本:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
试试这个方法:将接口描述为一个类,并使用@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();