成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript專題之函數(shù)組合

周國輝 / 1433人閱讀

摘要:專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。這便是函數(shù)組合。

JavaScript 專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn) pointfree 模式

需求

我們需要寫一個(gè)函數(shù),輸入 "kevin",返回 "HELLO, KEVIN"。

嘗試
var toUpperCase = function(x) { return x.toUpperCase(); };
var hello = function(x) { return "HELLO, " + x; };

var greet = function(x){
    return hello(toUpperCase(x));
};

greet("kevin");

還好我們只有兩個(gè)步驟,首先小寫轉(zhuǎn)大寫,然后拼接字符串。如果有更多的操作,greet 函數(shù)里就需要更多的嵌套,類似于 fn3(fn2(fn1(fn0(x))))

優(yōu)化

試想我們寫個(gè) compose 函數(shù):

var compose = function(f,g) {
    return function(x) {
        return f(g(x));
    };
};

greet 函數(shù)就可以被優(yōu)化為:

var greet = compose(hello, toUpperCase);
greet("kevin");

利用 compose 將兩個(gè)函數(shù)組合成一個(gè)函數(shù),讓代碼從右向左運(yùn)行,而不是由內(nèi)而外運(yùn)行,可讀性大大提升。這便是函數(shù)組合。

但是現(xiàn)在的 compose 函數(shù)也只是能支持兩個(gè)參數(shù),如果有更多的步驟呢?我們豈不是要這樣做:

compose(d, compose(c, compose(b, a)))

為什么我們不寫一個(gè)帥氣的 compose 函數(shù)支持傳入多個(gè)函數(shù)呢?這樣就變成了:

compose(d, c, b, a)
compose

我們直接抄襲 underscore 的 compose 函數(shù)的實(shí)現(xiàn):

function compose() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
        var i = start;
        var result = args[start].apply(this, arguments);
        while (i--) result = args[i].call(this, result);
        return result;
    };
};

現(xiàn)在的 compose 函數(shù)已經(jīng)可以支持多個(gè)函數(shù)了,然而有了這個(gè)又有什么用呢?

在此之前,我們先了解一個(gè)概念叫做 pointfree。

pointfree

pointfree 指的是函數(shù)無須提及將要操作的數(shù)據(jù)是什么樣的。依然是以最初的需求為例:

// 需求:輸入 "kevin",返回 "HELLO, KEVIN"。

// 非 pointfree,因?yàn)樘岬搅藬?shù)據(jù):name
var greet = function(name) {
    return ("hello " + name).toUpperCase();
}

// pointfree
// 先定義基本運(yùn)算,這些可以封裝起來復(fù)用
var toUpperCase = function(x) { return x.toUpperCase(); };
var hello = function(x) { return "HELLO, " + x; };

var greet = compose(hello, toUpperCase);
greet("kevin");

我們再舉個(gè)稍微復(fù)雜一點(diǎn)的例子,為了方便書寫,我們需要借助在《JavaScript專題之函數(shù)柯里化》中寫到的 curry 函數(shù):

// 需求:輸入 "kevin daisy kelly",返回 "K.D.K"

// 非 pointfree,因?yàn)樘岬搅藬?shù)據(jù):name
var initials = function (name) {
    return name.split(" ").map(compose(toUpperCase, head)).join(". ");
};

// pointfree
// 先定義基本運(yùn)算
var split = curry(function(separator, str) { return str.split(separator) })
var head = function(str) { return str.slice(0, 1) }
var toUpperCase = function(str) { return str.toUpperCase() }
var join = curry(function(separator, arr) { return arr.join(separator) })
var map = curry(function(fn, arr) { return arr.map(fn) })

var initials = compose(join("."), map(compose(toUpperCase, head)), split(" "));

initials("kevin daisy kelly");

從這個(gè)例子中我們可以看到,利用柯里化(curry)和函數(shù)組合 (compose) 非常有助于實(shí)現(xiàn) pointfree。

也許你會(huì)想,這種寫法好麻煩吶,我們還需要定義那么多的基礎(chǔ)函數(shù)……可是如果有工具庫已經(jīng)幫你寫好了呢?比如 ramda.js:

// 使用 ramda.js
var initials = R.compose(R.join("."), R.map(R.compose(R.toUpper, R.head)), R.split(" "));

