[TEAP早期試讀]《深入淺出CoffeeScript》集合與迭代節選和若干問題

寸志發表於2012-02-12

圖靈社群按
TEAP是什麼?TEAP是Turingbook Early Access Program的簡稱,即早期試讀,它公佈的是圖靈在途新書未經編輯的內容。一本書的翻譯週期約為3到6個月,如果在翻譯過程中,譯者就能與讀者進行溝通和交流,對整本書的翻譯品質是有幫助的。通過TEAP,讀者可以提前閱讀將來才能出版的內容,譯者也能收穫寶貴的反饋意見,改進翻譯,提高質量。

本書原名為CoffeeScript Accelerated JavaScript Development,中文名暫定為《深入淺出CoffeeScript》,本篇內容節選自該書中的第3章集合與迭代

本人初次翻譯這種非常正式的技術書籍,難免有不妥之處,若有問題或建議,歡迎大家與我交流,我的郵箱:island205#gmail.com,也可以在微博上@

在本文的最後,我還加幾個與翻譯本書相關的問題,希望大家能多多參與交流。下面就先來看看部分節選的譯稿吧!

3.2 陣列

你可以使用任意的普通物件來儲存值列表,只是陣列(繼承了Array原型中的屬性)為你提供了好幾個非常棒的特性。
你可以使用JSON式的語法來定義陣列:

mcFlys = ['George', 'Lorraine', 'Marty']

該行程式碼等價於:

mcFlys = new Array()
mcFlys[0] = 'George'
mcFlys[1] = 'Lorraine'
mcFlys[2] = 'Marty'

別忘了物件上的每一個鍵都會被轉化為字串,因此arr[1]arr[“1”]甚至包括arr[{toString:->”1”}]的含義都是一樣的。(如果某個物件有一個toString方法,則在該物件被轉化為字串時會使用該方法的返回值。)
因為陣列也是物件,所以你可以自由地為陣列加上各種屬性,儘管一般並不會這麼做。更通常的做法是直接修改Array的原型,為所有的陣列新增特殊的方法。例如,Prototype.js框架以此為陣列新增了像flatteneach這類方法,使其更像Ruby的陣列。

區間

開啟REPL吧,因為熟悉CoffeeScript區間語法(以及在下一小節中即將介紹與之密切相關的切分(slice)和剪接(splice)語法)的最好方法就是('practice' for i in [1..3]).join(', ')。 CoffeeScript引入了Ruby式語法來定義一個連續的整數陣列:

coffee> [1..5]
[1, 2, 3, 4, 5]

..被用來定義閉區間(inclusive range)。但是通常我們希望能夠忽略最後一個值,如果是這樣,可以新增一個.來生成排外區間(exclusive rage):

coffee> [1...5]
[1, 2, 3, 4]

(為了方便記憶,可想像額外的.代替了最後一項值。)區間也可以是倒序的:

coffee> [5..1]
[5, 4, 3, 2, 1]

不管順序是正是反,排外區間總是省略掉末尾的值:

coffee> [5...1]
[5, 4, 3, 2]

通常很少單獨使用這種語法,我們很快就會發現,它是CoffeeScriptfor迴圈不可或缺的一部分。

切分和剪接

當你想從陣列中切出一塊時,需要使用聽起來有點暴力的slice方法:

coffee> ['a', 'b', 'c', 'd'].slice 0, 3
['a', 'b', 'c']

傳給slice的兩個數字是索引值。從第一個索引開始到第二個索引結束但不包括第二個索引內的所有東西都拷貝到返回結果裡。你可能一看就會說:“這聽起來有點像排外區間 。”你說對了:

coffee> ['a', 'b', 'c', 'd'][0...3]
['a', 'b', 'c']

你也可以使用閉區間:

coffee> ['a', 'b', 'c', 'd'][0..3]
['a', 'b', 'c', 'd']

這裡的規則較閉區間來說稍有不同,這都是因 為slice的特殊性所致。特別地,如果第一個索引的指向位置在第二個的指向位置之後,結果就會是一個空陣列而不是一個倒轉的陣列:

coffee> ['a', 'b', 'c', 'd'][3...0]
[]

