高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

CoffeeScript 裡的全域變數

在CoffeeScript裡,即使是一個空的.coffee檔,它也會被編譯成這樣:

1
2
3
(function() {

}).call(this);

你在.coffee寫的任何變數或function,都是被宣告為區域變數包在裡面,只會在裡面有作用而已。這其實是好事,因為這樣一來與外界隔離,你寫的東西不會去污染到別人寫的,相對的別人寫的東西也不會去弄髒你寫的。但假設因為某些不可抗力,硬是要把變數或function弄成global好讓所有頁面都可以直接使用,也不是沒辦法,你可以直接把變數或function掛在window物件底下:

1
2
window.logout = ->
  alert "You've already logout!" if confirm "Are you sure to logout?"

或是這樣做:

1
2
3
root = exports ? this
root.logout = ->
  alert "You've already logout!" if confirm "Are you sure to logout?"

第二種寫法看起來比較麻煩,但比較通用。exports是在Node.js裡定義的物件,上面第1行的意思是指會先檢查exports是不是已經有定義了,如果這段程式是在Node.js裡執行的話,因為exports是存在的,所以會定義一個root變數並指向它;如果是在一般的網頁上執行,因為沒有exports,所以會定義一個root指向this,也就是window物件 ,所以這樣的寫法的好處就是在網頁或是Node.js都行得通。

這樣一來,你在頁面上的按鈕就可以這樣寫:

1
<input type="button" value="logout" id="Logout" onclick="logout();" />

但是,如果可以的話,建議還是儘量避免用這種global變數的方式來寫程式,現在的JavaScript越寫越複雜,沒人知道你寫的東西會不會剛好就去衝到誰家寫的library,或是衝到你自己或同事寫的程式碼 :)

CoffeeScript -> 與 => 的差別

不知道各位在用CoffeeScript的 -> (dash rocket)在寫function的時候,有沒有發現有另一個長得跟它有點像,但比較胖一點的 => (fat arrow),在CoffeeScript的source code裡有一段這樣的簡短說明:

CoffeeScript has two different symbols for functions. -> is for ordinary functions, and => is for functions bound to the current value of this.

在CoffeeScript裡,用 -> 會產生一個Anonymous function,例如:

1
2
3
4
skinny = (name) ->
  "hello, I'm skinny #{name}"

console.log(skinny "eddie")

編譯出來的結果是:

1
2
3
4
5
var skinny;
skinny = function(name) {
  return "hello, I'm skinny " + name;
};
console.log(skinny("eddie"));

執行結果是:

hello, I'm skinny eddie

=> 也是會產生Anonymous function:

1
2
3
4
fatty = (name) =>
  "hello, I'm fatty #{name}"

console.log(fatty "eddie")

執行結果是:

hello, I'm fatty eddie

就以程式的執行結果來看,用 -> 跟用 => 似乎沒什麼兩樣,但看一下用 => 編譯出來的Javascript程式碼:

1
2
3
4
5
var fatty;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
fatty = __bind(function(name) {
  return "hello, I'm fatty " + name;
}, this);

編出了看起來有點複雜的東東西,這串程式碼裡面看起來比較複雜的大概就是那個fn.apply(),我們先來看看那是做什麼的。

function.call() v.s function.apply()

直接來看段程式碼:

1
2
3
4
5
6
var who = "Eddie";
function doctor(){
  alert(who);
}

doctor();

這光用我們的人腦執行就猜得到結果是Eddie,如果我們再改一下:

1
2
3
4
5
6
var who = "Eddie";
function doctor(){
  alert(this.who);
}

doctor();

多了個this,但執行的結果還是一樣。這裡的this.who其實指的就是整個global的那個who,所以印出來結果也一樣。再來加一點變化:

1
2
3
4
5
6
7
8
9
var who = "Eddie";
var time_lord = { who: "the real doctor" }
function doctor(){
  alert(this);
  alert(this.who);
}

doctor();
doctor.call(time_lord);

其實this指的對像會隨著不同的情境而有所變化。例如我們平常可能會說:「媽! 我在這裡」,如果你是在公司說這句話,「這裡」表示的是公司;如果你是在家裡說這句話,「這裡」表示的是家裡。

再回來看程式碼,在上面程式碼的第8行的呼叫指的this是整個global,在這邊就是window,而第9行指的this則是透過call()方法傳進去的那個物件,程式碼所在的情境變了,所以當第5行要印出this.who的時候,會印出the real doctor

