JavaScript 并不是最好的语言,特别是在复杂的应用中,它可能不太能胜任。为了避免这种情况,一些新的语言或现有语言的编译器被创造出来,不用写一行 JavaScript 或者考虑这种语言的局限,就能生产在浏览器能运行的代码。
本文介绍几种能够编译为 JavaScript 的语言,可以在浏览器或者 Node.js 中执行。
从代码简洁的角度考虑的话,个人还是比较喜欢 ClojureScript ,可以用简短的代码实现复杂的逻辑。
而想尝试 TypeScript 来开发最新的项目,是因为看到报道说在谷歌到处可以看到 TypeScript 的身影,也想体验下 TypeScript 项目开发。
1. TypeScript
官方网站:Get started with TypeScript
TypeScript 是一种由微软开发的自由和开源的编程语言,是 JavaScript 的超集;一个有效的 JavaScript 项目也是一个有效的 TypeScript 项目只是添加了静态类型。编译器也可以作为ES2015+到当前实现的转译器,这样你总是能得到最新的特性。
不同于其他语言,TypeScript 保持了JavaScript 完整的精神,只是此外添加了增加代码可靠性的功能。这些功能就是类型注释和其他类型相关的功能,得益于专业工具像是静态分析器和其他工具在重构过程的加入,这些功能使写 JavaScript 更加有趣。并且,类型的加入改善了你的应用不同组件之间的接口。
类型诊断是支持性的,你不必从一开始就写所有的类型。你可以先快速的写代码,然后再加入类型来让代码更稳定。
TypeScript同样也支持高级类型,像是交叉类型,联合类型,类型别名,可辨识联合和类型保护。可以在 TypeScript 网站的 Advanced Types 页面查看。
如果你使用React的话,通过添加React类型,JSX也是支持的。
class Person { private name: string; private age: number; private salary: number; constructor(name: string, age: number, salary: number) { this.name = name; this.age = age; this.salary = salary; } toString(): string { return `${this.name} (${this.age}) (${this.salary})`; } }
2. ClojureScript
官方网站:Get started with ClojureScript
ClojureScript 是将 Clojure 编程语言转换为 JavaScript 的编译器。它是一种通用的函数式语言,支持动态类型和不可变数据结构。
它是这个列表中唯一属于 Lisp 家族系列的编程语言,当然,它拥有很多功能。 例如,代码可以被视为数据,并且支持宏系统,这使得元编程技术成为可能。与其他 Lisps 不同的是,Clojure 支持不可变的数据结构,使副作用的管理更加容易。
对于新手来说,它的语法使用括号可能看起来很恐怖,但是这样做有着深刻的意义,从长远来看,你一定会感谢这种做法。语法上的简约性和语法抽象能力使 Lisp 成为解决那些需要高抽象层次的问题的强大工具。
虽然 Clojure 主要是函数式语言,但它不像 PureScript 或 Elm 那样纯粹;副作用仍然可以发生,但其他函数式特征仍然存在。
ClojureScript 使用 Google Closure 进行代码优化,并且与现有的 JavaScript 库兼容。
(ns dom.test (:require [clojure.browser.event :as event] [clojure.browser.dom :as dom])) (defn log [& args] (.log js/console (apply pr-str args))) (defn log-obj [obj] (.log js/console obj)) (defn log-listener-count [] (log "listener count: " (event/total-listener-count))) (def source (dom/get-element "source")) (def destination (dom/get-element "destination")) (dom/append source (dom/element "Testing me ") (dom/element "out!")) (def success-count (atom 0)) (log-listener-count) (event/listen source :click (fn [e] (let [i (swap! success-count inc) e (dom/element :li {:id "testing" :class "test me out please"} "It worked!")] (log-obj e) (log i) (dom/append destination e)))) (log-obj (dom/element "Text node")) (log-obj (dom/element :li)) (log-obj (dom/element :li {:class "foo"})) (log-obj (dom/element :li {:class "bar"} "text node")) (log-obj (dom/element [:ul [:li :li :li]])) (log-obj (dom/element :ul [:li :li :li])) (log-obj (dom/element :li {} [:ul {} [:li :li :li]])) (log-obj (dom/element [:li {:class "baz"} [:li {:class "quux"}]])) (log-obj source) (log-listener-count)
3. Dart
Dart是一个典型的面向对象的语言,任何东西都是一个对象并且任何对象都是一个类的实例(对象也可以表现为函数)。它的特殊性用于打造面向浏览器,服务器和移动设备的应用。 它由谷歌来维护,是用于驱动下一代的AdWords UI。AdWords UI是谷歌盈利的重要产品,这也证明了它在体量上的强大。
这种语言可以编译为JavaScript用于浏览器,或者直接通过Dart VM解释,这样也可以允许你构建服务端应用。移动应用可以通过Flutter SDK创建。
复杂的应用还需要一系列特别为任务所设计的成熟的库和语言特性,Dart这些都有。举例来说一个流行的库是AngularDart,一个Dart版本的Angular。
它允许你写非侵入式的类型安全的代码,但是这不是必须的,因为他们可以自动检测类型。它可以允许你快速构建原型而不用过于思考细节,一旦你需要的时候,你可以加入类型让它更健壮。
至于在VM中的并发编程,相比与共享内存线程(Dart是单线程的),Dart使用所谓的Isolates,有它自己的堆内存,而交流是通过传递信息。在浏览器上,情况就有点不一样了:相比与创建一个新的isolates,你创建一个新的Workers。
// Example extracted from dartlang.org import 'dart:async'; import 'dart:math' show Random; main() async { print('Compute π using the Monte Carlo method.'); await for (var estimate in computePi()) { print('π ≅ $estimate'); } } /// Generates a stream of increasingly accurate estimates of π. Stream<double> computePi({int batch: 1000000}) async* { var total = 0; var count = 0; while (true) { var points = generateRandom().take(batch); var inside = points.where((p) => p.isInsideUnitCircle); total += batch; count += inside.length; var ratio = count / total; // Area of a circle is A = π⋅r², therefore π = A/r². // So, when given random points with x ∈ <0,1>, // y ∈ <0,1>, the ratio of those inside a unit circle // should approach π / 4\. Therefore, the value of π // should be: yield ratio * 4; } } Iterable<Point> generateRandom([int seed]) sync* { final random = new Random(seed); while (true) { yield new Point(random.nextDouble(), random.nextDouble()); } } class Point { final double x, y; const Point(this.x, this.y); bool get isInsideUnitCircle => x * x + y * y <= 1; }
4. Elm
官方网站:Get Started with Elm
Elm是一个可以编译成JS,HTML和JS的纯函数式编程语言。你可以只通过Elm创建一个完整的网站,这使得它是一个对像React这样的Javascript框架的一个很好的代替。通过它创建的应用自动使用了虚拟DOM库,使得它很快。一个大的加分项是内建的结构让你忘记数据流而是关注于数据声明和逻辑。
在Elm中,所有函数都是纯粹的,这意味着他们总是对一个给予的输入返回一个相同的输出。T他们不能做其他任何事情,除非你指定。举例来说,获取一个远程的API你会创建一个command函数来通讯外部世界,和一个 subscriptions 函数监听回复。另一个纯粹的点是,值是不可变的,当你需要什么的时候,你创建一个新值而不是改变它。
ELm的接受可以是平缓的;可以使用ports来和Javascript或其他库沟通。虽然Elm还没有到达版本1,它已经用于复杂大型的应用了,这使得它对复杂应用是一个可行的解决方案。
ELm其中一个吸引人的功能是初学者友好的编译器,它生成帮助你修复你的代码的信息,而不是产生难以阅读的信息。如果你正在学习这门语言,编译器本身就是一个大的帮助。
module Main exposing (..) import Html exposing (..) -- MAIN main : Program Never Model Msg main = Html.program { init = init , update = update , view = view , subscriptions = subscriptions } -- INIT type alias Model = String init : ( Model, Cmd Msg ) init = ( "Hello World!", Cmd.none ) -- UPDATE type Msg = DoNothing update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of DoNothing -> ( model, Cmd.none ) -- VIEW view : Model -> Html Msg view model = div [] [text model] -- SUBSCRIPTIONS subscriptions : Model -> Sub Msg subscriptions model = Sub.none
5. CoffeeScript
官方网站:Get Started with CoffeeScript
CoffeeScript是一个旨在暴露JavaScript的精华并提供一个干净的语法并在合适地方保留语义的语言。虽然近年来这个语言的热度在下降,它正在改变方向并且现在有一个新的大版本支持ES2015+特性。
你用CoffeeScript写的代码直接转化为可读的Javascript代码并且兼容现有的库。从2版本开始,编译器会产生兼容最新版本的ECMAScript的代码,比如,每次你使用class,你就在Javascript中得到class。并且,如果你使用React,好消息是,JSX兼容CoffeeScript。
这个编译器有一个十分有特色的功能是有能力处理用literate style写的代码。literate style相比于强调代码而把注释作为添加这种方式,而是你需要在一开始就写注释,代码只是偶尔出现。这种写代码的方式由Donald Knuth推荐,使得一个代码文件非常像一个技术文档。
相比于其他语言,CoffeeScript代码可以在浏览器中用一个库直接执行。所以如果你想要写一个快速测试,你可以写你的代码在一个text/coffeescriptscript标签中,并且引入编译器,这样就可以把你的代码轻易的转化为JavaScript了。
# Assignment: number = 42 opposite = true # Conditions: number = -42 if opposite # Functions: square = (x) -> x * x # Arrays: list = [1, 2, 3, 4, 5] # Objects: math = root: Math.sqrt square: square cube: (x) -> x * square x # Splats: race = (winner, runners...) -> print winner, runners # Existence: alert "I knew it!" if elvis? # Array comprehensions: cubes = (math.cube num for num in list)
6. PureScript
官方网站:Get Started with PureScript
PureScript 是由 Phil Freeman 创建的纯函数式强类型的编程语言。它旨在给可用的 JavaScript 库提供强大的兼容性,在精神上类似于 Haskell,但保持 JavaScript 的核心。
PureScript 的一个强项是它的极简主义。它不包括在其他语言中被认为是必需的功能的任何库。例如,不是编译器本身包含生成器和 promises,而是你可以使用特定的库来完成任务。你可以为所需功能选择想要的实现,这样可以在使用 PureScript 时实现高效和个性化的体验,同时保持生成的代码尽可能小。
其编译器的另一个显著特征就是能够在保持与 JavaScript 的兼容性的同时, 用库和工具生成整洁和可读的代码。
像其他语言一样,PureScript 有自己的构建工具叫做 Pulp,可以与 Gulp 进行比较, 但是用于以这种语言编写的项目。
关于类型系统,与 Elm不同,即另一种 ML 式的语言,PureScript 支持高级类型的功能,如取自 Haskell 的 higher-kinded types(高级类类型) 以及 type classes(类型类), 从而允许创建复杂的抽象。
module Main where import Prelude import Data.Foldable (fold) import TryPureScript main = render $ fold [ h1 (text "Try PureScript!") , p (text "Try out the examples below, or create your own!") , h2 (text "Examples") , list (map fromExample examples) ] where fromExample { title, gist } = link ("?gist=" <> gist) (text title) examples = [ { title: "Algebraic Data Types" , gist: "37c3c97f47a43f20c548" } , { title: "Loops" , gist: "cfdabdcd085d4ac3dc46" } , { title: "Operators" , gist: "3044550f29a7c5d3d0d0" } ]
7. Scala.js
官方网站:Get Started with Scala.js
Scala.js 是将 Scala 编程语言转换成 JavaScript 语言的编译器。Scala 语言的目标是合并面向对象编程和函数式编程到一种语言中来,并创建一套强有力的易于使用的工具。
作为一种强类型语言,你可以在灵活的类型系统中获取局部类型推断的好处。其最大的价值在于可以被推测,但是它的函数参数还是需要强制类型说明。
虽然支持许多常见的面向对象模式(例如,每个值都是一个对象,操作是方法调用),但你也可以获得函数特性,如支持一级函数和不可变数据结构。
Scala.js 的一个特殊优点是,你可以从熟悉的面向对象方式开始,并且按需要以自己的速度移动到更加函数的方式,而无需做大量的工作。此外,现有的 JavaScript 代码和库与你的 Scala 代码兼容。
新手 Scala 开发人员会发现这门语言与 JavaScript 没有太大差异,比较下面的等效代码:
// JavaScript var xhr = new XMLHttpRequest(); xhr.open("GET", "https://api.twitter.com/1.1/search/" + "tweets.json?q=%23scalajs" ); xhr.onload = (e) => { if (xhr.status === 200) { var r = JSON.parse(xhr.responseText); $("#tweets").html(parseTweets(r)); } }; xhr.send();
// Scala.js val xhr = new XMLHttpRequest() xhr.open("GET", "https://api.twitter.com/1.1/search/" + "tweets.json?q=%23scalajs" ) xhr.onload = { (e: Event) => if (xhr.status == 200) { val r = JSON.parse(xhr.responseText) $("#tweets").html(parseTweets(r)) } } xhr.send()
8. Reason
Reason是一个由Facebook创造和维护的语言,它为OCaml编译器提供了新的语法,并且代码可以转换成JavaScript和原生代码。
作为ML家族的一部分并且自己本身是函数式语言,它天生提供了强大但是灵活的伴随类型推断的类型系统,代数数据类型和模式匹配。它也支持不可变数据类型和参数多态(也被其他语言称为泛型),但是在OCaml中,也是支持面向对象编程的。
通过 bucklescript绑定就可以使用现存的JavaScript库。你也可以在你的Reason代码旁边混入你的JavaScript。插入的JavaScript代码不会严格的检查,但作为快速修复和原因也是不错的。
如果你是一个React开发者,绑定是可能的,并且这个语言也支持JSX。
/* A type variant being pattern matched */ let possiblyNullValue1 = None; let possiblyNullValue2 = Some "Hello@"; switch possiblyNullValue2 { | None => print_endline "Nothing to see here." | Some message => print_endline message }; /* Parametrized types */ type universityStudent = {gpa: float}; type response 'studentType = {status: int, student: 'studentType}; let result: response universityStudent = fetchDataFromServer (); /* A simple typed object */ type payload = Js.t {. name: string, age: int }; let obj1: payload = {"name": "John", "age": 30};
总结
介绍了这么多可以编译为JavaScript的语言,并不是说要放弃JavaScript,现在主流前端开发是使用 TypeScript,JavaScript 现在也变得越来越好,不管哪种语言,适合的才是最好的。