還有,負索引會從陣列末尾往前倒數,雖然arr[-1]只是表示查詢arr中名為“-1”的屬性值,但是arr[0…-1]的意思卻是“把從陣列開始直到但不包含最後一個元素的切分返回給我。”換句話說,當切分arr時,-1的意義與arr.length-1相同。
如果你省略了第二個索引,則無論你使用的是兩個還是三個點,都會一直切分到陣列的末尾:

coffee> ['this', 'that', 'the other'][1..]
['that', 'the other']
coffee> ['this', 'that', 'the other'][1...]
['that', 'the other']

CoffeeScript還為splice——slice的用於插值的兄弟方法——提供了一個簡寫法。它看起來就像是在給切分賦值:

coffee> arr = ['a', 'c']
coffee> arr[1...2] = ['b']
coffee> arr
['a', 'b']

字串的切分

有意思的是,JavaScript也為字串提供了一個slice方法,儘管它的行為與substring方法一致。這就非常方便了,因為意味這你可以使用CoffeeSript的切分語法獲取子字串:

coffee> 'The year is 3022'[-4..]
3022

然而,別太發散——雖然切分適用於字串,但是剪接卻不行。因為在JavaScript中字串一旦被定義就永遠不能修改了。


區間即表示陣列被替換的部分。如果該區間為空,則會從第一個索引處開始直接插入值。

coffee> arr = ['a', 'c']
coffee> arr[1...1] = ['b']
coffee> arr
['a', 'b', 'c']

有一點差異需要引起注意:雖然負數索引在切分時能夠表現完美,但是在剪接時就完全不行了。但是省略第二個索引的技巧也還適用:

coffee> steveAustin = ['regular', 'guy']
coffee> replacementParts = ['better', 'stronger', 'faster']
coffee> steveAustin[0..] = replacementParts
coffee> steveAustin
['better', 'stronger', 'faster']

關於切分和剪接的內容就這麼多。你可以認為自己是使用區間提取子字串和子陣列方面的專家啦!但是在for…in 語法中區間還有另外一種更加富有想象力的用法,在下一節中我們就會看到。

3.3 集合的迭代

在CoffeeScript中內建了兩種語法來對集合進行迭代:物件迭代和陣列迭代(包括其他可列舉物件,通常指的就是陣列)。雖然兩者看起來很像,但是它們的表現卻很不一樣。 使用下面的句法來迭代物件的屬性:

for key, value of object
  # do things with key and value

該迴圈會迭代該物件的所有鍵名,並且將其賦值給for後面第一個具名變數。第二個變數,即上面名為value的變數,可以省略。正如你所期望的那樣,它會被賦上與鍵相對應的值,因此value=object[key]


“hasOwnProperty”與“for own”

在JavaScript中,物件“自己”的屬性和從原型上繼承而來的屬性是有差別的。你可以使用object.hasOwnProperty(key)來檢測某個特定屬性是不是物件“自己”的。
因為大家通常希望迭代物件自己的屬性,而不是迭代那些與同類共享的屬性,所以CoffeeScript允許你使用for own自動進行檢查且跳過那些沒有通過檢查的屬性。這裡有個例子:

for own sword of Kahless
  ...

它是下面這段程式碼的簡寫:

for sword of Kahless
  continue unless Kahless.hasOwnProperty(sword)
  ...

每當for…of給出了你不想要的屬性時,使用for own…in替換了試試看。


對於陣列來說,語法稍稍有點不一樣:

for value in array
  # do things with the value

為什麼需要使用不一樣的語法呢?為什麼不直接使用for key, value of array呢?這是因為我們不能阻止陣列擁有額外的方法和資料。如果你想要它的全部家當,那用of沒問題。但是如果你就是想把陣列當作一個陣列,那麼就使用in——你只會依次地獲得array[0]array[1]等,直到array[array.length-1]為止。
兩種風格的for迴圈都可以在後面跟一個when從句,當給定的條件為假時,就跳過當前的迭代。例如,下面的程式碼會忽略掉obj上非方法屬性而呼叫其他所有的方法:

for key, func of obj when typeof func is 'function'
  func()

下面這段程式碼只有在big比較大時才會把其賦值給highestBig

highestBid = 0
for bid of entries when bid > highestBid
  highestBid = bid

當然,我們也可以在迴圈體內使用continueunless等條件語句來替代when從句。但是when是一個非常有用的語法糖,尤其對於單行程式碼控來說。就如我們將在47頁3.5節列表解析中看到的一樣,它還是可以阻止任何值被新增到迴圈返回值陣列中的唯一方法。


