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,例如:
skinny = (name) ->
"hello, I'm skinny #{name}"
console.log(skinny "eddie")
編譯出來的結果是:
var skinny;
skinny = function(name) {
return "hello, I'm skinny " + name;
};
console.log(skinny("eddie"));
執行結果是:
hello, I'm skinny eddie
而 =>
也是會產生 Anonymous function:
fatty = (name) =>
"hello, I'm fatty #{name}"
console.log(fatty "eddie")
執行結果是:
hello, I'm fatty eddie
就以程式的執行結果來看,用 ->
跟用 =>
似乎沒什麼兩樣,但看一下用 =>
編譯出來的 Javascript 程式碼:
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()
直接來看段程式碼:
var who = "Eddie";
function doctor(){
alert(who);
}
doctor();
這光用我們的人腦執行就猜得到結果是 Eddie
,如果我們再改一下:
var who = "Eddie";
function doctor(){
alert(this.who);
}
doctor();
多了個 this
,但執行的結果還是一樣。這裡的 this.who
其實指的就是整個 global 的那個 who
,所以印出來結果也一樣。再來加一點變化:
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()
也可以改寫成:
doctor.call(this);
執行結果是一樣的。再來繼續再加點變化,call()
還可以傳更多的參數進去:
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()
的參數則是一個一個傳進去,並用逗號分開:
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 編譯出來的程式碼:
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 的時候會用得上,來看個範例:
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,大概就可以知道 ->
跟 =>
各別做什麼不同的事。
以上,供大家參考,如果有哪邊寫錯再請跟我說 :)