其實通常沒特別指定的變數或function呼叫,前面的this是可以省略的,所以上面的第8行的doctor()也可以改寫成:

1
doctor.call(this);

執行結果是一樣的。再來繼續再加點變化,call()還可以傳更多的參數進去:

1
2
3
4
5
6
7
8
9
10
var who = "Eddie";
var time_lord = { who: "the real doctor" }

function doctor(real_name){
  alert(this.who);
  alert("His real name is : " + real_name);
}

doctor("Aquarianboy");
doctor.call(time_lord, "No one knows!");

另外,在JavaScript裡,還有一個跟call()有點像的function叫做apply(),這兩個的功能差不多,最大的差別是apply()傳的第二個參數是陣列,而call()的參數則是一個一個傳進去,並用逗號分開:

1
2
3
4
5
6
7
8
9
10
var who = "Eddie";
var time_lord = { who: "the real doctor" }

function doctor(real_name){
  alert(this.who);
  alert("His real name is : " + real_name);
}

doctor("Aquarianboy");
doctor.apply(time_lord, ["No one knows!"]);

大概知道在JavaScript裡call()apply()的意思之後,再回來看我們剛剛那段CoffeeScript編譯出來的程式碼:

1
2
3
4
5
var fatty;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
fatty = __bind(function(name) {
  return "hello, I'm fatty " + name;
}, this);

function一層包一層,return再return,不過大意就是把this當做參數傳進fatty這個function裡的意思。

什麼時候會用到

有點離題了..再回來看看CoffeeScript,原來,=>在定義function的同時,還會把this也同時給綁到這個function,雖然this會隨著所在的情境而有所改變,但=>this給綁進來之後,可以確保在指向this的時候不會指錯人。

為什麼要這樣做? 什麼時候會用到它? 大多是被拿來當做event callback的時候會用得上,來看個範例:

1
2
3
4
5
6
7
8
class Student
  constructor: (@username) ->
  say_hello: =>
    alert "Hello, my name is #{@username}"

$ ->
  eddie = new Student("Eddie")
  $("#student_1").click(eddie.say_hello)

我把eddie.say_hello傳給click做為callback,意思就是當頁面上某個id叫做student_1的元素被點擊之後,就會去執行它,而執行結果是:

Hello, my name is Eddie

但是如果你把第3行程式碼的=>換成->的話,執行結果會變成:

Hello, my name is undefined

為什麼結果是undefined? 前面提到,this會隨著出現在不同的地方而會有不同的意思,如果你是用->的話,它的this是指向你剛剛點擊的那顆按鈕,而當然那顆按鈕上面不會有username這個屬性,所以印出undefined;如果是用=>的話,它會透過fn.apply()this給包進來,所以say_hello裡的this,指的就是它自己這個物件,也就是Student類別產生出來的instance,印出來的結果就是你要的了。

細節可以再看看它編譯出來的JavaScript code,大概就可以知道->=>各別做什麼不同的事。

以上,供大家參考,如果有哪邊寫錯再請跟我說 :)

RVM - Ruby enVironment(Version) Manager

本篇文章已刊載於 OpenFoundry 電子報技術專欄,刊登之內容由專業的 OpenFoundry 團隊潤稿,此篇為原文。

前言

我相信很多人學習Ruby是因為Ruby on Rails的緣故,但Rails在改版的速度很快,而且有時候改版的幅度不小,例如從以前的2.x版本跳到3.x版本,甚至有些小版號的改版幅度也不小。像是不久前release出來的Rails 3.1.0,就跟3.0.x的架構就差滿多的。就算不談 Rails,光是主流的 Ruby 目前也有 1.8.x 跟 1.9.x 的分支,在功能上都有些差異。

這些新推出來的新玩具有的很好玩,不裝起來試一下就會覺得手很癢。你當然可以在你電腦裡安裝這些不同版本新玩具,但畢竟在自己工作的機器上試是有風險的,萬一把環境弄壞了還得花時間想辦法回復。以往我們可能會使用類似VirtualBox之類的軟體來模擬作業環境,玩壞了隨時都可以很快的還原或重建一個新的,不過即使這樣還是有點麻煩。

