此页面比较了 JavaScript 和 Scala 编程语言。它面向了解 JavaScript 并希望了解 Scala 的程序员,具体是通过查看 JavaScript 语言功能与 Scala 的比较示例。
概述
本节提供了后续章节的相对简要介绍和总结。它从高层次介绍了 JavaScript 和 Scala 之间的相似性和差异,然后介绍了编写代码时每天都会遇到的差异。
高层次相似性
从高层次来看,Scala 与 JavaScript 具有以下相似性
- 两者都被认为是高级编程语言,你无需关注指针和手动内存管理等低级概念
- 两者都有相对简单、简洁的语法
- 两者都支持 C/C++/Java 风格的大括号语法来编写方法和其他代码块
- 两者都包含面向对象编程 (OOP) 的功能(如类)
- 两者都包含(例如 lambda)用于 函数式编程(FP)的功能
- JavaScript 在浏览器和其他环境(例如 Node.js)中运行。Scala 的 Scala.js 版本针对 JavaScript,因此 Scala 程序可以在相同环境中运行。
- 开发人员使用 Node.js 用 JavaScript 和 Scala 编写服务器端应用程序;类似于 Play Framework 的项目也允许您用 Scala 编写服务器端应用程序
- 两种语言都有类似的
if
语句、while
循环和for
循环 - 从 此 Scala.js 页面 开始,您会找到几十个库来支持 React、Angular、jQuery 和许多其他 JavaScript 和 Scala 库
- JavaScript 对象是可变的;Scala 对象在以命令式风格编写时可以是可变的
- JavaScript 和 Scala 都支持promise,作为处理异步计算结果的一种方式(Scala 并发使用 future 和 promise)
高级差异
同样在高级别上,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
- 本书中你可以阅读到的许多其他优点:case 类、伴随类和对象、宏、并集 和 交集 类型、多个参数列表、命名参数等
变量和类型
注释
//
|
//
|
可变变量
let // 现在首选用于可变
|
var // 用于可变变量
|
不可变值
const
|
val
|
Scala 中的经验法则是使用 val
声明变量,除非你有特定原因需要可变变量。
命名标准
JavaScript 和 Scala 通常使用相同的驼峰式命名标准。变量命名为 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 既有类也有 case 类。类类似于 JavaScript 类,通常用于 OOP 样式应用程序(尽管它们也可以用于 FP 代码),而 case 类具有其他功能,使其在 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 使用特征作为接口,也用于创建 mixin。特征既可以有抽象成员,也可以有具体成员,包括方法和字段。
此示例展示了如何定义两个特征,创建一个扩展并实现这些特征的类,然后创建并使用该类的实例
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
表达式
让 minVal = a < b ? a : b;
|
val minValue = 如果 a < b 则 a 否则 b
|
if
作为方法的主体
Scala 方法往往很短,你可以轻松地使用 if
作为方法的主体
函数 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
循环
让 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
循环中的多个生成器
让 str = "ab";
|
for
|
带守卫的生成器
守卫是 for
表达式中 if
表达式的名称。
for (let i = 0; i < 10; i++) {
|
for
|
for
推导
for
推导是一种 for
循环,它使用 yield
返回(生成)一个值。它们在 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 针对不同的需求提供了不同的 集合类。
常见的不可变序列是
List
Vector
常见的可变序列是
Array
ArrayBuffer
Scala 还有可变和不可变的 Maps 和 Sets。
以下是创建常见的 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 允许你编写编译为 JavaScript 代码的 Scala 代码,然后可以在浏览器中使用该代码。该方法类似于 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 中没有等效的概念