JavaScript 專題之偏函式

冴羽發表於2017-08-16

JavaScript 專題系列第十四篇,講解偏函式以及如何實現一個 partial 函式

定義

維基百科中對偏函式 (Partial application) 的定義為:

In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.

翻譯成中文:

在電腦科學中,區域性應用是指固定一個函式的一些引數,然後產生另一個更小元的函式。

什麼是元?元是指函式引數的個數,比如一個帶有兩個引數的函式被稱為二元函式。

舉個簡單的例子:

function add(a, b) {
    return a + b;
}

// 執行 add 函式,一次傳入兩個引數即可
add(1, 2) // 3

// 假設有一個 partial 函式可以做到區域性應用
var addOne = partial(add, 1);

addOne(2) // 3複製程式碼

個人覺得翻譯成“區域性應用”或許更貼切些,以下全部使用“區域性應用”。

柯里化與區域性應用

如果看過上一篇文章《JavaScript專題之柯里化》,實際上你會發現這個例子和柯里化太像了,所以兩者到底是有什麼區別呢?

其實也很明顯:

柯里化是將一個多引數函式轉換成多個單引數函式,也就是將一個 n 元函式轉換成 n 個一元函式。

區域性應用則是固定一個函式的一個或者多個引數,也就是將一個 n 元函式轉換成一個 n - x 元函式。

如果說兩者有什麼關係的話,引用 functional-programming-jargon 中的描述就是:

Curried functions are automatically partially applied.

partial

我們今天的目的是模仿 underscore 寫一個 partial 函式,比起 curry 函式,這個顯然簡單了很多。

也許你在想我們可以直接使用 bind 吶,舉個例子:

function add(a, b) {
    return a + b;
}

var addOne = add.bind(null, 1);

addOne(2) // 3複製程式碼

然而使用 bind 我們還是改變了 this 指向,我們要寫一個不改變 this 指向的方法。

第一版

根據之前的表述,我們可以嘗試著寫出第一版:

// 第一版
// 似曾相識的程式碼
function partial(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};複製程式碼

我們來寫個 demo 驗證下 this 的指向:

function add(a, b) {
    return a + b + this.value;
}

// var addOne = add.bind(null, 1);
var addOne = partial(add, 1);

var value = 1;
var obj = {
    value: 2,
    addOne: addOne
}
obj.addOne(2); // ???
// 使用 bind 時,結果為 4
// 使用 partial 時,結果為 5複製程式碼

第二版

然而正如 curry 函式可以使用佔位符一樣,我們希望 partial 函式也可以實現這個功能,我們再來寫第二版:

// 第二版
var _ = {};

function partial(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var position = 0, len = args.length;
        for(var i = 0; i < len; i++) {
            args[i] = args[i] === _ ? arguments[position++] : args[i]
        }
        while(position < arguments.length) args.push(argumetns[position++]);
        return fn.apply(this, args);
    };
};複製程式碼

我們驗證一下:

var subtract = function(a, b) { return b - a; };
subFrom20 = partial(subtract, _, 20);
subFrom20(5);複製程式碼

寫在最後

值得注意的是:underscore 和 lodash 都提供了 partial 函式,但只有 lodash 提供了 curry 函式。

專題系列

JavaScript專題系列目錄地址:github.com/mqyqingfeng…

JavaScript專題系列預計寫二十篇左右,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、型別判斷、拷貝、最值、扁平、柯里、遞迴、亂序、排序等,特點是研(chao)究(xi) underscore 和 jQuery 的實現方式。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

相關文章