這時候你就需要RVM(Ruby enVironment(或Version) Manager)了,有了它,你可以安心的在你的電腦裡同時安裝多個不同版本的Ruby而不會打架,例如標準的MRI(Matz’s Ruby Interpreter),或是REE(Ruby Enterprise Edition),甚至是JRuby、MacRuby都沒問題,在RVM裡都可以都可以隨你高興的切換。RVM裡每個版本的Ruby的gem也都是分開裝的,甚至在同一個Ruby版本底下也可以建立不同的gemset,彼此獨立互不影響。如果哪天覺得膩了、不想玩了,可以用rvm remove指令移掉指定的版本;萬一哪天整個不想要玩了,因為RVM是把檔案安裝在/home的個人帳號資料夾底下,所以也不會去影響到系統的設定,不要的時候就整個~/.rvm資料夾砍掉就行了,不會影響原來系統的設定。也就是因為RVM是安裝在你的個人帳號底下,所以你在安裝過程中是不需要root權限的。

安裝

以下提到的各項安裝指令,會以Ubuntu 10.10做為示範。

安裝RVM會需要兩項工具curlgit,如果沒有的話請用apt-get install curl以及apt-get install git指令把這兩個裝起來(安裝這兩個工具你可能需要有系統的root權限)。這兩項工具安裝完成後,請打開你的終端機,貼上這行:

bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

它就會開始去下載RVM的安裝程式回來並且自動開始進行安裝。完成後還需要做一下簡單的設定,看你用的shell是哪一套而要修改不同的檔案,假設你用的是ubuntu預設的bash shell的話,你可以直接在終端機裡貼下面這行:

echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM function' >> ~/.bash_profile

更詳細的安裝說明可以參考官網的安裝說明

使用

接著我們來看一些在RVM裡常用的指令,rvm list known會列出目前有哪些可以安裝的列表:

> rvm list known

# MRI Rubies
[ruby-]1.8.6[-p420]
[ruby-]1.8.6-head
[ruby-]1.8.7[-p352]
[ruby-]1.8.7-head
[ruby-]1.9.1-p378
[ruby-]1.9.1[-p431]
[ruby-]1.9.1-head
[ruby-]1.9.2-p180
[ruby-]1.9.2[-p290]
[ruby-]1.9.2-head
[ruby-]1.9.3-preview1
[ruby-]1.9.3[-rc1]
[ruby-]1.9.3-head
ruby-head

# GoRuby
goruby

# JRuby
jruby-1.2.0
jruby-1.3.1
jruby-1.4.0
jruby-1.6.1
jruby-1.6.2
jruby-1.6.3
jruby[-1.6.4]
jruby-head

# Rubinius
rbx-1.0.1
rbx-1.1.1
rbx-1.2.3
rbx-1.2.4
rbx[-head]
rbx-2.0.0pre

# Ruby Enterprise Edition
ree-1.8.6
ree[-1.8.7][-2011.03]
ree-1.8.6-head
ree-1.8.7-head

# Kiji
kiji

# MagLev
maglev[-26852]
maglev-head

# Mac OS X Snow Leopard Only
macruby[-0.10]
macruby-nightly
macruby-head

# IronRuby -- Not implemented yet.
ironruby-0.9.3
ironruby-1.0-rc2
ironruby-head

長長的一大串,幾乎目前常見的Ruby Interpreter都有了。列表裡的中括號表示那些是可以省略的,所以如果用rvm install來安裝的時候輸入:

> rvm install 1.8.7

會自動找[ruby-]1.8.7[-p352]這個版本的Ruby來安裝。前面我們提過可以一次安裝多個不同的版本,所以你也可以再裝個1.9.2的版本:

> rvm install 1.9.2

如果你對最新的Ruby 1.9.3版本有興趣,雖然目前還沒正式release,也可以試著裝一份來試一下它的新功能。再來,我們看看目前已經安裝哪些版本:

> rvm list

rvm rubies

        ruby-1.9.2-p290 [ i686 ]
        ruby-1.8.7-p352 [ i686 ]

可以看到我電腦上目前裝了2個版本的Ruby(1.8.7跟1.9.2),這時候我們來看看Ruby的版本:

> ruby -v
ruby 1.8.7 (2010-06-23 patchlevel 299) [i686-linux]

看到patchlevel只有299,表示這個不是我們剛才安裝的版本,這是系統裡原來就已經裝好的。如果要切換到我們剛才安裝的Ruby 1.9.2版本:

