JavaScript中的類有什么問題?
本文已經過原作者 Fernando Doglio 授權翻譯。
并不是說 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函數。如果,你沒有完全理解它試圖做什么,但可以清楚地看到它正在訪問所有類的原型屬性來復制和重新分配方法和屬性。這就是我們需要看到真相的地方:類只不過是在經過驗證的原型繼承模型之上的語法糖。
這是否意味著我們應該停止使用類?當然不是,重要的是要理解它,而且如果我們想做些突破類的限制,那么我們就必須用原型來處理。
JS 的OOP 模型缺失了什么呢?
如果我們當前的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體驗,這三個都是必要的。受保護的屬性和方法只能從類內部或它的一個子類中訪問(與私有可見性相反,私有可見性將訪問限制為只能訪問父類)。
今天就跟大家分享到這里了,我是小智,我們下期再見。
作者:Fernando Doglio 譯者:前端小智 來源:meidum
原文:https://blog.bitsrc.io/whats-wrong-with-javascript-s-classes-3378c73205af
本文轉載自微信公眾號「大遷世界」,可以通過以下二維碼關注。轉載本文請聯系大遷世界公眾號。