返回首页 - Notes - 2017

TypeScript VS CoffeeScript


写在前面

原本在 JS 相关技术日新月异的今天,已经没有多大必要考虑 CoffeeScript 了,TypeScript 更符合发展的潮流

但因为对 Ruby 爱得深沉,语法借鉴自 RubyCoffeeScript 用起来更加自然,这才有了比较的必要


优劣纵览

  TypeScript CoffeeScript
优势 兼容标准 JavaScript 近似 RubyPython,语法简洁
  拥有可选的类型系统,能避免将一些显而易见的错误带入运行时 为诸多标准 JavaScript 繁琐的写法提供了易用的语法糖
  拥抱最新的 JavaScript 标准,能提前使用未普及的语法特性 方法调用可以省略括号
  内建支持 JSX,对 React 支持良好 RailsPlay 框架的默认前端语言
  Angular2+ 的官方推荐语言  
  MicroSoft 大厂做后盾,开发工具支持良好  
     
劣势 C 系语法,不够简洁 与标准 JavaScript 语法差异较大,对熟悉 C 系语言的人不友好
  习惯了动态类型的人不太 care 其附加的类型系统 目前发展缓慢,跟不上 JavaScript 进化的节奏,一些 ES6 以后新加的重要特性没法使用
    不支持 JSX 语法,对 React 不友好
     

细节对比


注释

TypeScript

// 单行注释

/*
多行注释
*/

CoffeeScript

# 单行注释

###
多行注释
###

变量声明

TypeScript

let a = 1
let b: string = 'Ruchee'
const c: boolean = true

CoffeeScript

a = 1
b = 'Ruchee'
c = true

# 也可以写成下面这样
[a, b, c] = [1, 'Ruchee', true]

字符串插值

TypeScript

let age = 18
let str = `Your age is ${age}`

console.log(str)  // Your age is 18

CoffeeScript

age = 18
str = "Your age is #{18}"

console.log str  # Your age is 18

交换两变量的值

TypeScript

let a = 1
let b = 2

a = [b, b = a][0]

console.log(a)  // 2
console.log(b)  // 1

CoffeeScript

[a, b] = [1, 2]

[a, b] = [b, a]

console.log a  # 2
console.log b  # 1

数组和对象的解构

TypeScript

// 数组解构
let arr = [1, 2, 3, 4, 5]
let [a, b, ...c] = arr

console.log(a)  // 1
console.log(b)  // 2
console.log(c)  // [ 3, 4, 5 ]


// 对象解构
let obj = { x: 1, y: 2, z: 3}
let {x, ...other} = obj

console.log(x)     // 1
console.log(other) // { y: 2, z: 3 }

CoffeeScript

# 数组解构
arr = [1..5]
[a, b, c...] = arr

console.log a  # 1
console.log b  # 2
console.log c  # [ 3, 4, 5 ]


# 对象解构 [支持不完整]
obj =
  x: 1
  y: 2
  z: 3
{ x, y, z } = obj

console.log x  # 1
console.log y  # 2
console.log z  # 3

数组和对象的展开

TypeScript

// 展开数组
let arr_a = [1, 2, 3]
let arr_b = [4, 5]
let arr_c = [...arr_a, ...arr_b]

console.log(arr_c)  // [ 1, 2, 3, 4, 5 ]


// 展开对象
let obj_a = { a: 1, b: 2, c: 3 }
let obj_b = { d: 4, e: 5 }
let obj_c = { ...obj_a, ...obj_b }

console.log(obj_c)  // { a: 1, b: 2, c: 3, d: 4, e: 5 }

CoffeeScript

# 展开数组
arr_a = [1..3]
arr_b = [4..5]
arr_c = [arr_a..., arr_b...]

console.log arr_c  # [ 1, 2, 3, 4, 5 ]


# 展开对象 [不支持]
obj_a =
  a: 1
  b: 2
  c: 3
obj_b =
  d: 4
  e: 5
obj_c = Object.assign(obj_a, obj_b)

console.log obj_c  # { a: 1, b: 2, c: 3, d: 4, e: 5 }

函数定义

TypeScript

// 常规函数
function hello1(name: string): void {
  console.log(`Hello ${name}`)
}

// 匿名函数1
let hello2 = (name: string) => {
  console.log(`Hello ${name}`)
}

// 匿名函数2
let hello3 = name => console.log(`Hello ${name}`)

hello1('Ruchee')
hello2('Ruchee')
hello3('Ruchee')

CoffeeScript

hello = (name) -> console.log "Hello #{name}"

hello 'Ruchee'

类定义

TypeScript

class Person {
  // 声明实例变量
  name: string

  // 构造函数
  constructor(name: string) {
    this.name = name
  }

  // 实例方法
  sayHello() {
    console.log(`Hello ${this.name}`)
  }
}


// 实例化对象
let p = new Person('Ruchee')
p.sayHello()  // Hello Ruchee

CoffeeScript

class Person
  # 构造函数
  constructor: (name) ->
    @name = name

  # 实例方法
  sayHello: ->
    console.log "Hello #{@name}"


# 实例化对象
p = new Person('Ruchee')
p.sayHello()  # Hello Ruchee

类继承

TypeScript

// 父类
class Animal {
  name: string

  constructor(name: string) {
    this.name = name
  }

  drink() {
    console.log(`${this.name} drinking`)
  }
}


