跳到主要內容
黯羽輕揚每天積累一點點

箭頭函數_ES6 筆記 6

免費2016-05-02#JS#js箭头函数#js lambda表达式#js拉姆达表达式#javascript lambda expression#邱奇数#Y组合子

lambda 表達式的極致簡潔很誘人,定義函數就像寫數學公式一樣,支持函數式編程的語言本該如此

寫在前面

(這篇本來是上週的內容,昨天忘記寫啦,趕緊偷偷補上)

最近事情有點多,雖然 sort 過了,還是略顯忙亂。但無論怎樣,計劃擺在那裡,最終都會一件一件完成

廢話適可而止,願老媽身體趕緊好起來~

一。箭頭函數簡介

箭頭函數(arrow function),就是 C# 中的 lambda 表達式,據說 Java8 也把它加入了豪華午餐。但不管怎样,JS 正在從其它語言吸納優秀的特性(比如 yield, class, 預設參數等等),且不論這些特性好壞,這件事本身就是極好的(至少我們正在使用的是一個充滿活力的工具)

只是 Java 用 -> 箭頭,C# 用的箭頭與 JS 一樣:=>,這個箭頭叫「lambda 運算符」,行話讀作 "goes to"

lambda 表達式(箭頭函數)據說是定義函數最簡潔的方法,語法上幾乎沒有冗餘成分了。因為 JS 弱類型的特點,JS 中的 lambda 表達式要比 C# 和 Java 中的更簡潔(少了參數類型聲明)

一句話,箭頭函數就是 lambda 表達式,提供了更簡潔的 function 定義方式

二。語法

arg => returnVal 語法是創建函數最簡潔的方式,定義了一個形參為 arg,返回值為 returnVal 的 function

其它語法如下表:

箭頭函數語法
語法等價代碼含義
x => f(x)
function(x) {
    return f(x);
}
y=f(x)
(x, y)=>x + y;
function(x, y) {
    return x + y;
}
y=f(x,y)=x+y
(x, y)=>{g(x); g(y); return h(x, y);};
function(x, y) {
    g(x);
    g(y);
    return h(x, y);
}
          g(x), g(y)
y=f(x,y)==============h(x,y)
()=>({});
function() {
    return {};
}
y={}

P.S. 第三列的「含義」指的是數學函數含義,lambda 表達式本來就是數學家弄出來的

簡單示例如下:

// 簡單例子,簡化匿名函數的定義
var arr = [1, 3, 21, 12];
console.log(arr.map(x => 2 * x));   // [2, 6, 42, 24]
console.log(arr.sort((a, b) => a - b)); // [1, 3, 12, 21]
arr.forEach((item, index, arr) => {
    if (index %2 == 0) {
        console.log(item);
    }
    if (index == arr.length - 1) {
        console.log(`last item is ${item}`);
    }
});

複雜一點的示例:

// 複雜例子
var app = {
    cache: {},
    ajax: function(url, callback) {
        var self = this;
        function req(url) {
            var res = `data from ${url}`;
            console.log(`ajax request ${url}`);
            // cache res
            self.cache[url] = res;
            return res;
        }
        var data = req(url);
        callback(data);
    }
}
app.ajax('http://www.xxx.xx', function(data) {
    console.log(`receive: ${data}`);
});
console.log(app.cache);

用箭頭函數改寫上例:

// 用箭頭函數改寫
var es6App = {
    cache: {},
    ajax(url, callback) {
        var req = url => {
            var res = `data from ${url}`;
            console.log(`ajax request ${url}`);
            // cache res
            this.cache[url] = res;
            return res;
        }
        var data = req(url);
        callback(data);
    }
}
es6App.ajax('http://www.q.xx', function(data) {
    console.log(`receive: ${data}`);
});
console.log(es6App.cache);

消除了 that = this 這種必要的廢話,其實只要遵守一項原則就可以消除所有的 that = this,見下文注意事項中的 3. 關於 this

三。特點及注意事項

###1. 參數列表與返回值的語法

1 個參數時,左邊直接寫參數名,0 個或者多個參數時,參數列表要用 () 包裹起來

函數體只有 1 條語句時,右邊值自動成為函數返回值,函數體不止 1 條語句時,函數體需要用 {} 包裹起來,並且需要手動 return