> rvm use 1.9.2
Using /home/eddie/.rvm/gems/ruby-1.9.2-p290

想少打幾個字的話,use也可以省略:

> rvm 1.9.2

再來看一下Ruby的版本:

> ruby -v
ruby 1.9.2p290 (2011-07-09 revision 32553) [i686-linux]

已經切換到Ruby 1.9.2了! 不過有個小問題是,RVM會在下次終端機視窗重開的時候回到預設值(就是系統內建的Ruby版本),所以如果你希望每次開終端機視窗的時候都會切到Ruby 1.9.2的話:

> rvm 1.9.2 --default

這樣你每次開終端機視窗就會自動幫你切換到1.9.2版了。如果你想切回到原來系統內建的版本:

> rvm system

不要了,想砍掉它?

> rvm remove 1.9.2

就可以把指定的版本移除了,相當便利。如果你整個RVM都不想要了,只要把個人帳號底下的.rvm資料夾整個移除,再把.bash_profile的RVM相關設定改回來,就會整個清潔溜溜了。

運作原理

你也許會好奇為什麼RVM可以這麼神奇的切換Ruby的環境,我們來看一下系統的預設PATH:

> echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/eddie/.gem/ruby/1.8/bin:/usr/local/lib/python2.6/dist-packages/django/bin:/home/eddie/.rvm/bin:/home/eddie/.rvm/bin

> which ruby
/usr/bin/ruby

> ruby -v
ruby 1.8.7 (2010-06-23 patchlevel 299) [i686-linux]

(以上內容是我自己電腦裡的設定,可能跟各位的會有些不同)

再來用RVM切換到1.9.2:

> rvm 1.9.2
Using /home/eddie/.rvm/gems/ruby-1.9.2-p290

> echo $PATH
/home/eddie/.rvm/gems/ruby-1.9.2-p290/bin:/home/eddie/.rvm/gems/ruby-1.9.2-p290@global/bin:/home/eddie/.rvm/rubies/ruby-1.9.2-p290/bin:/home/eddie/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/eddie/.gem/ruby/1.8/bin:/usr/local/lib/python2.6/dist-packages/django/bin

> which ruby
/home/eddie/.rvm/rubies/ruby-1.9.2-p290/bin/ruby

> ruby -v
ruby 1.9.2p290 (2011-07-09 revision 32553) [i686-linux]

事實上,RVM是把不同版本的Ruby安裝在你的個人帳號底下的.rvm資料夾,在你切換不同版本的Ruby的時候,會幫你把系統預設的PATH的最前面加上這個.rvm的資料夾,當你在終端機底下輸入ruby指令的時候,系統原本的/usr/bin/ruby因為在PATH的比較後面,所以感覺會暫時失效而使用RVM版本的Ruby。你可以試著用rvm info來看看RVM幫你做了哪些設定。

Gem

在RVM裡,不同版本的Ruby的gem是各別獨立的,來做個簡單的實驗,例如我們先切換到1.9.2底下:

> gem list

*** LOCAL GEMS ***

bundler (1.0.0.rc.5)
json (1.6.1 ruby)
rake (0.9.2 ruby)
rdoc (3.10 ruby)

再來我們切換到1.8.7,然後安裝Rails:

> rvm 1.8.7
> gem install rails

然後你就可以看到一大票的安裝訊息,然後我們看看gem的安裝情形:

> gem list

*** LOCAL GEMS ***

actionmailer (3.1.1)
actionpack (3.1.1)
activemodel (3.1.1)
activerecord (3.1.1)
.. 略 ..
rails (3.1.1)
railties (3.1.1)
rake (0.9.2 ruby)
rdoc (3.10 ruby)
sprockets (2.0.2)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
tzinfo (0.3.30)

安裝了最新版本的Rails 3.1.1,這時候切回去Ruby 1.9.2再看原來的gem list的話,還是原來的樣子,表示gem是跟著不同版本的Ruby獨立安裝的。

要注意的是我們在RVM安裝的任何Ruby版本都跟系統的Ruby無關,即使版號相同也是不同的Ruby,gem也不會安裝到系統的Ruby裡,所以可以放心的玩。基本上你只要發現你在安裝gem的時候會需要root權限的時候,通常你用的就是系統版本的Ruby了。

Gemset