無作用域的for
當你寫for x of obj或者for x in arr時,你其實正在給一個當前作用域內名為x的變數賦值。在迴圈結束後你還可以繼續利用它們。看這個例子:

for name, occupation of murderMysteryCharacters
  break if occupation is 'butler'
console.log "#{name} did it!"

再看另外一個例子:

countdown = [10..0]
for num in countdown
  break if abortLaunch()
if num is 0
  console.log 'We have liftoff!'
else
  console.log "Launch aborted with #{num} seconds left"

但是這樣作用域的缺少也會讓你出其不意,尤其是當你在迴圈內定義了函式的時候。因此在不確定的情況下,就用do來捕獲每個迭代內的變數:

for x in arr
  do (x) ->
    setTimeout (-> console.log x), 0

我們還會在3.9節練習中來回顧這個問題,詳見56頁。


for…in支援一個其表兄for…of並不支援的補充修飾符:by。比起每次逐個迴圈(預設情況)整個陣列,by可以讓你隨意地設定步值 :

decimate = (army) ->
  execute(soldier) for soldier in army by 10

步值並非必須是整數。分數能與區間正常地協同工作:

animate = (startTime, endTime, framesPerSecond) ->
  for pos in [startTime..endTime] by 1 / framesPerSecond
    addFrame pos

你也可以使用一個負步值反過來從區間末尾開始迭代:

countdown = (max) ->
  console.log x for x in [max..0] by -1

但是要注意,陣列迭代並不支援負步值。當你寫for…in [start..end]時,start是第一個迭代值(而end是最後一個迭代值),只要start>end時負數步值就沒有問題。但是每當你寫for…in arr時,第一個迭代索引值總是0,最後一個迭代索引總是arr.length-1。因此如果arr.length大於零,則負步值會產生死迴圈——永遠不可能達到最後一個迭代索引!
關於for…offor…in,你應該瞭解的就這麼多。記住最重要的一點,CoffeeScript中的of與JavaScript中的in等價。可以這樣來想:值在陣列中(in),而你知道陣列的(of)鍵。
ofin作為運算子有兩種存在方式:key of obj用來檢測obj[key]是否已被賦值,而x in arr則是用來檢測arr是否存在某個值等於x。正如for…in迴圈,in運算子只能用在陣列上(還包括其他可列舉實體,比如說arguments和jQuery物件)。下面是一個例子:

fruits = ['apple', 'cherry', 'tomato']
'tomato' in fruits # true
germanToEnglish: {ja: 'yes', nein: 'no'}
'ja' of germanToEnglish #true
germanToEnglish[ja]?

如果你想檢測一個非列舉物件是否包含某個特定值時該怎麼辦?我們把這個問題留到練習中去解答。


下面是我的幾個問題

  • 書名就叫《深入淺出CoffeeScript》嗎?
    這個問題本可忽略,這確實是一本簡單易懂的入門書籍。但原書名為CoffeeScript: Accelerated JavaScript Development,後面的Accelerated JavaScript Development譯為“加快JavaScript開發”。中文版是否以此作為副標題,這樣讀者也能夠一眼認出CoffeeScript與JavaScript的關係。畢竟CoffeeScript的金科玉律就是“it's just JavaScript!”。不知大家怎麼看?

  • 專有名詞的問題
    在本書中出現了程式設計領域比較少見的程式設計術語,比方說splatssoaksexistential operatordestructuring assignment等等。當然這些名詞不是沒有其他語言使用,基本上Ruby或者Python都有用到。但splats在其他相關書籍中有的直接翻譯為引數列表,鑑於該書中已有“arguments list”已翻譯為引數列表,以示區分,我將splats譯為了“引數槽”,雖然與splats的英文原意稍有差別,但是還算形象。 大家覺得這樣恰當嗎?

  • 關於書中的一些錯誤
    作者也在本書的謝辭中說到: “感謝諸位技術評審——任何遺留的錯誤絕對都是‘我的不對’。”在http://pragprog.com/titles/tbcoffee/errata上也能找到英文原版上的一些錯誤,為了保證譯文與原作的一一對應,我儘量會把上面的勘誤在譯文中以“譯者注”的方式標出,以便讀者知會。不知這樣做是否恰當?

相關文章