// 子类
class Cat extends Animal {
  age: number

  constructor(name: string, age: number) {
    super(name)  // 调用父类的构造函数
    this.age = age
  }

  // 覆盖父类的实例方法
  drink() {
    console.log(`${this.name} is ${this.age} years old`)
    super.drink()  // 调用父类的实例方法
  }
}


// 实例化对象
let cat = new Cat('Tom', 3)
cat.drink()

// Tom is 3 years old
// Tom drinking

CoffeeScript

# 父类
class Animal
  constructor: (name) ->
    @name = name

  drink: ->
    console.log "#{@name} drinking"


# 子类
class Cat extends Animal
  constructor: (name, age) ->
    super(name)  # 调用父类的构造函数
    @age = age

  drink: ->
    console.log "#{@name} is #{@age} years old"
    super  # 调用父类的实例方法

# 实例化对象
cat = new Cat('Tom', 3)
cat.drink()

# Tom is 3 years old
# Tom drinking

实现约瑟夫环算法

TypeScript

/**
 * 节点结构
 */
class Person {
  data: any
  next: any

  constructor(data, next) {
    return { data: data, next: next }
  }
}


/**
 * 创建循环链表
 */
let createPeople = (n) => {
  let p = new Person(1, null)
  let head = p

  for (let i = 2; i <= n; ++i) {
    let q = new Person(i, null)
    p.next = q
    p = q
  }
  p.next = head

  return head
}


/**
 * 执行杀人
 */
let killPerson = (head, start, step, sum, want) => {
  let p = head

  // 先找到开始喊号的人
  while (p.data !== start) {
    p = p.next
  }

  // 已经杀掉的人数
  let n = 0
  // 已经杀掉的人
  let killed = []
  // 幸存者
  let survivals = []

  // 确保圈里还有多于1人
  while (p.next !== p) {
    let t
    for (let i = 1; i <= step; ++i) {
      t = p
      p = p.next
    }
    if ((sum - n) > want) {  // 如果剩余的人数大于想要的人数,那就继续杀
      killed.push(p.data)
    } else {
      survivals.push(p.data)
    }
    ++n
    t.next = p.next
    p = p.next
  }

  survivals.push(p.data)

  // 输出结果
  console.log('Killed: ' + killed)
  console.log('The survival' + (want > 1 ? 's' : '' ) +': ' + survivals)
}


let sum = 10
let head = createPeople(sum)   // 10人围成一圈
killPerson(head, 5, 3, sum, 2) // 从5号开始,每次往后杀第3人,并传递总数和要求最后剩下的人数

// Killed: 8,2,6,1,7,4,3,5
// The survivals: 10,9

CoffeeScript

class Person
  constructor: (@data, @next) ->
    data: @data
    next: @next


createPeople = (n) ->
  head = p = new Person 1, null
  for i in [2..n]
    q = new Person i, null
    [p.next, p] = [q, q]
  p.next = head


killPerson = (head, start, step, sum, want) ->
  p = head
  p = p.next while p.data != start

  [n, killed, survivals] = [0, [], []]

  while p.next != p
    [t, p] = [p, p.next] for i in [1..step]
    if (sum - n) > want
      killed.push p.data
    else
      survivals.push p.data
    ++n
    [t.next, p] = [p.next, p.next]

  survivals.push p.data

  console.log "Killed: #{killed}"
  if want > 1
    console.log "The survivals: #{survivals}"
  else
    console.log "The survival: #{survivals}"


sum = 10
head = createPeople sum
killPerson head, 5, 3, sum, 2

# Killed: 8,2,6,1,7,4,3,5
# The survivals: 10,9

总结

以上只是一些常规语法结构的对比,另有一堆 TypeScript 有而 CoffeeScript 没有的特性无从比较

CoffeeScript 已是明日黄花,尽管语法简洁高效,但新特性的缺失让激情难以为继,是时候试试 TypeScript


2017-05-11 更新

CoffeeScript 最近有了新的动作,2.x 版本已经发布,目前是 beta1 状态

新版 CoffeeScript 广泛支持了 ES6 的新特性,包括 importawait 等,不再落后

另外,我在前文说 CoffeeScriptReact 支持不好,其实也算是了解不到位产生的误解,用 CoffeeScriptReact 可以用下面的语法:

class Hello
  render: ->
    `(
      <div>
        <h1>Hello React</h1>
      </div>
    )`

编译成 JavaScript 是这样:

// Generated by CoffeeScript 2.0.0-beta1
(function() {
  var Hello;

  Hello = class Hello {
    render() {
      return (
      <div>
        <h1>Hello React</h1>
      </div>
    );
    }

  };

}).call(this);

所以,用 CoffeeScriptReact 完全没有问题,喜爱 CoffeeScript 的朋友可以继续深沉地爱下去


2017-07-04 更新

最新的 CoffeeScript 2.0.0-beta3 版本,已内建支持 JSX 语法

上面的示例可以写成:

class Hello
  render: ->
    <div>
      <h1>Hello React</h1>
    </div>

编译成 JavaScript 是这样:

// Generated by CoffeeScript 2.0.0-beta3
(function() {
  var Hello;

  Hello = class Hello {
    render() {
      return <div>
      <h1>Hello React</h1>
    </div>;
    }

  };

}).call(this);

date:2017-02-04、2017-05-11、2017-07-04