Ruby/Rails的世界在進步很快,套件的版本會一直在變,所以很常看到這個狀況:

> gem list
.. 略 ..
rails (3.1.1, 3.1.0, 3.0.9, 3.0.7)
.. 略 ..

同一個套件就裝了二、三個以上的版本,看了實在有些討厭,而且也不知道會不會遇到版本衝突的問題。如果你跟我一樣有這方面的潔癖,你可以選擇把不用的版本移除掉,不過我會建議你用不同的gemset來管理。例如我想同時在1.9.2版本的Ruby底下同時安裝Rails 2.3.9跟3.1.1版本,來看看怎麼做。

先切換到1.9.2之後,建立一個準備要來裝Rails 2.3.9版的gemset:

> rvm gemset create rails-2.3.9
'rails-2.3.9' gemset created (/home/eddie/.rvm/gems/ruby-1.9.2-p290@rails-2.3.9).

這邊不一定要跟我一樣用rails-2.3.9,gemset的名字你可以取自己喜歡的。再來建一個給Rails 3.1.1版的:

> rvm gemset create rails-3.1.1
'rails-3.1.1' gemset created (/home/eddie/.rvm/gems/ruby-1.9.2-p290@rails-3.1.1).

看一下目前的已經建立的gemset:

> rvm gemset list

gemsets for ruby-1.9.2-p290 (found in /home/eddie/.rvm/gems/ruby-1.9.2-p290)
        global
        rails-2.3.9
        rails-3.1.1

你看到2個我們剛剛建好的gemset,那個global是整個Ruby預設的gemset,如果沒特別指定的話,gem都是安裝到global裡面。再來我們切換到rails-2.3.9:

  > rvm gemset use rails-2.3.9

來安裝一下Rails 2.3.9版

> gem install rails -v='2.3.9' --no-rdoc --no-ri
Fetching: activesupport-2.3.9.gem (100%)
Fetching: activerecord-2.3.9.gem (100%)
.. 略 ..
Successfully installed activeresource-2.3.9
Successfully installed rails-2.3.9
7 gems installed

安裝完成,再來切換到rails-3.1.1:

> rvm gemset use rails-3.1.1

再來做一樣的動作,只是版號改成3.1.1。兩個gemset都裝好之後來看看結果:

> rvm gemset use rails-2.3.9
> rails -v
Rails 2.3.9

> rvm gemset use rails-3.1.1
> rails -v
Rails 3.1.1

如果你要從別的版本的Ruby直接切換到指定的gemset:

> rvm 1.9.2@rails-3.1.1

你可以隨你高興的建立、切換gemset,每個gemset都是獨立的。如果玩膩了或玩爛了,想要把rails-2.3.9這個gemset清空的話:

> rvm gemset empty rails-2.3.9
WARN: Are you SURE you wish to remove the installed gems for gemset 'ruby-1.9.2-p290@global' (/home/eddie/.rvm/gems/ruby-1.9.2-p290@global)?
(anything other than 'yes' will cancel) > yes

清空只會把安裝的gem砍掉,gemset的名字還會在;如果想把整個gemset刪掉:

> rvm gemset delete rails-2.3.9
WARN: Are you SURE you wish to remove the entire gemset directory 'rails-2.3.9' (/home/eddie/.rvm/gems/ruby-1.9.2-p290@rails-2.3.9)?
(anything other than 'yes' will cancel) > yes

各別專案設定

有時候我們會遇到專案A用的是Ruby 1.8.7,專案B用的是Ruby 1.9.2,雖然RVM可以很快的切換,但偶爾忘了換回來可能也會造成一些困擾。這時候你可以在該專案的資料夾底下放一個.rvmrc的檔案,只要簡單的一行設定:

rvm use 1.9.2@rails-3.1.1

如果後面沒特別指定的話,預設會使用global的gemset。在你第一次進到該資料夾的時候會出現一個提示訊息:

> cd project1
==============================================================================
= NOTICE                                                                     =
==============================================================================
= RVM has encountered a new or modified .rvmrc file in the current directory =
= This is a shell script and therefore may contain any shell commands.       =
=                                                                            =
= Examine the contents of this file carefully to be sure the contents are    =
= safe before trusting it! ( Choose v[iew] below to view the contents )      =
==============================================================================
Do you wish to trust this .rvmrc file? (/tmp/project1/.rvmrc)
y[es], n[o], v[iew], c[ancel]> yes
Using /home/eddie/.rvm/gems/ruby-1.9.2-p290 with gemset rails-3.1.1

