10个编译为JavaScript的语言

1,394 阅读8分钟
原文链接: www.zcfy.cc

现代应用相比普通的网页有不同的要求。但是浏览器是一个有着一套(大部分)固定可用的技术的平台,JavaScript依然是web应用的核心语言;任何需要在浏览器上跑的应用都需要使用这种语言。

我们都知道Javascript并不是最好的语言,特别是在复杂的应用中,它可能不太能胜任。为了避免这种情况,一些新的语言或现有语言的编译器被创造出来,你不用写一行Javascript或者考虑这种语言的局限,就能生产在浏览器能运行的代码。

这篇文章包括了十种有趣的语言能够编译为Javascript,在浏览器或者Node.js中被执行。

--ADVERTISEMENT--

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;
}


Get started with Dart

TypeScript

TypeScript 是Javascript的超集;一个有效的Javascript项目也是一个有效的TypeScript项目只是添加了静态类型。编译器也可以作为ES2015+到当前实现的转译器,这样你总是能得到最新的特性。

不同于其他语言,TypeScript保持了Javascript完整的精神,只是此外添加了增加代码可靠性的功能。这些功能就是类型注释和其他类型相关的功能,得益于专业工具像是静态分析器和其他工具在重构过程的加入,这些功能使写Javascript更加有趣。并且,类型的加入改善了你的应用不同组件之间的接口。

类型诊断是支持性的,你不必从一开始就写所有的类型。你可以先快速的写代码,然后再加入类型来让代码更稳定。

TypeScript同样也支持高级类型,像是交叉类型,联合类型,类型别名,可辨识联合和类型保护。你可以在TypeScript Documentation网站的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})`;
    }
}


——

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


Get Started with Elm

PureScript

PureScript 是一个纯函数强类型的编程语言,由Phil Freeman创造。它旨在与现有的JavaScript库进行很好的兼容,与Haskell精神上类似,但是核心保留了Javascript。

PureScript一个重要的点是它的极简主义。它没有包含任何在其他语言认为很重要的功能库。比如,相比于在编译器中包含generators和promises,你可以自己使用指定的库来完成这个任务。你可以选择你想要的功能的实现,当使用PureScript的时候需要高效和个性化的经验,能使生成的代码尽可能的小。

这个编译器另一个重要的功能是构建出清晰可读的代码,能够兼容Javascript包括库和工具。

和其他语言一样,PureScript有它自己的构建工具称为Pulp,可以和Gulp做对比,只是用这个语言写的。

至于类型系统,不同于另一个ML类的语言Elm,PureScript支持更先进的类型特性比如高级类类型和类型类,这些是从Haskell来的特性,允许创建复杂的抽象。

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"
        }
      ]


Get Started with PureScript

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)


Get Started with CoffeeScript 2

ClojureScript

ClojureScript是一个转化Clojure编程语言为JavaScript的编译器。Clojure是一个多用途的函数式原因伴随着动态类型和不可变数据结构的支持。

这是这个列表中唯一一个属于Lisp家族的语言,自然有着它们共同的特性。举例来说,代码可以作为数据,支持宏系统,使得元编程成为可能。 Unlike other Lisps, Clojure has support for immutable data structures, making the management of side-effects easier.不同于其他类Lisp,Clojure支持不可变数据结构,使得函数副作用的管理更容易。

这个语法对初学者看起吓人,因为圆括号的使用。但这样使用是经过深思熟虑的,并且在长远看来你一定会感谢这种语法的。语法的极简和抽象能力使得Lisp成为一个解决高抽象问题的强力工具。

虽然Clojure主要是一个函数式语言,但是不像PureScript或者Elm那样纯粹。函数副作用还是会发生,但是其他函数式特性也会存在。

ClojureScript使用Google Closure做代码优化并且也兼容现有的JavaScript库。

; Extracted from https://github.com/clojure/clojurescript/blob/master/samples/dom/src/dom/test.cljs

(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)


Get Started with ClojureScript

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()


Get Started with Scala.js

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};


Get Started with Reason

Haxe

Haxe是一个多范式编程语言,并且它的编译器可以产生二进制或者其他语言的源代码。

虽然Haxe提供了严格的类型系统并带有类型推断,它也可以作为动态语言只要目标语言支持。同样的,它也支持多种的编程风格比如面向对象,泛型,函数式。

当你写Haxe代码的时候,你可以为编译指定多个平台或语言,但不需要对代码做什么大的改变。指定目标的代码块也支持。

你可以用Haxe同时写前端和后端用同样的代码,并且通过Haxe Remoting进行沟通,既可以同步连接也可以异步连接。

不出所料,Haxe代码可以兼容现有的库但也提供了成熟的标准库。

// Example extracted from http://code.haxe.org

extern class Database {
  function new();
  function getProperty<T>(property:Property<T>):T;
  function setProperty<T>(property:Property<T>, value:T):Void;
}

abstract Property<T>(String) {
  public inline function new(name) {
    this = name;
  }
}

class Main {
  static inline var PLAYER_NAME = new Property<String>("playerName");
  static inline var PLAYER_LEVEL = new Property<Int>("playerLevel");

  static function main() {
    var db = new Database();

    var playerName = db.getProperty(PLAYER_NAME);
    trace(playerName.toUpperCase());

    db.setProperty(PLAYER_LEVEL, 1);
  }
}


Get Started with Haxe

Nim

Nim是一个静态类型,多范式编程语言,有着极简风格与空格敏感的语法,编译为C,C++和JavaScript。

这个语言本身很小,但它的元编程能力会吸引你自己去实现一些在别的语言内置的功能。这些构建模块有宏,模板和泛型,通过它们你可以实现不论是简单的功能还是不同的泛型。这使得Nim成为一个非常通用的语言可以适应你的需求,有着Lisp的精髓。

Nim的语法抽象功能允许你去让语言去适应你的功能,让真正的DSLs成为可能。如果你有着专门的任务需要处理,你可以获得更高级的表达性。

# Reverse a string
proc reverse(s: string): string =
  result = ""
  for i in countdown(high(s), 0):
    result.add s[i]

var str1 = "Reverse This!"
echo "Reversed: ", reverse(str1)

# Using templates
template genType(name, fieldname: expr, fieldtype: typedesc) =
  type
    name = object
      fieldname: fieldtype

genType(Test, foo, int)

var x = Test(foo: 4566)
echo(x.foo) # 4566


Get Started with Nim

Conclusion

如果JavaScript不是你最喜欢的语言,你依然可以创建web应用而不用忍受这个技术的缺点。可供选择的范围很广,从纯粹的函数式语言,比如PureScript,到面向对象语言,比如Dart。并且如果你想要不只是语言的转化,你也可以选择比如Elm,Elm提供像是虚拟DOM和内置的架构这样的工具。

你是否有尝试了这篇文章的任何一种语言,又或者你有自己的推荐?请在评论中让我知道!