而且你也會(huì)發(fā)現(xiàn):

Pointfree 的本質(zhì)就是使用一些通用的函數(shù),組合出各種復(fù)雜運(yùn)算。上層運(yùn)算不要直接操作數(shù)據(jù),而是通過底層函數(shù)去處理。即不使用所要處理的值,只合成運(yùn)算過程。

那么使用 pointfree 模式究竟有什么好處呢?

pointfree 模式能夠幫助我們減少不必要的命名,讓代碼保持簡潔和通用,更符合語義,更容易復(fù)用,測試也變得輕而易舉。

實(shí)戰(zhàn)

這個(gè)例子來自于 Favoring Curry:

假設(shè)我們從服務(wù)器獲取這樣的數(shù)據(jù):

var data = {
    result: "SUCCESS",
    tasks: [
        {id: 104, complete: false,            priority: "high",
                  dueDate: "2013-11-29",      username: "Scott",
                  title: "Do something",      created: "9/22/2013"},
        {id: 105, complete: false,            priority: "medium",
                  dueDate: "2013-11-22",      username: "Lena",
                  title: "Do something else", created: "9/22/2013"},
        {id: 107, complete: true,             priority: "high",
                  dueDate: "2013-11-22",      username: "Mike",
                  title: "Fix the foo",       created: "9/22/2013"},
        {id: 108, complete: false,            priority: "low",
                  dueDate: "2013-11-15",      username: "Punam",
                  title: "Adjust the bar",    created: "9/25/2013"},
        {id: 110, complete: false,            priority: "medium",
                  dueDate: "2013-11-15",      username: "Scott",
                  title: "Rename everything", created: "10/2/2013"},
        {id: 112, complete: true,             priority: "high",
                  dueDate: "2013-11-27",      username: "Lena",
                  title: "Alter all quuxes",  created: "10/5/2013"}
    ]
};

我們需要寫一個(gè)名為 getIncompleteTaskSummaries 的函數(shù),接收一個(gè) username 作為參數(shù),從服務(wù)器獲取數(shù)據(jù),然后篩選出這個(gè)用戶的未完成的任務(wù)的 ids、priorities、titles、和 dueDate 數(shù)據(jù),并且按照日期升序排序。

以 Scott 為例,最終篩選出的數(shù)據(jù)為:

[
    {id: 110, title: "Rename everything", 
        dueDate: "2013-11-15", priority: "medium"},
    {id: 104, title: "Do something", 
        dueDate: "2013-11-29", priority: "high"}
]

普通的方式為:

// 第一版 過程式編程
var fetchData = function() {
    // 模擬
    return Promise.resolve(data)
};

var getIncompleteTaskSummaries = function(membername) {
     return fetchData()
         .then(function(data) {
             return data.tasks;
         })
         .then(function(tasks) {
             return tasks.filter(function(task) {
                 return task.username == membername
             })
         })
         .then(function(tasks) {
             return tasks.filter(function(task) {
                 return !task.complete
             })
         })
         .then(function(tasks) {
             return tasks.map(function(task) {
                 return {
                     id: task.id,
                     dueDate: task.dueDate,
                     title: task.title,
                     priority: task.priority
                 }
             })
         })
         .then(function(tasks) {
             return tasks.sort(function(first, second) {
                 var a = first.dueDate,
                     b = second.dueDate;
                 return a < b ? -1 : a > b ? 1 : 0;
             });
         })
         .then(function(task) {
             console.log(task)
         })
};

getIncompleteTaskSummaries("Scott")

如果使用 pointfree 模式:

// 第二版 pointfree 改寫
var fetchData = function() {
    return Promise.resolve(data)
};

// 編寫基本函數(shù)
var prop = curry(function(name, obj) {
    return obj[name];
});

var propEq = curry(function(name, val, obj) {
    return obj[name] === val;
});

var filter = curry(function(fn, arr) {
    return arr.filter(fn)
});

var map = curry(function(fn, arr) {
    return arr.map(fn)
});

var pick = curry(function(args, obj){
    var result = {};
    for (var i = 0; i < args.length; i++) {
        result[args[i]] = obj[args[i]]
    }
    return result;
});

var sortBy = curry(function(fn, arr) {
    return arr.sort(function(a, b){
        var a = fn(a),
            b = fn(b);
        return a < b ? -1 : a > b ? 1 : 0;
    })
});

var getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(prop("tasks"))
        .then(filter(propEq("username", membername)))
        .then(filter(propEq("complete", false)))
        .then(map(pick(["id", "dueDate", "title", "priority"])))
        .then(sortBy(prop("dueDate")))
        .then(console.log)
};

getIncompleteTaskSummaries("Scott")

如果直接使用 ramda.js,你可以省去編寫基本函數(shù):

// 第三版 使用 ramda.js
var fetchData = function() {
    return Promise.resolve(data)
};

var getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(R.prop("tasks"))
        .then(R.filter(R.propEq("username", membername)))
        .then(R.filter(R.propEq("complete", false)))
        .then(R.map(R.pick(["id", "dueDate", "title", "priority"])))
        .then(R.sortBy(R.prop("dueDate")))
        .then(console.log)
};

getIncompleteTaskSummaries("Scott")

當(dāng)然了,利用 compose,你也可以這樣寫:

// 第四版 使用 compose
var fetchData = function() {
    return Promise.resolve(data)
};

var getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(R.compose(
            console.log,
            R.sortBy(R.prop("dueDate")),
            R.map(R.pick(["id", "dueDate", "title", "priority"])
            ),
            R.filter(R.propEq("complete", false)),
            R.filter(R.propEq("username", membername)),
            R.prop("tasks"),
        ))
};

getIncompleteTaskSummaries("Scott")

compose 是從右到左依此執(zhí)行,當(dāng)然你也可以寫一個(gè)從左到右的版本,但是從右向左執(zhí)行更加能夠反映數(shù)學(xué)上的含義。

ramda.js 提供了一個(gè) R.pipe 函數(shù),可以做的從左到右,以上可以改寫為:

// 第五版 使用 R.pipe
var getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(R.pipe(
            ),
            R.prop("tasks"),
            R.filter(R.propEq("username", membername)),
            R.filter(R.propEq("complete", false)),
            R.map(R.pick(["id", "dueDate", "title", "priority"])
            R.sortBy(R.prop("dueDate")),
            console.log,
        ))
};
專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。

JavaScript專題系列預(yù)計(jì)寫二十篇左右,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,請?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵(lì)。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85144.html

相關(guān)文章

  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評論0 收藏0
  • JavaScript專題系列20篇正式完結(jié)!

    摘要:寫在前面專題系列是我寫的第二個(gè)系列,第一個(gè)系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫在前面 JavaScript 專題系列是我寫的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專題系列共計(jì) 20 篇,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評論0 收藏0
  • JS專題繼承

    摘要:構(gòu)造函數(shù)所以,就有了畸形的繼承方式原型鏈繼承三原型鏈繼承改變構(gòu)造函數(shù)的原型對象繼承了屬性以上例子中,暴露出原型鏈繼承的兩個(gè)問題包含引用類型數(shù)據(jù)的原型屬性,會(huì)被所有實(shí)例共享,基本數(shù)據(jù)類型則不會(huì)。 前言 眾所周知,JavaScript 中,沒有 JAVA 等主流語言類的概念,更沒有父子類繼承的概念,而是通過原型對象和原型鏈的方式實(shí)現(xiàn)繼承。 于是,我們這一篇講一講 JS 中的繼承(委托)。 ...

    rollback 評論0 收藏0
  • 前端_JavaScript

    摘要:為此決定自研一個(gè)富文本編輯器。例如當(dāng)要轉(zhuǎn)化的對象有環(huán)存在時(shí)子節(jié)點(diǎn)屬性賦值了父節(jié)點(diǎn)的引用,為了關(guān)于函數(shù)式編程的思考作者李英杰,美團(tuán)金融前端團(tuán)隊(duì)成員。只有正確使用作用域,才能使用優(yōu)秀的設(shè)計(jì)模式,幫助你規(guī)避副作用。 JavaScript 專題之惰性函數(shù) JavaScript 專題系列第十五篇,講解惰性函數(shù) 需求 我們現(xiàn)在需要寫一個(gè) foo 函數(shù),這個(gè)函數(shù)返回首次調(diào)用時(shí)的 Date 對象,注意...

    Benedict Evans 評論0 收藏0
  • 深入理解JavaScript

    摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...

    myeveryheart 評論0 收藏0

發(fā)表評論

0條評論

周國輝

|高級講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<