這樣當你下次再用cd指令進到該資料夾的時候,就會自動幫你切換成你指定的Ruby + gemset版本。

你可能會遇到的麻煩..

RVM 很方便、很好用,但之前在OSSF工作坊開Ruby的課的時候也曾經吃過悶虧。RVM 在安裝的時候,有些特別的package的安裝需要另外處理,像OpenSSL就是其中一例。就算用系統的apt-get install來安裝也沒用,RVM 也是找不到而出現LoadError。需要用以下面這個方式來安裝:

> rvm pkg install openssl
> rvm remove 1.9.2
> rvm install 1.9.2 --with-openssl-dir=$rvm_path/usr

除了OpenSSL之外,Readline也有類似的狀況,如果你是用RVM在Ubuntu上練習寫Rails的時候特別容易遇到,這點還請多留意。更多詳細內容可參考這裡

結論

RVM可以快速的在各個版本之間切換,每個版本跟gemset之間也都切得很乾淨,不會影響到原來系統設定,對我這種喜歡嚐鮮、試新玩具又怕把系統搞髒搞爛的人來說,真的是一大福音!

參考資料

當jQuery遇上CoffeeScript

上週的PHPConf Taiwan,有幾位朋友在會後問我CoffeeScript要怎麼樣跟jQuery之類的framework整合在一起,會不會打架? 基本上是不會的,CoffeeScript寫出來的東西就是JavaScript,如果會打架,那表示你即使不用CoffeeScript來寫也一樣打架了。

我們來看看要怎麼把jQuery跟CoffeeScript放一起。

簡單的說,就跟你平常在用jQuery沒什麼兩樣,例如你要取得頁面上某個元素:

1
$pokemon = $('#pikachu')

就可以取得頁面上某個id叫做pikachu的元素(這例子是從那本CoffeeScript的書上借來的 orz)。

再來,你可能寫過這樣的程式:

1
2
3
$().ready = function(){
  alert("hello, jQuery");
};

就是在頁面載入完成後跟你打聲招呼的程式,在CoffeeScript裡你就這樣寫:

1
2
$().ready = ->
  alert "hello jQuery"

如果你是用這種寫法:

1
2
3
$(function() {
  alert("hello, jQuery");
});

改成CoffeeScript則是:

1
2
$ ->
  alert "hello, jQuery"

其實寫法跟原來的寫法沒什麼兩樣,就一樣$給它用下去吧。

你可能也寫過類似這樣的東西:

1
2
3
4
5
$(function(){
  $('h1').click(function() {
      $(this).html('I am clicked');
  });
});

意思是「點擊h1元素之後改變它的內容」,改成用CoffeeScript寫的話:

1
2
3
$ ->
  $('h1').click ->
    $(@).html 'I am clicked'

好像已經簡單到我都不好意思要寫這篇了..

如果要寫jQuery的Plugin呢? 舉個簡單的例子,例用Regular Expression來檢查Email格式的小程式,原本可能會長得像這樣:

1
2
3
4
5
6
7
(function($){
  $.fn.extend({
    isEmail: function(email){
      return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(email);
    }
  });
})(jQuery);

改成用CoffeeScript寫會長這樣:

1
2
3
4
5
$ = jQuery

$.fn.extend
  isEmail: (email) ->
    /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test email

少了一些不必要的大括號跟小括號以及分號,看起來真的清爽多了 :)

另外也有朋友問到,如果CoffeeScript這麼威的話,要不要乾脆把之前寫的JavaScript程式碼全部port成CoffeeScript嗎? 我想這問題的答案就由各位自己評估了。我是因為自己爾後的案子應該會用到Rails 3.x居多,所以會把自己之前寫的一些東西也慢慢的轉過來(但其實也沒多少程式碼),順便當做練功。

閒聊投影片製作與上台表演

請注意,本文有許多個人自以為是的內容,不一定適合每個人,所以請謹慎服用。

我之前工作的某個老闆跟我說過「每次的上台簡報就是一場表演」,這句話我一直記在心裡。即然是表演,表演內容固然很重要,但"說故事"的方法也很重要,不然內容也許真的很棒,但台下卻睡成一團。