P.S. 當然,可能很容易想到不分青紅皂白,把 () => {} 作為箭頭函數的起手式,但不建議這樣做,因為下一條說了 { 是有歧義的,可能會帶來麻煩

###2. 有歧義的字符

{唯一1 個有歧義的字符,所以返回對象字面量時需要用 () 包裹,否則會被當作塊語句解析

例如:

var f1 = () => {};
f1();   // 返回 undefined
// 等價於
// var f1 = function() {};

var f2 = () => ({});
f2();   // 返回空對象 {}
// 等價於
// var f2 = function() {return {};};

###3. 關於 this

箭頭函數會從外圍作用域繼承 this,為了避免 that = this,需要遵守:除了對象上的直接函數屬性值用 function 語法外,其它函數都用箭頭函數

這個規則很容易理解,示例如下:

// 場景 1
function MyType() {}
MyType.prototype.fn = function() {/*定義箭頭函數*/};  // 箭頭函數中 this 指向 MyType 類型實例

// 場景 2
var obj = {};
obj.fn = function() {/*定義箭頭函數*/};   // 箭頭函數中 this 指向 obj

區別在於 function 關鍵字定義的函數屬性中,該函數的 this 指向這個函數屬性所屬的對象(匿名函數的 this 指向 global 對象或者 undefined)。說白了,function 能定義一個新 this,而箭頭函數不能,它只能從外層借一個 this。所以,需要新 this 出現的時候用 function 定義函數,想沿用外層的 this 時就用箭頭函數

###4. 關於 arguments 對象

箭頭函數沒有 arguments 對象,因為標準鼓勵使用 [預設參數、可變參數](/articles/預設參數和不定參數-es6 筆記 4/)、[參數解構](/articles/destructuring(解構賦值)-es6 筆記 5/)

例如:

// 一般函數
(function(a) {console.log(`a = ${a}, arguments[0] = ${arguments[0]}`)})(1);
// log print: a = 1, arguments[0] = 1

// 與上面等價的箭頭函數
(a => console.log(`a = ${a}, arguments[0] = ${arguments[0]}`))(1);
// log print: Uncaught ReferenceError: arguments is not defined

這與函數匿名不匿名無關,規則就是箭頭函數中無法訪問 arguments 對象(undefined),如下:

// 非匿名函數
var f = a => console.log(`a = ${a}, arguments[0] = ${arguments[0]}`);
f(2);
// log print: Uncaught ReferenceError: arguments is not defined

四。題外話

就 ES6 箭頭函數而言,上面的內容足以隨心所欲地駕馭它了,下面我們扯點別的(有意思的)

###1.JS 中支持的所有箭頭

到 ES6 為止,目前 js 中支持的箭頭
箭頭含義
<!--單行註釋
-->在行首表示單行註釋,在其它位置表示「趨向於」(n --> 0 等價於 n-- > 0)
<=比較運算符,小於等於
=>箭頭函數

看到兩個單行註釋語法不要大驚小怪,歷史原因,但目前所有瀏覽器都支持。沒什麼用,冷知識吧

###2.lambda 演算與邱奇數

lambda 演算中唯一基礎數據類型是函數,邱奇數(church numerals)就是用高階函數表示常見的基礎數據類型(整數、布爾值、鍵值對、列表等等)

自然數都是數字,邱奇數都是函數,邱奇數的 n 是 n 階函數,f^n(inc, base) === f(inc, f(inc, ...f(inc, base))),所有邱奇數都是有 2 個參數的函數

如何用函數表示自然數?內容比較多,這裡給一個自然數集小例子:

// 定義自然數集合
var number = (function*(inc, base) {
    var n = zero;

    while(true) {
        yield n(inc, base);
        n = succ(n);
    }
})(inc, 0);
for (var n of number) {
    console.log(n); // 0, 1, 2, 3, 4
    if (n > 3) {
        break;
    }
}

用邱奇數表示的自然數集如下:

// 0, 1, 2
var zero = (inc, base) => base;
var one = (inc, base) => inc(base);
var two = (inc, base) => inc(inc(base));

// 定義後繼函數 f^n -> f^(n+1)
// succ = ln.lf.lx.f (n f x)
var succ = n => (inc, base) => inc(n(inc, base));

// 定義邱奇數集合
var church = (function*() {
    var fn = zero;

    while(true) {
        yield fn;
        fn = succ(fn);
    }
})();
// test
var [, , , three, four, five, six, seven] = church;
console.log(three(inc, 0)); // 3
console.log(four(inc, 0));  // 4
console.log(five(inc, 0));  // 5
console.log(six(inc, 0));   // 6
console.log(seven(inc, 0)); // 7

仔細想想的話會發現世界真奇妙,這樣也行??感興趣的話請查看筆者的 Demo(實現了減法、乘法和減法)

###3.Y 組合子與函數式編程

Y 組合子能實現匿名遞歸函數

Y 組合子就是一個函數,如下:

var Y = F => G(G), var G = slef => F(self(self))

其中有個不動點的概念。不動點:若 F(f) = f,則 f 是不動點,在 Y 組合子中,G(G) 是不動點

假設現有一個用來求階乘的遞歸函數:

var fact = n => n === 0 ? 1 : n * fact(n - 1);

這顯然不是一個匿名遞歸,fact 是函數名,遞歸調用它實現計算階乘。那麼如何實現一個匿名的遞歸函數?這有可能嗎?

用 Y 組合子來一發就好了,如下:

// 定義 Y 組合子
// var Y = F => G(G);
// var G = self => F(self(self));
var Y = F =>
    ((g => g(g))
        (g =>
            (F((...x) =>
                g(g)(...x)))));

// 實現匿名遞歸求階乘
var yFact = Y(f =>
    n => n === 0 ? 1 : n * f(n - 1));
console.log(yFact(5));  // 120

奇妙吧?函數式編程中有各種類似的奇妙變換,且不說 FP 的理解成本,執行效率,這些變換本身就是一些有意思的值得研究的東西,給思維多一點空間,讓 cpu 跑起來

五。總結

lambda 表達式的極致簡潔很誘人,定義函數就像寫數學公式一樣,支持函數式編程的語言本該如此

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論