這篇文章主要講解了“JS函數(shù)的柯里化怎么實(shí)現(xiàn)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“JS函數(shù)的柯里化怎么實(shí)現(xiàn)”吧!

成都創(chuàng)新互聯(lián)公司擁有十多年成都網(wǎng)站建設(shè)工作經(jīng)驗(yàn),為各大企業(yè)提供成都網(wǎng)站制作、成都做網(wǎng)站服務(wù),對(duì)于網(wǎng)頁(yè)設(shè)計(jì)、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、app軟件開(kāi)發(fā)公司、wap網(wǎng)站建設(shè)(手機(jī)版網(wǎng)站建設(shè))、程序開(kāi)發(fā)、網(wǎng)站優(yōu)化(SEO優(yōu)化)、微網(wǎng)站、域名注冊(cè)等,憑借多年來(lái)在互聯(lián)網(wǎng)的打拼,我們?cè)诨ヂ?lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了很多網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、網(wǎng)絡(luò)營(yíng)銷(xiāo)經(jīng)驗(yàn),集策劃、開(kāi)發(fā)、設(shè)計(jì)、營(yíng)銷(xiāo)、管理等網(wǎng)站化運(yùn)作于一體,具備承接各種規(guī)模類(lèi)型的網(wǎng)站建設(shè)項(xiàng)目的能力。
來(lái)一個(gè)簡(jiǎn)單的思考題。
function fn() {
return 20;
}
console.log(fn + 10); // 輸出結(jié)果是多少?稍微修改一下,再想想輸出結(jié)果會(huì)是什么?
function fn() {
return 20;
}
fn.toString = function() {
return 10;
}
console.log(fn + 10); // 輸出結(jié)果是多少?還可以繼續(xù)修改一下。
function fn() {
return 20;
}
fn.toString = function() {
return 10;
}
fn.valueOf = function() {
return 5;
}
console.log(fn + 10); // 輸出結(jié)果是多少?// 輸出結(jié)果分別為
function fn() {
return 20;
}
10
20
15當(dāng)使用console.log,或者進(jìn)行運(yùn)算時(shí),隱式轉(zhuǎn)換就可能會(huì)發(fā)生。從上面三個(gè)例子中我們可以得出一些關(guān)于函數(shù)隱式轉(zhuǎn)換的結(jié)論。
當(dāng)我們沒(méi)有重新定義toString與valueOf時(shí),函數(shù)的隱式轉(zhuǎn)換會(huì)調(diào)用默認(rèn)的toString方法,它會(huì)將函數(shù)的定義內(nèi)容作為字符串返回。而當(dāng)我們主動(dòng)定義了toString/vauleOf方法時(shí),那么隱式轉(zhuǎn)換的返回結(jié)果則由我們自己控制了。其中valueOf的優(yōu)先級(jí)會(huì)toString高一點(diǎn)。
因此上面例子的結(jié)論就很容易理解了。建議大家動(dòng)手嘗試一下。
map(): 對(duì)數(shù)組中的每一項(xiàng)運(yùn)行給定函數(shù),返回每次函數(shù)調(diào)用的結(jié)果組成的數(shù)組。
通俗來(lái)說(shuō),就是遍歷數(shù)組的每一項(xiàng)元素,并且在map的第一個(gè)參數(shù)(回調(diào)函數(shù))中進(jìn)行運(yùn)算處理后返回計(jì)算結(jié)果。返回一個(gè)由所有計(jì)算結(jié)果組成的新數(shù)組。
// 回調(diào)函數(shù)中有三個(gè)參數(shù)
// 第一個(gè)參數(shù)表示newArr的每一項(xiàng),第二個(gè)參數(shù)表示該項(xiàng)在數(shù)組中的索引值
// 第三個(gè)表示數(shù)組本身
// 除此之外,回調(diào)函數(shù)中的this,當(dāng)map不存在第二參數(shù)時(shí),this指向丟失,當(dāng)存在第二個(gè)參數(shù)時(shí),指向改參數(shù)所設(shè)定的對(duì)象
var newArr = [1, 2, 3, 4].map(function(item, i, arr) {
console.log(item, i, arr, this); // 可運(yùn)行試試看
return item + 1; // 每一項(xiàng)加1
}, { a: 1 })
console.log(newArr); // [2, 3, 4, 5]在上面例子的注釋中詳細(xì)闡述了map方法的細(xì)節(jié)。現(xiàn)在要面臨一個(gè)難題,就是如何封裝map。
可以先想想for循環(huán)。我們可以使用for循環(huán)來(lái)實(shí)現(xiàn)一個(gè)map,但是在封裝的時(shí)候,我們會(huì)考慮一些問(wèn)題。我們?cè)谑褂胒or循環(huán)的時(shí)候,一個(gè)循環(huán)過(guò)程確實(shí)很好封裝,但是我們?cè)趂or循環(huán)里面要對(duì)每一項(xiàng)做的事情卻很難用一個(gè)固定的東西去把它封裝起來(lái)。因?yàn)槊恳粋€(gè)場(chǎng)景,for循環(huán)里對(duì)數(shù)據(jù)的處理肯定都是不一樣的。
于是大家就想了一個(gè)很好的辦法,將這些不一樣的操作單獨(dú)用一個(gè)函數(shù)來(lái)處理,讓這個(gè)函數(shù)成為map方法的第一個(gè)參數(shù),具體這個(gè)回調(diào)函數(shù)中會(huì)是什么樣的操作,則由我們自己在使用時(shí)決定。因此,根據(jù)這個(gè)思路的封裝實(shí)現(xiàn)如下。
Array.prototype._map = function(fn, context) {
var temp = [];
if(typeof fn == 'function') {
var k = 0;
var len = this.length;
// 封裝for循環(huán)過(guò)程
for(; k < len; k++) {
// 將每一項(xiàng)的運(yùn)算操作丟進(jìn)fn里,利用call方法指定fn的this指向與具體參數(shù)
temp.push(fn.call(context, this[k], k, this))
}
} else {
console.error('TypeError: '+ fn +' is not a function.');
}
// 返回每一項(xiàng)運(yùn)算結(jié)果組成的新數(shù)組
return temp;
}
var newArr = [1, 2, 3, 4]._map(function(item) {
return item + 1;
})
// [2, 3, 4, 5]在上面的封裝中,我首先定義了一個(gè)空的temp數(shù)組,該數(shù)組用來(lái)存儲(chǔ)最終的返回結(jié)果。在for循環(huán)中,每循環(huán)一次,就執(zhí)行一次參數(shù)fn函數(shù),fn的參數(shù)則使用call方法傳入。
在理解了map的封裝過(guò)程之后,我們就能夠明白為什么我們?cè)谑褂胢ap時(shí),總是期望能夠在第一個(gè)回調(diào)函數(shù)中有一個(gè)返回值了。在eslint的規(guī)則中,如果我們?cè)谑褂胢ap時(shí)沒(méi)有設(shè)置一個(gè)返回值,就會(huì)被判定為錯(cuò)誤。
ok,明白了函數(shù)的隱式轉(zhuǎn)換規(guī)則與call/apply在這種場(chǎng)景的使用方式,我們就可以嘗試通過(guò)簡(jiǎn)單的例子來(lái)了解一下柯里化了。
在前端面試中有一個(gè)關(guān)于柯里化的面試題,流傳甚廣。
實(shí)現(xiàn)一個(gè)add方法,使計(jì)算結(jié)果能夠滿足如下預(yù)期:
add(1)(2)(3) = 6
add(1, 2, 3)(4) = 10
add(1)(2)(3)(4)(5) = 15
很明顯,計(jì)算結(jié)果正是所有參數(shù)的和,add方法每運(yùn)行一次,肯定返回了一個(gè)同樣的函數(shù),繼續(xù)計(jì)算剩下的參數(shù)。
我們可以從最簡(jiǎn)單的例子一步一步尋找解決方案。
當(dāng)我們只調(diào)用兩次時(shí),可以這樣封裝。
function add(a) {
return function(b) {
return a + b;
}
}
console.log(add(1)(2)); // 3如果只調(diào)用三次:
function add(a) {
return function(b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6上面的封裝看上去跟我們想要的結(jié)果有點(diǎn)類(lèi)似,但是參數(shù)的使用被限制得很死,因此并不是我們想要的最終結(jié)果,我們需要通用的封裝。應(yīng)該怎么辦?總結(jié)一下上面2個(gè)例子,其實(shí)我們是利用閉包的特性,將所有的參數(shù),集中到最后返回的函數(shù)里進(jìn)行計(jì)算并返回結(jié)果。因此我們?cè)诜庋b時(shí),主要的目的,就是將參數(shù)集中起來(lái)計(jì)算。
來(lái)看看具體實(shí)現(xiàn)。
function add() {
// 第一次執(zhí)行時(shí),定義一個(gè)數(shù)組專(zhuān)門(mén)用來(lái)存儲(chǔ)所有的參數(shù)
var _args = [].slice.call(arguments);
// 在內(nèi)部聲明一個(gè)函數(shù),利用閉包的特性保存_args并收集所有的參數(shù)值
var adder = function () {
var _adder = function() {
[].push.apply(_args, [].slice.call(arguments));
return _adder;
};
// 利用隱式轉(zhuǎn)換的特性,當(dāng)最后執(zhí)行時(shí)隱式轉(zhuǎn)換,并計(jì)算最終的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
return adder.apply(null, [].slice.call(arguments));
}
// 輸出結(jié)果,可自由組合的參數(shù)
console.log(add(1, 2, 3, 4, 5)); // 15
console.log(add(1, 2, 3, 4)(5)); // 15
console.log(add(1)(2)(3)(4)(5)); // 15上面的實(shí)現(xiàn),利用閉包的特性,主要目的是想通過(guò)一些巧妙的方法將所有的參數(shù)收集在一個(gè)數(shù)組里,并在最終隱式轉(zhuǎn)換時(shí)將數(shù)組里的所有項(xiàng)加起來(lái)。因此我們?cè)谡{(diào)用add方法的時(shí)候,參數(shù)就顯得非常靈活。當(dāng)然,也就很輕松的滿足了我們的需求。
那么讀懂了上面的demo,然后我們?cè)賮?lái)看看柯里化的定義,相信大家就會(huì)更加容易理解了。
柯里化(英語(yǔ):Currying),又稱(chēng)為部分求值,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回一個(gè)新的函數(shù)的技術(shù),新函數(shù)接受余下參數(shù)并返回運(yùn)算結(jié)果。
接收單一參數(shù),因?yàn)橐獢y帶不少信息,因此常常以回調(diào)函數(shù)的理由來(lái)解決。
將部分參數(shù)通過(guò)回調(diào)函數(shù)等方式傳入函數(shù)中
返回一個(gè)新函數(shù),用于處理所有的想要傳入的參數(shù)
在上面的例子中,我們可以將add(1, 2, 3, 4)轉(zhuǎn)換為add(1)(2)(3)(4)。這就是部分求值。每次傳入的參數(shù)都只是我們想要傳入的所有參數(shù)中的一部分。當(dāng)然實(shí)際應(yīng)用中,并不會(huì)常常這么復(fù)雜的去處理參數(shù),很多時(shí)候也僅僅只是分成兩部分而已。
咱們?cè)賮?lái)一起思考一個(gè)與柯里化相關(guān)的問(wèn)題。
假如有一個(gè)計(jì)算要求,需要我們將數(shù)組里面的每一項(xiàng)用我們自己想要的字符給連起來(lái)。我們應(yīng)該怎么做?想到使用join方法,就很簡(jiǎn)單。
var arr = [1, 2, 3, 4, 5];
// 實(shí)際開(kāi)發(fā)中并不建議直接給Array擴(kuò)展新的方法
// 只是用這種方式演示能夠更加清晰一點(diǎn)
Array.prototype.merge = function(chars) {
return this.join(chars);
}
var string = arr.merge('-')
console.log(string); // 1-2-3-4-5增加難度,將每一項(xiàng)加一個(gè)數(shù)后再連起來(lái)。那么這里就需要map來(lái)幫助我們對(duì)每一項(xiàng)進(jìn)行特殊的運(yùn)算處理,生成新的數(shù)組然后用字符連接起來(lái)了。實(shí)現(xiàn)如下:
var arr = [1, 2, 3, 4, 5];
Array.prototype.merge = function(chars, number) {
return this.map(function(item) {
return item + number;
}).join(chars);
}
var string = arr.merge('-', 1);
console.log(string); // 2-3-4-5-6但是如果我們又想要讓數(shù)組每一項(xiàng)都減去一個(gè)數(shù)組之后再連起來(lái)呢?當(dāng)然和上面的加法操作一樣的實(shí)現(xiàn)。
var arr = [1, 2, 3, 4, 5];
Array.prototype.merge = function(chars, number) {
return this.map(function(item) {
return item - number;
}).join(chars);
}
var string = arr.merge('~', 1);
console.log(string); // 0~1~2~3~4機(jī)智的小伙伴肯定發(fā)現(xiàn)困惑所在了。我們期望封裝一個(gè)函數(shù),能同時(shí)處理不同的運(yùn)算過(guò)程,但是我們并不能使用一個(gè)固定的套路將對(duì)每一項(xiàng)的操作都封裝起來(lái)。于是問(wèn)題就變成了和封裝map的時(shí)候所面臨的問(wèn)題一樣了。我們可以借助柯里化來(lái)搞定。
與map封裝同樣的道理,既然我們事先并不確定我們將要對(duì)每一項(xiàng)數(shù)據(jù)進(jìn)行怎么樣的處理,我只是知道我們需要將他們處理之后然后用字符連起來(lái),所以不妨將處理內(nèi)容保存在一個(gè)函數(shù)里。而僅僅固定封裝連起來(lái)的這一部分需求。
于是我們就有了以下的封裝。
// 封裝很簡(jiǎn)單,一句話搞定
Array.prototype.merge = function(fn, chars) {
return this.map(fn).join(chars);
}
var arr = [1, 2, 3, 4];
// 難點(diǎn)在于,在實(shí)際使用的時(shí)候,操作怎么來(lái)定義,利用閉包保存于傳遞num參數(shù)
var add = function(num) {
return function(item) {
return item + num;
}
}
var red = function(num) {
return function(item) {
return item - num;
}
}
// 每一項(xiàng)加2后合并
var res1 = arr.merge(add(2), '-');
// 每一項(xiàng)減2后合并
var res2 = arr.merge(red(1), '-');
// 也可以直接使用回調(diào)函數(shù),每一項(xiàng)乘2后合并
var res3 = arr.merge((function(num) {
return function(item) {
return item * num
}
})(2), '-')
console.log(res1); // 3-4-5-6
console.log(res2); // 0-1-2-3
console.log(res3); // 2-4-6-8大家能從上面的例子,發(fā)現(xiàn)柯里化的特征嗎?
通用的柯里化寫(xiě)法其實(shí)比我們上邊封裝的add方法要簡(jiǎn)單許多。
var currying = function(fn) {
var args = [].slice.call(arguments, 1);
return function() {
// 主要還是收集所有需要的參數(shù)到一個(gè)數(shù)組中,便于統(tǒng)一計(jì)算
var _args = args.concat([].slice.call(arguments));
return fn.apply(null, _args);
}
}
var sum = currying(function() {
var args = [].slice.call(arguments);
return args.reduce(function(a, b) {
return a + b;
})
}, 10)
console.log(sum(20, 10)); // 40
console.log(sum(10, 5)); // 25Object.prototype.bind = function(context) {
var _this = this;
var args = [].prototype.slice.call(arguments, 1);
return function() {
return _this.apply(context, args)
}
}這個(gè)例子利用call與apply的靈活運(yùn)用,實(shí)現(xiàn)了bind的功能。
在前面的幾個(gè)例子中,我們可以總結(jié)一下柯里化的特點(diǎn):
接收單一參數(shù),將更多的參數(shù)通過(guò)回調(diào)函數(shù)來(lái)搞定?
返回一個(gè)新函數(shù),用于處理所有的想要傳入的參數(shù);
需要利用call/apply與arguments對(duì)象收集參數(shù);
返回的這個(gè)函數(shù)正是用來(lái)處理收集起來(lái)的參數(shù)。
感謝各位的閱讀,以上就是“JS函數(shù)的柯里化怎么實(shí)現(xiàn)”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)JS函數(shù)的柯里化怎么實(shí)現(xiàn)這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
新聞標(biāo)題:JS函數(shù)的柯里化怎么實(shí)現(xiàn)
網(wǎng)頁(yè)地址:http://chinadenli.net/article48/gejiep.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開(kāi)發(fā)、外貿(mào)網(wǎng)站建設(shè)、靜態(tài)網(wǎng)站、Google、軟件開(kāi)發(fā)、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)