共计 2423 个字符,预计需要花费 7 分钟才能阅读完成。
导读 | 并不是说 JS 的类有问题,但是如果你使用该语言已有一段时间,特别是使用过 ES5,那么你可能就知道了从原型继承到当前类模型的演变。 |
以我的拙见,这个问题的答案是:没有。但是社区花了很多年的时间才将类的概念强加到不同的结构和库中,因此 ECMA 技术委员会决定无论如何都要添加它。
你会问,这有什么问题吗? 这就是他们真正做的,在我们已经拥有的原型继承之上添加了一些构成,并决定将其称为类,这反过来又让开发人员认为他们正在处理一种面向对象的语言,而实际上它们并不是。
jS 没有完全的 OOP 支持,它从来没有,这是因为它从来都不需要它。
表面上,当前版本的类显示 OOP 范例,因为:
- 我们可以创建基本的类定义,用非常经典的语法将状态和行为分组在一起。
- 我们可以从一个类继承到另一个类。
- 我们可以在公有和私有之间定义属性和方法的可见性 (尽管私有字段仍然是一个实验性的特性)。
- 我们可以为属性定义 getter 和 setter。
- 我们可以实例化类。
那么为什么我说类是语法糖呢? 因为尽管在表面上,它们看起来是非常面向对象的,但是如果我们试图做一些超出它们可能的事情,比如定义一个类扩展两个类 (目前不可能的事情),我们需要使用下面的代码
// 辅助函数
function applyMixins(derivedCtor, baseCtors) {
baseCtors.forEach(baseCtor => {Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {let descriptor = Object.getOwnPropertyDescriptor(baseCtor.prototype, name)
Object.defineProperty(derivedCtor.prototype, name, descriptor);
});
});
}
class A {methodA () {console.log('A')
}
}
class B {methodB () {console.log('B')
}
}
class C {
}
// 使用 mixins
我们需要这样做,因为在 JS 中我们无法编写:
class A {methodA(){console.log("A")
}
}
class B {methodB(){console.log("B")
}
}
class C extends A, B {}
在上面的示例中,关键部分应该是 applyMixins 函数。如果,你没有完全理解它试图做什么,但可以清楚地看到它正在访问所有类的原型属性来复制和重新分配方法和属性。这就是我们需要看到真相的地方: 类只不过是在经过验证的原型继承模型之上的语法糖。
这是否意味着我们应该停止使用类? 当然不是,重要的是要理解它,而且如果我们想做些突破类的限制,那么我们就必须用原型来处理。
如果我们当前的 OOP 模型是如此之薄,仅是原型继承的抽象层,那么我们到底缺少什么呢? 是什么让 JS 真正成为 OOP?
看这个问题的一个好方法就是看看 TypeScript 在做什么。该语言背后的团队通过创建一些可以翻译成 JS 的东西,无疑将 JS 推向了极限。这反过来也限制了它们的能力。
目前 JS 中缺失的一些 OOP 构造具有内在的类型检查功能,在动态类型语言中没有真正的意义,这可能是它们还没有被添加的原因。
接口可帮助定义类应遵循的 API。接口的主要好处之一是,我们可以定义实现相同接口的任何类的变量,然后安全地调用其任何方法。
interface Animal {speak()
}
class Dog implements Animal{speak() {console.log("Woof!")
}
}
class Cat implements Animal{speak() {console.log("Meau!")
}
}
class Human implements Animal{speak() {console.log("Hey dude, what's up?")
}
}
// 如果我们在 JS 中有接口,我们可以放心地做:
let objects = [new Dog(), new Cat(), new Human()]
objects.forEach(o => o.speak())
当然,我们可以通过定义 speak 方法并覆盖它的类来实现同样的目的,但接口更加清晰和优雅。
每当我尝试对我的代码进行完整的 OOP 操作时,我肯定会错过 JS 中的抽象类。抽象类是定义和实现方法的类,但永远不会实例化。这是一种可以扩展但从未直接使用的常见行为的分组方式。这是一个很好的资源,并且绝对可以在当前 JS 领域内实现而不会花费太多精力。
静态多态性使我们可以在相同的类中多次定义相同的方法,但是具有不同的签名。换句话说,重复该名称,但要确保其接收不同的参数。现在我们有了 JS 的 rest 参数,这使我们可以拥有一个任意数字,但是,这也意味着我们必须在方法中添加额外的代码来处理这种动态性。相反,我们可以更清楚地区分方法签名,则可以将相同行为的不同含义直接封装到不同方法中。
左边的版本不是有效的 JS,但它提供了一个更干净的代码,因此,阅读和理解起来比较容易。右边的版本是完全有效的,它阅读起来相对困难些,还要懂得一些 ES6 的语法。
多态性通常是通过查看方法中接收到的参数的类型来实现的。但是,由于 JS 的工作原理,我们知道这是不可能的。
我们已经有了公开的可见性,而且我们很快就得到了方法和属性的私有可见性 (通过 #前缀)。我认为下一步应该是添加受保护的可见性,然而,现在还没有,我认为如果你想要有一个合适的 OOP 体验,这三个都是必要的。受保护的属性和方法只能从类内部或它的一个子类中访问 (与私有可见性相反,私有可见性将访问限制为只能访问父类)。