此頁面提供 JavaScript 和 Scala 程式語言的比較。它適用於了解 JavaScript 並想學習 Scala 的程式設計師,特別是透過查看 JavaScript 語言功能與 Scala 的比較範例。
概觀
本節提供後續各節的簡要介紹和摘要。它在高層級介紹 JavaScript 和 Scala 之間的相似性和差異,然後介紹您在撰寫程式碼時每天會遇到的差異。
高層級相似性
在高層級上,Scala 與 JavaScript 有下列相似性
- 兩者都被視為高層級程式語言,您不必擔心指標和手動記憶體管理等低層級概念
- 兩者都有相對簡單、簡潔的語法
- 兩者都支援 C/C++/Java 風格的大括號語法,用於撰寫方法和其他程式碼區塊
- 兩者都包含物件導向程式設計 (OOP) 的功能(例如類別)
- 兩者都包含 函式式程式設計 (FP) 的功能(例如 lambda)
- JavaScript 在瀏覽器和其他環境(例如 Node.js)中執行。因此,Scala.js 風格的 Scala 目標為 JavaScript,而 Scala 程式可以執行於相同的環境中。
- 開發人員使用 Node.js 以 JavaScript 和 Scala 編寫伺服器端應用程式;Play Framework 等專案也讓您能以 Scala 編寫伺服器端應用程式
- 兩種語言都有類似的
if
陳述式、while
迴圈和for
迴圈 - 從 此 Scala.js 頁面 開始,您會找到數十個支援 React、Angular、jQuery 和許多其他 JavaScript 和 Scala 函式庫
- JavaScript 物件是可變的;Scala 物件在以命令式風格撰寫時可以是可變的
- JavaScript 和 Scala 都支援承諾,作為處理非同步運算結果的方法(Scala 並行處理使用 future 和承諾)
高階差異
此外,在高階層面,JavaScript 和 Scala 之間的一些差異如下
- JavaScript 是動態型別,而 Scala 是靜態型別
- 儘管 Scala 是靜態型別,但型別推論等功能讓它感覺像動態語言(正如您將在以下範例中看到的)
- Scala 慣用語預設偏好不可變性:建議您使用不可變變數和不可變集合
- Scala 具有簡潔但可讀的語法;我們稱之為表達性
- Scala 是純 OOP 語言,因此每個物件都是類別的實例,而像
+
和+=
這些看起來像運算子的符號實際上是方法;這表示您可以建立自己的方法,作為運算子運作 - 作為純 OOP 語言和純 FP 語言,Scala 鼓勵 OOP 和 FP 的融合,使用函式進行邏輯處理,並使用不可變物件進行模組化
- Scala 擁有最先進的第三方開源函式程式庫
- Scala 中的一切都是表達式:
if
陳述式、for
迴圈、match
表達式,甚至try
/catch
表達式等建構式,都具有傳回值 - Scala Native 專案讓您可以撰寫「系統」層級程式碼,並編譯為原生可執行檔
程式設計層級差異
在較低層級,以下是你撰寫程式碼時每天會看到的差異
- Scala 變數和參數使用
val
(不可變,就像 JavaScriptconst
)或var
(可變,就像 JavaScriptvar
或let
)定義 - Scala 不使用分號作為行尾
- Scala 是靜態型別,儘管在許多情況下你不需要宣告型別
- Scala 使用特質作為介面並建立混入
- 除了簡單的
for
迴圈,Scala 還有強大的for
理解,根據你的演算法產生結果 - 模式配對和
match
表達式會改變你撰寫程式碼的方式 - Scala 的 情境抽象 和型別推論提供一系列功能
- 由於按名稱參數、中綴表示法、可選括號、擴充方法和 高階函式 等功能,你可以建立自己的「控制結構」和 DSL
- 本書中你可以閱讀到的許多其他好處:案例類別、伴隨類別和物件、巨集、聯集 和 交集 型別、多個參數清單、命名參數等等
變數和類型
註解
//
|
//
|
可變變數
let // 現在優先用於可變變數
|
var // 用於可變變數
|
不可變值
const
|
val
|
Scala 的經驗法則是用 val
宣告變數,除非有特定原因需要可變變數。
命名標準
JavaScript 和 Scala 通常使用相同的 CamelCase 命名標準。變數命名為 myVariableName
,方法命名為 lastIndexOf
,類別和物件命名為 Animal
和 PrintedBook
。
字串
字串的許多用法在 JavaScript 和 Scala 中很相似,儘管 Scala 僅對單行字串使用雙引號,對多行字串使用三引號。
字串基礎
// 使用單引號或雙引號
|
// 僅使用雙引號
|
內插
let name = 'Joe';
|
val name = "Joe"
|
帶有內插的多行字串
let name = "joe";
|
val name = "Martin Odersky"
|
JavaScript 和 Scala 也有類似的字串處理方法,包括 charAt
、concat
、indexOf
,以及更多方法。跳脫字元,例如 \n
、\f
、\t
,在這兩種語言中也是相同的。
數字和算術
JavaScript 和 Scala 的數字運算子類似。最大的差異是 Scala 沒有提供 ++
和 --
運算子。
數字運算子
let x = 1;
|
val x = 1
|
遞增和遞減
i++;
|
i += 1;
|
最大的差異可能是,像 +
和 -
這樣的「運算子」在 Scala 中實際上是方法,而不是運算子。Scala 數字也有這些相關方法
var a = 2
a *= 2 // 4
a /= 2 // 2
Scala 的 Double
類型最接近 JavaScript 的預設 number
類型,Int
表示有符號的 32 位元整數值,而 BigInt
則對應於 JavaScript 的 bigint
。
這些是 Scala Int
和 Double
值。請注意,類型不必明確宣告
val i = 1 // Int
val d = 1.1 // Double
您也可以視需要使用其他數值類型
val a: Byte = 0 // Byte = 0
val a: Double = 0 // Double = 0.0
val a: Float = 0 // Float = 0.0
val a: Int = 0 // Int = 0
val a: Long = 0 // Long = 0
val a: Short = 0 // Short = 0
val x = BigInt(1_234_456_789)
val y = BigDecimal(1_234_456.890)
布林值
兩種語言都使用 true
和 false
作為布林值
let a = true;
|
val a = true
|
日期
日期是兩種語言中另一種常用的類型。
取得目前日期
let d = new Date();
|
// 取得目前日期和時間的不同方法
|
指定不同的日期
let d = Date(2020, 1, 21, 1, 0, 0, 0);
|
val d = LocalDate.of(2020, 1, 21)
|
在這種情況下,Scala 使用 Java 附帶的日期和時間類別。許多日期/時間方法在 JavaScript 和 Scala 之間類似。請參閱 java.time 套件 以取得更多詳細資訊。
函數
在 JavaScript 和 Scala 中,函數都是物件,因此它們的功能類似,但其語法和術語略有不同。
命名函數,一行
function add(a, b) {
|
// 技術上來說這是一個方法,而不是函數
|
命名函數,多行
function addAndDouble(a, b) {
|
def addAndDouble(a: Int, b: Int): Int =
|
在 Scala 中,顯示 Int
回傳類型是可選的。它在 add
範例中沒有顯示,而在 addAndDouble
範例中有顯示,因此你可以看到兩種方法。
匿名函數
JavaScript 和 Scala 都允許你定義匿名函數,你可以將它們傳遞到其他函數和方法中。
箭頭函數和匿名函數
// 箭頭函數
|
// 函數(指派給變數的匿名函數)
|
在 Scala 中,你很少使用顯示的第一個語法定義函數。相反,你通常在使用點定義匿名函數。許多集合方法是 高階函數,並接受函數參數,因此你可以撰寫類似這樣的程式碼
// map method, long form
List(1,2,3).map(i => i * 10) // List(10,20,30)
// map, short form (which is more commonly used)
List(1,2,3).map(_ * 10) // List(10,20,30)
// filter, short form
List(1,2,3).filter(_ < 3) // List(1,2)
// filter and then map
List(1,2,3,4,5).filter(_ < 3).map(_ * 10) // List(10, 20)
類別
Scala 同時具有類別和案例類別。類別類似於 JavaScript 類別,通常用於 OOP 風格應用程式(儘管它們也可以用於 FP 程式碼),而案例類別具有其他功能,使其在 FP 風格應用程式 中非常有用。
下列範例顯示如何建立多種類型作為列舉,然後定義 OOP 風格的 Pizza
類別。最後,建立一個 Pizza
實例並使用它
// create some enumerations that the Pizza class will use
enum CrustSize:
case Small, Medium, Large
enum CrustType:
case Thin, Thick, Regular
enum Topping:
case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions
// import those enumerations and the ArrayBuffer,
// so the Pizza class can use them
import CrustSize.*
import CrustType.*
import Topping.*
import scala.collection.mutable.ArrayBuffer
// define an OOP style Pizza class
class Pizza(
var crustSize: CrustSize,
var crustType: CrustType
):
private val toppings = ArrayBuffer[Topping]()
def addTopping(t: Topping): Unit =
toppings += t
def removeTopping(t: Topping): Unit =
toppings -= t
def removeAllToppings(): Unit =
toppings.clear()
override def toString(): String =
s"""
|Pizza:
| Crust Size: ${crustSize}
| Crust Type: ${crustType}
| Toppings: ${toppings}
""".stripMargin
end Pizza
// create a Pizza instance
val p = Pizza(Small, Thin)
// change the crust
p.crustSize = Large
p.crustType = Thick
// add and remove toppings
p.addTopping(Cheese)
p.addTopping(Pepperoni)
p.addTopping(BlackOlives)
p.removeTopping(Pepperoni)
// print the pizza, which uses its `toString` method
println(p)
介面、特質和繼承
Scala 使用特質作為介面,也用於建立混入。特質可以同時具有抽象和具體成員,包括方法和欄位。
此範例顯示如何定義兩個特質,建立一個擴充和實作這些特質的類別,然後建立和使用該類別的實例
trait HasLegs:
def numLegs: Int
def walk(): Unit
def stop() = println("Stopped walking")
trait HasTail:
def wagTail(): Unit
def stopTail(): Unit
class Dog(var name: String) extends HasLegs, HasTail:
val numLegs = 4
def walk() = println("I’m walking")
def wagTail() = println("⎞⎜⎛ ⎞⎜⎛")
def stopTail() = println("Tail is stopped")
override def toString = s"$name is a Dog"
// create a Dog instance
val d = Dog("Rover")
// use the class’s attributes and behaviors
println(d.numLegs) // 4
d.wagTail() // "⎞⎜⎛ ⎞⎜⎛"
d.walk() // "I’m walking"
控制結構
除了 JavaScript 中使用 ===
和 !==
之外,JavaScript 和 Scala 中的比較和邏輯運算子幾乎相同。
比較運算子
JavaScript | Scala |
---|---|
== |
== |
=== |
== |
!= |
!= |
!== |
!= |
> |
> |
< |
< |
>= |
>= |
<= |
<= |
邏輯運算子
JavaScript | Scala |
---|---|
&&
|
&&
|
if/then/else 表達式
JavaScript 和 Scala 的 if/then/else 陳述式類似。在 Scala 2 中,它們幾乎相同,但在 Scala 3 中,不再需要大括號(儘管仍然可以使用)。
if
陳述式,一行
if (x == 1) { console.log(1); }
|
if x == 1 then println(x)
|
if
陳述式,多行
if (x == 1) {
|
if x == 1 then
|
if、else if、else
if (x < 0) {
|
if x < 0 then
|
從 if
傳回值
JavaScript 使用三元運算子,而 Scala 則照常使用其 if
表達式
let minVal = a < b ? a : b;
|
val minValue = if a < b then a else b
|
if
作為方法主體
Scala 方法傾向於非常簡短,而且你可以輕鬆地使用 if
作為方法主體
function min(a, b) {
|
def min(a: Int, b: Int): Int =
|
在 Scala 3 中,如果你願意,仍然可以使用「大括號」樣式。例如,你可以這樣撰寫 if/else-if/else 表達式
if (i == 0) {
println(0)
} else if (i == 1) {
println(1)
} else {
println("other")
}
迴圈
JavaScript 和 Scala 都具有 while
迴圈和 for
迴圈。Scala 以前有 do/while 迴圈,但它們已從語言中移除。
while
迴圈
let i = 0;
|
var i = 0;
|
如果您偏好,Scala 程式碼也可以寫成這樣
var i = 0
while (i < 3) {
println(i)
i += 1
}
下列範例顯示 JavaScript 和 Scala 中的 for
迴圈。它們假設您有下列集合可以使用
// JavaScript
let nums = [1, 2, 3];
// Scala
val nums = List(1, 2, 3)
for
迴圈,單行
// 較新的語法
|
// 偏好的
|
for
迴圈,主體中有多行
// 偏好的
|
// 偏好的
|
for
迴圈中有多個產生器
let str = "ab";
|
for
|
帶有防護的產生器
防護是 for
表達式內 if
表達式的名稱。
for (let i = 0; i < 10; i++) {
|
for
|
for
理解
for
理解是使用 yield
傳回 (產生) 值的 for
迴圈。它們常在 Scala 中使用。
不適用 |
val list =
|
switch 和 match
JavaScript 有 switch
陳述式,Scala 則有 match
表達式。與 Scala 中的其他所有事物一樣,它們確實是表達式,表示它們會傳回結果
val day = 1
// later in the code ...
val monthAsString = day match
case 1 => "January"
case 2 => "February"
case _ => "Other"
match
表達式可以在每個 case
陳述式中處理多個比對。
val numAsString = i match
case 1 | 3 | 5 | 7 | 9 => "odd"
case 2 | 4 | 6 | 8 | 10 => "even"
case _ => "too big"
它們也可以用作方法的主體
def isTruthy(a: Matchable) = a match
case 0 | "" => false
case _ => true
def isPerson(x: Matchable): Boolean = x match
case p: Person => true
case _ => false
match
表達式有許多其他模式比對選項。
集合類別
Scala 有不同的 集合類別 來滿足不同的需求。
常見的不可變序列為
清單
向量
常見的可變序列為
陣列
陣列緩衝區
Scala 也有可變和不可變的對應和集合。
以下是建立常見 Scala 集合類型的步驟
val strings = List("a", "b", "c")
val strings = Vector("a", "b", "c")
val strings = ArrayBuffer("a", "b", "c")
val set = Set("a", "b", "a") // result: Set("a", "b")
val map = Map(
"a" -> 1,
"b" -> 2,
"c" -> 3
)
集合方法
以下範例展示許多使用 Scala 集合的不同方式。
填充清單
// to, until
(1 to 5).toList // List(1, 2, 3, 4, 5)
(1 until 5).toList // List(1, 2, 3, 4)
(1 to 10 by 2).toList // List(1, 3, 5, 7, 9)
(1 until 10 by 2).toList // List(1, 3, 5, 7, 9)
(1 to 10).by(2).toList // List(1, 3, 5, 7, 9)
('d' to 'h').toList // List(d, e, f, g, h)
('d' until 'h').toList // List(d, e, f, g)
('a' to 'f').by(2).toList // List(a, c, e)
// range method
List.range(1, 3) // List(1, 2)
List.range(1, 6, 2) // List(1, 3, 5)
List.fill(3)("foo") // List(foo, foo, foo)
List.tabulate(3)(n => n * n) // List(0, 1, 4)
List.tabulate(4)(n => n * n) // List(0, 1, 4, 9)
序列函數方法
// these examples use a List, but they’re the same with Vector
val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10)
a.contains(20) // true
a.distinct // List(10, 20, 30, 40)
a.drop(2) // List(30, 40, 10)
a.dropRight(2) // List(10, 20, 30)
a.dropWhile(_ < 25) // List(30, 40, 10)
a.filter(_ < 25) // List(10, 20, 10)
a.filter(_ > 100) // List()
a.find(_ > 20) // Some(30)
a.head // 10
a.headOption // Some(10)
a.init // List(10, 20, 30, 40)
a.last // 10
a.lastOption // Some(10)
a.slice(2,4) // List(30, 40)
a.tail // List(20, 30, 40, 10)
a.take(3) // List(10, 20, 30)
a.takeRight(2) // List(40, 10)
a.takeWhile(_ < 30) // List(10, 20)
// map, flatMap
val fruits = List("apple", "pear")
fruits.map(_.toUpperCase) // List(APPLE, PEAR)
fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R)
val nums = List(10, 5, 8, 1, 7)
nums.sorted // List(1, 5, 7, 8, 10)
nums.sortWith(_ < _) // List(1, 5, 7, 8, 10)
nums.sortWith(_ > _) // List(10, 8, 7, 5, 1)
List(1,2,3).updated(0,10) // List(10, 2, 3)
List(2,4).union(List(1,3)) // List(2, 4, 1, 3)
// zip
val women = List("Wilma", "Betty") // List(Wilma, Betty)
val men = List("Fred", "Barney") // List(Fred, Barney)
val couples = women.zip(men) // List((Wilma,Fred), (Betty,Barney))
Scala 有許多其他可供你使用的函數方法。所有這些函數方法的優點如下:
- 你不需要撰寫自訂
for
迴圈來解決問題 - 當你閱讀別人的程式碼時,你不需要閱讀他們的自訂
for
迴圈;你只要找到像這些的常見函數方法即可,因此可以更輕鬆地閱讀不同專案的程式碼
元組
當你想要將多個資料類型放入同一個清單時,JavaScript 讓你這樣做
let stuff = ["Joe", 42, 1.0];
在 Scala 中,你這樣做
val a = ("eleven")
val b = ("eleven", 11)
val c = ("eleven", 11, 11.0)
val d = ("eleven", 11, 11.0, Person("Eleven"))
在 Scala 中,這些類型稱為元組,如所示,它們可以包含一個或多個元素,而且元素可以有不同的類型。你存取它們的元素的方式就像存取 List
、Vector
或 Array
的元素一樣
d(0) // "eleven"
d(1) // 11
列舉
JavaScript 沒有列舉,但你可以這樣做
let Color = {
RED: 1,
GREEN: 2,
BLUE: 3
};
Object.freeze(Color);
在 Scala 3 中,你可以使用列舉執行許多事情。你可以建立等同於該程式碼的程式碼
enum Color:
case Red, Green, Blue
你可以建立參數化列舉
enum Color(val rgb: Int):
case Red extends Color(0xFF0000)
case Green extends Color(0x00FF00)
case Blue extends Color(0x0000FF)
你也可以建立使用者定義的列舉成員
enum Planet(mass: Double, radius: Double):
case Mercury extends Planet(3.303e+23, 2.4397e6)
case Venus extends Planet(4.869e+24,6.0518e6)
case Earth extends Planet(5.976e+24,6.37814e6)
// more planets here ...
private final val G = 6.67300E-11
def surfaceGravity = G * mass / (radius * radius)
def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity
Scala.js DOM 程式碼
Scala.js 讓你撰寫 Scala 程式碼,並編譯成 JavaScript 程式碼,然後可以在瀏覽器中使用。此方法類似於 TypeScript、ReScript 和其他編譯成 JavaScript 的語言。
包含必要的函式庫,並匯入專案中必要的套件後,撰寫 Scala.js 程式碼看起來與撰寫 JavaScript 程式碼非常類似
// show an alert dialog on a button click
jQuery("#hello-button").click{() =>
dom.window.alert("Hello, world")
}
// define a button and what should happen when it’s clicked
val btn = button(
"Click me",
onclick := { () =>
dom.window.alert("Hello, world")
})
// create two divs with css classes, an h2 element, and the button
val content =
div(cls := "foo",
div(cls := "bar",
h2("Hello"),
btn
)
)
// add the content to the DOM
val root = dom.document.getElementById("root")
root.innerHTML = ""
root.appendChild(content.render)
請注意,儘管 Scala 是類型安全的語言,但上述程式碼中並未宣告任何類型。Scala 強大的類型推論功能通常會讓 Scala 程式碼看起來像是動態類型。但它具有類型安全性,因此您可以在開發週期早期就捕捉到許多類型的錯誤。
其他 Scala.js 資源
Scala.js 網站有許多教學課程,專為有興趣使用 Scala.js 的 JavaScript 開發人員設計。以下是其中一些入門教學課程
- 基礎教學課程(建立第一個 Scala.js 專案)
- 適用於 JavaScript 開發人員的 Scala.js
- 從 ES6 到 Scala:基礎
- 從 ES6 到 Scala:集合
- 從 ES6 到 Scala:進階
Scala 中獨有的概念
Scala 中還有其他目前在 JavaScript 中沒有等效項的概念