应该有不少程序员是像我一样从写动态网站起步学习编程的。最初我用的是 ASP。后来微软发布了 .NET,我就开始学习 C# 和 ASP.NET。大学时开始用 Java、C、C++,不过除了拿来写了写 HTTP 服务器以外,这几门语言都与制作网页没有太大关系。再后来由于做项目需要,偶尔用了用 PHP,发现其巨恶心,项目最终也未能完成。

最近几年受师兄的影响,开始用 Python 和 Django 写动态网页,并接触到了一些前端的新技术,如 Haml、Sass 和 CoffeeScript。Haml 替代了 HTML,Sass 替代了 CSS,CoffeeScript 替代了 JavaScript,完全转变了网页前端的几个基本构件的开发方式。感觉就像是一下子从网页开发的黑暗时代跃进到了现代,因此最近网页类的业余项目也特别多。

Haml

 Beautiful, DRY, well-indented, clear markup: templating haiku.

不久以前,网页开发者都在追捧 XHTML。大家纷纷在自己的网页上挂上一个 W3C Valid XHTML 的标志,意思是该网页通过了 W3C 的这个语法检查器的验证。XHTML 比起 HTML 4 语法更严谨简洁。它对源码主要的要求是标签必须正确地嵌套,即开闭标签必须一一对应。当然,它还有其他要求,如必须声明 doctype,必须有且仅有一个根,标签必须小写,属性必须是由双引号括起来等等。这一系列的要求,在我看来只是使得 XHTML 变得更机器友好,可能可以提高浏览器解析的效率,但没有改善对人类而言的可读性或可写性。编辑 HTML 变成了一个功夫活,往往表达的意思很简单,但写起来很繁琐,而且很容易出错。例如在多层的 div 里,我经常会漏掉一个或多写一个关闭 div 的标签。

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
  <div class="row">
    <div class="col-lg-4 col-md-6">
      <div class="alert alert-warning">{{ message }}</div>
      <a href="#" target="_blank">Link</a>
    </div>
    <div class="col-lg-8 col-md-6">
      <div id="content" class="content">{{ content }}</div>
    </div>
  </div>
</div>

上面这一段 HTML 代码利用了 Bootstrap 框架中的一些常见的排版样式类,但是使用起来需要写很长很长的代码,读起来也不直接了当。这时如果我们转而使用 Haml 来写同等的代码,然后再生成 HTML,这个任务就变得很简单了,代码就会变得简洁明了。如下:

1
2
3
4
5
6
7
.container
  .row
    .col-lg-4.col-md-6
      .alert.alert-warning= message
      %a{href: "#", target: "_blank"} Link
    .col-lg-8.col-md-6
      #content.content= content

值得一提的是,我使用的 Haml 编译器是 Hamlpy,因此对标签属性的写法跟 Ruby 里的那个原生的 haml gem 略有不同。Haml 类似于 Python,通过行的缩进来表明代码作用域,这是我最近比较喜欢的语言特性。Haml 生成出来的代码当然能够自动保证与 XHTML 的要求相符,甚至还能根据你的要求做很多细节上的修改。例如,我的这个博客网站的 Jekyll 模板就是由我自己略为修改的 Hamlpy 生成的。生成的时候它把所有多余的换行和空格都去掉了,这样可以起到一个对 HTML 压缩的效果,减少参差不齐的缩进。

Sass

Syntactically Awesome Style Sheets

使用 Sass 的好处有很多,但最吸引我的是以下三点:

  1. 类似 Python,利用行缩进来决定样式类的嵌套以及作用域,不用使用分号。表达同样的意思更清晰明了,而且能够少打很多字。
  2. 能够定义变量和宏。对于一个程序员来说这个功能非常方便。
  3. Compass 这种强大的库,提供跨浏览器支持。

以 Bootstrap 的按钮为例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.btn {
  display: inline-block;
  padding: 6px 12px;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

.btn:active,
.btn.active {
  background-image: none;
  outline: 0;
  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}

.btn .badge {
  color: #fff;
  background-color: #333;
}

虽然上面这几段 CSS 代码都是关于 btn 这一个类的,但是必须分开来一段一段写。在我看来很反常,表述同一样元素的样式不应该放在一起吗?否则管理起来很困难。此外,不同浏览器有不同的属性前缀,写起来很麻烦。如果我们转而使用 Sass,这个样式表马上就变简洁了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@import compass/css3/box-shadow
@import compass/css3/user-select

.btn
  display: inline-block
  padding: 6px 12px
  +user-select(none)

  &:active, &.active
    background-image: none
    outline: 0
    +box-shadow(rgba(0, 0, 0, .125) 0 3px 5px inset)

  .badge
    color: #fff
    background-color: #333

以上代码调用了 Compass 提供的 user-selectbox-shadow 这两个宏,自动生成不同浏览器所需要的对应语句。如果你经常用到某个颜色,还可以通过定义类似 $accent_color: #333 的全局变量来规划颜色的管理。

CoffeeScript

It’s just JavaScript

最后大家发现写 JavaScript 也很不爽。整天遇到一些问题,如必须记得使用 var 来定义变量的作用域,必须加分号或者说必须知道哪里要加分号,没有列表推导,需要区分 =====,定义或者继承类的方式很反常等等。然后就出现了救星 CoffeeScript。CoffeeScript 的官网上就有一个很好的类继承例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal
  constructor: (@name) ->

  move: (meters) ->
    alert @name + " moved #{meters}m."

class Snake extends Animal
  move: ->
    alert "Slithering..."
    super 5

class Horse extends Animal
  move: ->
    alert "Galloping..."
    super 45

sam = new Snake "Sammy the Python"
tom = new Horse "Tommy the Palomino"

sam.move()
tom.move()

箭头方程 -> 和双箭头方程 => 都很强大,后者可以帮你自动保存 this 变量:

1
2
3
4
5
6
7
8
Account = (customer, cart) ->
  # @ means this
  # @customer is equivalent to this.customer
  @customer = customer
  @cart = cart

  $('.shopping_cart').bind 'click', (event) =>
    @customer.purchase @cart

除此以外还有很多语法上的简化,所谓的 Syntactic sugar,使得 CoffeeScript 写起来很舒服:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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) 

当然有时候我会遇到一些语法上模棱两可的地方,此时需要稍加注意,不要过于贪图简化即可。

源码

但愿我对这几项网页前端技术的简单介绍能够激发你对它们的兴趣。如果你需要参照一个完整的实例以进一步了解这些新技术,欢迎你访问我的 GitHub 页面,里面有我这个博客网站用来生成 HTML、CSS 和 JavaScript 的源码:

附言:六年后第一篇中文博文 ……

附附言:发现用 GitHub 上的网页编辑器全屏模式直接编辑博文文件也很爽。