不久前看了我的偶像之一Lindsay Fallow(a.k.a Stray)的一份簡報Robotlegs 2 and your brain,真的感覺會做投影片的人就是不一樣,即使人不在現場聽演講,簡報裡也沒有太多的文字說明,光看簡報就能大概知道作者想要表達的意思,這很酷!

所以這回在第一屆的PHPConf Taiwan上用的投影片,我也想來試一下不同的簡報風格。

投影片準備

設定方向

因為我的場次只有20分鐘,要講很詳細是不可能的,而且我猜聽眾們對這個主題應該有些陌生,所以我把投影片設定在入門簡介等級的,目的是讓大家知道或認識這個主題就好。

軟體選擇

其實軟體不是重點,沒有什麼"用XX軟體來做簡報才是王道"這回事,只要選擇自己順手的工具來做就行了。前面幾場我選擇用HTML5 + Deck.js來做,我覺得效果滿ok的。基於自以為自己是個閃客,本來想用Flash來做,但時間有點趕,而且我想做的內容可能會需要更精準的控制元素的位置,所以最後我選擇了Keynote。

製作概念

用圖片來取代我想講的話,讓聽眾看到圖片之後能自行腦補我想表達的意思,這是我這回想要試驗的目標。

例如我想表達CoffeeScript因為是最近才出來的,會不會被大家認為"嫩"、"年輕"、"不成熟"的感覺,本來想找嗷嗷待哺的幼鳥,但因為幼鳥的皮膚顏色並不好看,所以最後找了小鴨:

image

再來例如我想表達的是「JavaScript看起來很容易學、很容易寫,但要寫到夠好卻很不容易」。閃過腦袋的「簡單」是「a piece of cake」:

image

但即使是一片蛋糕,也是有人可以吃的很難看,滿嘴都是:

image

圖片真的有達到我想表達的意思嗎? 不確定,如果沒有,表示我還有很大的努力空間。

整體而言,我準備投影片的內容大概只佔了30%的時間,60%的時間在找圖,剩下的10%在順投影片。值得嗎? 我覺得這是不同的嘗試,並沒有值不值得的問題。

自我介紹

上台表演是個讓大家認識你的好機會,所以在投影片裡放個自我介紹是很重要的。這是我本來的自我介紹頁:

image

整個都是字,密密麻麻的感覺好像感覺很強大、有幾百年功力的樣子。但我其實並不想在個人頁停留太久,而且寫了一堆,別人也不見得想看,還可能被覺得是在吹噓當年有多勇。真正厲害的角色只要一句:

I'm CEO, Bitch

輕描淡寫的一句話,大家就知道他的趴數了。

為了讓自己更快讓別人知道我的比較熟悉的技能,所以我做了這張,跟大家說我是寫Flash的:

image

然後把前面那個都是字的內容縮減成這樣:

image

其實字還是滿多的,所以我不會全部照著唸,在這頁我大概只會停留10秒鐘以內就會離開。至於網站或是聯絡資訊,反正也不會有人想看,所以我在放在最後一張。我把它放在最後一張並不表示不讓它登場。我大概計算過,在我跟大家說謝謝準備下台之後,從我原本站的位置走到我的電腦、關掉投影片,大概有5秒左右的時間,這個時間我預計大家都在拍手,然後我會在這個時候從倒數第二張的QA畫面,切換到最後一張投影片:

image

字型

全英文投影片? 英文是很厲害是嗎?

當然不是! 不得不說,中文字型的筆畫多,如果想走精緻路線很吃虧,所以除了我的名字是中文的之外,其它的我選用英文來寫。

為了不讓字型搶了圖片的風采,所以我想找細一點的字型,但如果太細到時候播放出來又擔心看不清楚,所以字型的樣式跟粗細在用的時候是要很注意的。系統裡的字型沒有我看喜歡的,所以我上網找找看有沒有漂亮的字型,後來找到Rawengulk這套字型,樣子看起來像這樣:

image

免費而且可商業使用。雖然它不是等寬字,不過這套字型從最細到最粗看起來都滿舒服的,在視覺上跟圖片應該會滿搭的(個人感覺)。

順便也備份了一下。

圖片來源

一開始我是使用Google的圖片搜尋功能,但圖片的尺寸、品質好壞很難控制,而且授權也不見得都有標示清楚。後來才發現Flickr是個很棒的圖片來源,照片多,而且有很多CC(Creative Commons)授權的圖片,只要搜尋的時候加勾選就行了。

