# 3字符串
### 大寫單詞首字母
### 問題
你想把字符串中每個單詞的首字母轉換為大寫形式。
### 解決方案
使用“拆分-映射-拼接”模式:先把字符串拆分成單詞,然后通過映射來大寫單詞第一個字母小寫其他字母,最后再將轉換后的單詞拼接成字符串。
~~~
("foo bar baz".split(' ').map (word) -> word[0].toUpperCase() + word[1..-1].toLowerCase()).join ' '
# => 'Foo Bar Baz'
~~~
或者使用列表推導(comprehension),也可以實現同樣的結果:
~~~
(word[0].toUpperCase() + word[1..-1].toLowerCase() for word in "foo bar baz".split /\s+/).join ' '
# => 'Foo Bar Baz'
~~~
### 討論
“拆分-映射-拼接”是一種常用的腳本編寫模式,可以追溯到Perl語言。如果能把這個功能直接通過“[擴展類](http://coffeescript-cookbook.github.io/chapters/objects/extending-classes)”放到String類里,就更方便了。
需要注意的是,“拆分-映射-拼接”模式存在兩個問題。第一個問題,只有在文本形式統一的情況下才能有效拆分文本。如果來源字符串中有分隔符包含多個空白符,就需要考慮怎么過濾掉多余的空單詞。一種解決方案是使用正則表達式來匹配空白符的串,而不是像前面那樣只匹配一個空格:
~~~
("foo bar baz".split(/\s+/).map (word) -> word[0].toUpperCase() + word[1..-1].toLowerCase()).join ' '
# => 'Foo Bar Baz'
~~~
但這樣做又會導致第二個問題:在結果字符串中,原來的空白符串經過拼接就只剩下一個空格了。
不過,一般來說,這兩個問題還是可以接受的。所以,“拆分-映射-拼接”仍然是一種有效的技術。
### 查找子字符串
### 問題
你想在一條消息中查找某個關鍵字第一次或最后一次出現的位置。
### 解決方案
分別使用 JavaScript 的 indexOf() 和 lastIndexOf() 方法查找字符串第一次和最后一次出現的位置。語法: string.indexOf searchstring, start
~~~
message = "This is a test string. This has a repeat or two. This might even have a third."
message.indexOf "This", 0
# => 0
?
?
# Modifying the start parameter
?
message.indexOf "This", 5
# => 23
?
?
message.lastIndexOf "This"
# => 49
~~~
### 討論
還需要想辦法統計出給定字符串在一條消息中出現的次數。
### 生成唯一ID
### 問題
你想隨機生成一個唯一的標識符。
### 解決方案
可以根據一個隨機數值生成一個 Base 36 編碼的字符串。
~~~
uniqueId = (length=8) ->
id = ""
id += Math.random().toString(36).substr(2) while id.length < length
id.substr 0, length
?
uniqueId() # => n5yjla3b
uniqueId(2) # => 0d
uniqueId(20) # => ox9eo7rt3ej0pb9kqlke
uniqueId(40) # => xu2vo4xjn4g0t3xr74zmndshrqlivn291d584alj
~~~
### 討論
使用其他技術也可以,但這種方法相對來說性能更高,也更靈活。
### 字符串插值
### 問題
你想創建一個字符串,讓它包含體現某個 CoffeeScript 變量的文本。
### 解決方案
使用 CoffeeScript 中類似 Ruby 的字符串插值,而不是 JavaScript 的字符串拼接。
插值:
~~~
muppet = "Beeker"
favorite = "My favorite muppet is #{muppet}!"
?
# => "My favorite muppet is Beeker!"
~~~
~~~
square = (x) -> x * x
message = "The square of 7 is #{square 7}."
?
# => "The square of 7 is 49."
~~~
### 討論
CoffeeScript 的插值與 Ruby 類似,多數表達式都可以用在 #{ ... } 插值結構中。
CoffeeScript 支持在插值結構中放入多個有副作用的表達式,但建議大家不要這樣做。因為只有表達式的最后一個值會被插入。
~~~
# 可以這樣做,但不要這樣做。否則,你會瘋掉。
?
square = (x) -> x * x
muppet = "Beeker"
message = "The square of 10 is #{muppet='Animal'; square 10}. Oh, and your favorite muppet is now #{muppet}."
?
# => "The square of 10 is 100. Oh, and your favorite muppet is now Animal."
~~~
### 把字符串轉換為小寫形式
### 問題
你想把字符串轉換成小寫形式。
### 解決方案
使用 JavaScript 的 String 的 toLowerCase() 方法:
~~~
"ONE TWO THREE".toLowerCase()
# => 'one two three'
~~~
### 討論
toLowerCase() 是一個標準的 JavaScript 方法。不要忘了帶圓括號。
### 語法塊
通過下面的快捷方式可以添加某種類似 Ruby 的語法塊:
~~~
String::downcase = -> @toLowerCase()
"ONE TWO THREE".downcase()
# => 'one two three'
~~~
上面的代碼演示了 CoffeeScript 的兩個特性:
- 雙冒號 :: 是引用 `.prototype` 的快捷方式;
- “at” 字符 @ 是引用 this 的快捷方式。
上面的代碼會編譯成如下 JavaScript 代碼:
~~~
String.prototype.downcase = function() {
return this.toLowerCase();
};
"ONE TWO THREE".downcase();
~~~
**提示**盡管上面的用法在類似 Ruby 的語言中很常見,但在 JavaScript 中對本地對象的擴展經常被視為不好的。(請看:[Maintainable JavaScript: Don’t modify objects you don’t own](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/);[Extending built-in native objects. Evil or not?](http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/))
### 匹配字符串
### 問題
你想要匹配兩個或多個字符串。
### 解決方案
計算把一個字符串轉換成另一個字符串所需的編輯距離或操作數。
~~~
levenshtein = (str1, str2) ->
?
l1 = str1.length
l2 = str2.length
prevDist = [0..l2]
nextDist = [0..l2]
?
for i in [1..l1] by 1
nextDist[0] = i
for j in [1..l2] by 1
if (str1.charAt i-1) == (str2.charAt j-1)
nextDist[j] = prevDist[j-1]
else
nextDist[j] = 1 + Math.min prevDist[j], nextDist[j-1], prevDist[j-1]
[prevDist,nextDist]=[nextDist, prevDist]
?
prevDist[l2]
~~~
### 討論
可以使用赫斯伯格( Hirschberg )或瓦格納菲舍爾( Wagner–Fischer)的算法來計算來文史特( Levenshtein )距離。這個例子用的是瓦格納菲舍爾算法。
這個版本的文史特算法和內存呈線性關系,和時間呈二次方關系。
在這里我們使用 str.charAt i 這種表示法而不用 str[i] 這種方式,是因為后者在某些瀏覽器(如 IE7)中不支持。
起初,"by 1" 在兩次循環中看起來似乎是沒用的。它在這里是用來避免一個 coffeescript [i..j] 語法的常見錯誤。如果 str1 或 str2 為空字符串,那么 [1..l1] 或 [1..l2] 將會返回 [1,0] 。添加了 "by 1" 的循環也能編譯出更加簡潔高效的 javascript 。
最后,循環結尾處對回收數組的優化在這里主要是為了演示 coffeescript 中交換兩個變量的語法。
### 重復字符串
### 問題
你想重復一個字符串。
### 解決方案
創建一個包含 n+1 個空元素的數組,然后用要重復的字符串作為連接字符將數組元素拼接到一起:
~~~
# 創建包含10個foo的字符串
?
Array(11).join 'foo'
?
# => "foofoofoofoofoofoofoofoofoofoo"
~~~
### 為字符串重復方法
你也可以在字符串的原型中為其創建方法。它十分簡單:
~~~
# 為所有的字符串添加重復方法,這會重復返回 n 次字符串
?
String::repeat = (n) -> Array(n+1).join(this)
~~~
### 討論
JavaScript 缺少字符串重復函數,CoffeeScript 也沒有提供。雖然在這里也可以使用列表推導( comprehensions ),但對于簡單的字符串重復來說,還是像這樣先構建一個包含 n+1 個空元素的數組,然后再把它拼接起來更方便。
### 拆分字符串
### 問題
你想拆分一個字符串。
### 解決方案
使用 JavaScript 字符串的 split() 方法:
~~~
"foo bar baz".split " "
# => [ 'foo', 'bar', 'baz' ]
~~~
### 討論
String 的這個 split() 方法是標準的 JavaScript 方法。可以用來基于任何分隔符——包括正則表達式來拆分字符串。這個方法還可以接受第二個參數,用于指定返回的子字符串數目。
~~~
"foo-bar-baz".split "-"
# => [ 'foo', 'bar', 'baz' ]
~~~
```
"foo bar \t baz".split /\s+/ # => [ 'foo', 'bar', 'baz' ]
~~~
?
~~~
"the sun goes down and I sit on the old broken-down river pier".split " ", 2 # => [ 'the', 'sun' ]
~~~
?
?
?
清理字符串前后的空白符
?
?
問題
?
?
你想清理字符串前后的空白符。
?
解決方案
?
?
使用 JavaScript 的正則表達式來替換空白符。
?
要清理字符串前后的空白符,可以使用以下代碼:
~~~
" padded string ".replace /^\s+|\s+$/g, "" # => 'padded string'
~~~
?
如果只想清理字符串前面的空白符,使用以下代碼:
~~~
" padded string ".replace /^\s+/g, "" # => 'padded string '
~~~
?
如果只想清理字符串后面的空白符,使用以下代碼:
~~~
" padded string ".replace /\s+$/g, "" # => ' padded string'
~~~
?
討論
?
?
Opera、Firefox 和 Chrome 中 String 的原型都有原生的 trim 方法,其他瀏覽器也可以添加一個。對于這個方法而言,還是盡可能使用內置方法,否則就創建一個 polyfill:
~~~
unless String::trim then String::trim = -> @replace /^\s+|\s+$/g, ""
" padded string ".trim() # => 'padded string'
~~~
?
語法塊
?
?
還可以添加一些類似Ruby中的語法塊,定義如下快捷方法:
~~~
String::strip = -> if String::trim? then @trim() else @replace /^\s+|\s+$/g, ""String::lstrip = -> @replace /^\s+/g, ""String::rstrip = -> @replace /\s+$/g, ""
" padded string ".strip() # => 'padded string'
" padded string ".lstrip() # => 'padded string '
" padded string ".rstrip() # => ' padded string'
~~~
?
要想深入了解 JavaScript 執行 trim 操作時的性能,請參見 Steve Levithan 的這篇博客文章。
?
?
把字符串轉換為大寫形式
?
?
問題
?
?
你想把字符串轉換成大寫形式。
?
解決方案
?
?
使用 JavaScript 的 String 的 toUpperCase() 方法:
~~~
"one two three".toUpperCase() # => 'ONE TWO THREE'
~~~
?
討論
?
?
toUpperCase() 是一個標準的 JavaScript 方法。不要忘了帶圓括號。
?
語法塊
?
?
通過下面的快捷方式可以添加某種類似 Ruby 的語法塊:
~~~
String::upcase = -> @toUpperCase()"one two three".upcase() # => 'ONE TWO THREE'
~~~
?
上面的代碼演示了 CoffeeScript 的兩個特性:
?
* 雙冒號 :: 是引用 .prototype 的快捷方式;
* “at” 字符 @ 是引用 this 的快捷方式。
?
上面的代碼會編譯成如下 JavaScript 代碼:
~~~
String.prototype.upcase = function() { return this.toUpperCase();};"one two three".upcase();```
**提示**盡管上面的用法在類似 Ruby 的語言中很常見,但在 JavaScript 中對本地對象的擴展經常被視為不好的。(請看:[Maintainable JavaScript: Don’t modify objects you don’t own](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/);[Extending built-in native objects. Evil or not?](http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/))