在 ES6 之前,使用 function
建立匿名函式是很熟悉的一件事:
> var arr = [1, 2, 5, 2, 5, 6, 9];
undefined
> arr.filter(function(elem) {
... return elem % 2 === 0;
... }).map(function(elem) {
... return elem * 10;
... });
[ 20, 20, 60 ]
>
可讀性很差,在 ES6 中,可以寫成:
> let arr = [1, 2, 5, 2, 5, 6, 9];
undefined
> arr.filter(elem => elem % 2 == 0).map(elem => elem * 10);
[ 20, 20, 60 ]
>
elem => elem % 2
這個運算式,在 ES6 中稱為箭號函式(Arrow Function),是類似其他語言中被稱為 Lambda 運算式的東西,不過,Lambda 運算式這名詞其實沒有嚴謹的定義,在 ES6 中就用個箭號函式名詞來區別吧!
如果有兩個以上的參數,箭號函式的參數列必須使用 ()
包含起來,沒有參數的話,就只要寫 ()
就可以了,在只有一行運算式的情況下,=>
右邊的運算式傳回值會自動 return
為箭號函式的傳回值。如果要換行的話,可以使用 {}
,最後要傳回值時可以使用 return
:
> let plus = (a, b) => a + b;
undefined
> plus(1, 2);
3
> let minus = (a, b) => {
... return a - b;
... }
undefined
> minus(10, 5)
5
>
箭號函式的目的之一,是讓那些運算本體很簡單的函式,可以用簡潔的方式撰寫,因此,雖然可以使用 {}
,然而,若本體很長的話,並不建議使用箭號函式。
另一方面,箭號函式也不單純只是 function
匿名函式的簡寫形式,它在 this
方面的解析方式,並不是依照 function
的既有方式,例如。底下是 function
的形式:
this.x = 1;
var o = {
x: 10,
foo : function() {
console.log(this.x);
}
};
o.foo();
你應該知道執行結果會顯示 10 吧!換成箭號函式的話會如何呢?
this.x = 1;
let o = {
x: 10,
foo : () => console.log(this.x)
};
o.foo();
執行結果會顯示 1,也就是說,箭號函式中的 this
,並不是依當時呼叫者來綁定的,而是依當時的語彙(Lexical)環境來綁定,白話的話,就是哪個環境包含箭號函式,而當時環境中的 this
,就是箭號函式的 this
,一旦綁定了 this
的對象,就不會再改變,就算使用 o.foo.call(o)
也不會改變。
在上面的例子中,包含箭號函式的環境是全域,在全域中寫 this
是什麼物件,就是上例中箭號函式中 this
的物件,此時就像是:
let who = this;
this.x = 1;
let o = {
x: 10,
foo : function() {
console.log(who.x);
}
};
o.foo();
再來看一個例子:
var o = {
x : 10,
foo : function() {
return {
x : 20,
doFoo : function() {
console.log(this.x);
}
}
}
};
o.foo().doFoo();
這個比較難一些,也是為什麼 function
中的 this
難以掌握的例子之一,呼叫 o.foo
會傳回一個物件,該物件有個 doFoo
方法,實際上是呼叫 o.foo
傳回的物件在呼叫 doFoo
方法,因此 doFoo
方法中的 this
會是呼叫 o.foo
傳回的物件,該物件上的 x
特性是 20,也就是最後的顯示結果。
換成箭號函式的話:
let o = {
x : 10,
foo : function() {
return {
x : 20,
doFoo : () => console.log(this.x)
}
}
};
o.foo().doFoo();
哪個環境包含了箭號函式呢?foo
特性參考的函式!在該函式中寫 this
的話,代表著 o
參考的物件,因此箭號函式中的 this
代表的就是 o
參考的物件,因此會顯示 10,就像以底下的作用:
let o = {
x : 10,
foo : function() {
var who = this;
return {
x : 20,
doFoo : function() {
console.log(who.x);
}
}
}
};
o.foo().doFoo();
如果想更進一步探索的話,箭號函式中若出現 super
,也是依當時語彙環境決定的,super
可以出現在〈物件實字簡化與增強〉談過的方法簡便定義形式,代表著物件的原型(__proto__
),因此底下的程式最後會顯示 10:
var o = {
foo() {
var o2 = {
foo2() {
console.log(super.x);
}
};
o2.__proto__ = {x: 10};
o2.foo2();
}
};
o.__proto__ = {x : 1};
o.foo();
__proto__
算是被列入標準了(儘管不建議使用,ES6 提供了標準化的 Object.setPrototypeOf
,之後有機會再介紹),在上例中,分別設定了兩個物件的 __proto__
,而 super
是依哪個物件呼叫了方法而綁定的。
如果改成箭號函式的話:
var o = {
foo() {
var o2 = {
foo2 : () => console.log(super.x)
};
o2.__proto__ = {x: 10};
o2.foo2();
}
};
o.__proto__ = {x : 1};
o.foo();
其實如果單看 o2
的定義,是不能有 super
出現的,然而由於有 foo
方法的包覆,而 super
來自於 foo
環境,因此上面的程式得以執行成功,並且顯示 1 的結果。