image

CC授權的作品大多只要在使用的時候標記作者姓名、出處,而且不做為商業用途的話,幾乎可以無限制的使用,感謝這些人無私的奉獻。

然後因為我還沒辦法做到完全只靠圖片就說故事給大家聽,還是需要一些簡單的字比較容易說明,所以在選擇照片的時候我會特別選擇有適當的留白區塊可以讓我填字的照片。

上台表演

上台前準備

我大概花了10%的時間在練習講自己的投影片,我儘量要求自己記得所有投影片的順序,這樣我才能不用回頭看一下投影片就可以繼續講下去,腦袋裡本來預計要講的故事不會因為這樣中斷,在講的過程中因為自己知道下一頁要講什麼,也比較不會緊張。

站的位置

我不喜歡選擇站在講桌後面講話,除非是要操作電腦。

雖然不是第一次來中研院的這個會議廳,但以前都是當聽眾,不知道前面的舞台有多長多寬、視野如何,所以我一早到會場,試了一下投影機沒問題之後就先去試一下舞台,看看哪個位置是我可以看得到我目前簡報播到哪一頁,還可以看到全部的聽眾,也可以讓大家都看得到我。

image photo by othree @PHPConf Taiwan 2011

簡報器

Wii手把真的是個很不錯的簡報器。

Apple雖然有出一隻長得像ipod的遙控器,但它是紅外線的,缺點就是距離短,而且有指向性,只要站太遠或指的角度不對,就沒辦法用。而Wii是使用藍芽跟電腦連結的,不僅距離長,而且不用太刻意指向電腦就可以操作簡報。

有些手機的App可以做到類似的事,但因為手機是平的,不像Wii手把的十字鍵是凸起來的,所以我如果我對這個App不熟的話,可能得先低頭看一下手機的畫面才能操作,而且可能還得一手拿著、另一手來操作,但Wii手把的話則是只要一隻大姆指就可以輕鬆操作了。

除此之外,Wii手把在"視覺上"還有某種程度的娛樂效果 :)

image
photo by othree @PHPConf Taiwan 2011

麥克風

因為拿一般的無線麥克風的話,會讓我兩隻手都被佔用,沒辦法有其它手勢,所以我最後跟會場借了小蜜蜂來用。

其實我本來想去找像豬哥亮那種可以把麥克風吊在脖子上的東西,然後把自己搞得像夜市叫賣的,我很愛這種不協調的感覺,只是不知道要去哪邊買就是了。

與台下聽眾互動

有些人喜歡在演講過程透過問台下聽眾問題來增加與聽眾的互動,但就我觀察,台灣的聽眾普遍都比較害羞,很容易在問問題的時候會遇到沒人要鳥你的情況。所以問問題就要有用一點小技巧,例如「請問台下有沒有人平常的工作是寫JavaScript呢?」這個問題,我用了反面的問法:「請問台下有沒有人平常的工作是"不用"寫JavaScript呢?」,即使大家不捧場,也滿容易再接下去講,場面比較不會僵掉或是中斷原本的流程。

眼睛看哪裡?

當然要看正妹啊!!(誤)

上台表演難免會抖,而我自己消除緊張的方法,是在講的過程中,快速掃瞄台下對我講的內容有反應的聽眾,反應包括微笑、點頭等表示聽得懂或認同的,然後把比較多的視線停留在這些人身上來增加自己的一些自信,當自信增加到一定程度之後就比較不會抖了。

結論

記得要預先盤算你要做的每個動作的目的以及台下聽眾可能會有的反應(也許是笑、也許是問問題,或是丟雞蛋)。每次的表演結束後,如果有錄影的話,請再花點時間看看自己的表演,看看哪邊是有問題的。不要覺得不好意思,別人眼中的你才是真正的你,鏡子裡的那個自己並不是。看看自己的動作、語調,然後利用每次的演講就改掉一些缺點,才會越來越進步。

我一直相信,只要用心的準備每次的表演,台下的聽眾也會用心的回應你的,即使不是當面跟你說,也會在社群網路上看到正面的回應。

一些個人的想法,與大家分享。

自己要檢討的地方:

  • 講話依舊有些抖
  • 無謂的手勢太多
  • 時間的掌控不足