电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

深入詳解JS函數(shù)的柯里化

瀏覽:87日期:2024-03-23 08:56:24
目錄一、補(bǔ)充知識點(diǎn)之函數(shù)的隱式轉(zhuǎn)換二、補(bǔ)充知識點(diǎn)之利用call/apply封數(shù)組的map方法三、由淺入深的柯里化四、柯里化通用式五、柯里化與bind一、補(bǔ)充知識點(diǎn)之函數(shù)的隱式轉(zhuǎn)換

來一個簡單的思考題。

function fn() { return 20;}console.log(fn + 10); // 輸出結(jié)果是多少?

稍微修改一下,再想想輸出結(jié)果會是什么?

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)算時,隱式轉(zhuǎn)換就可能會發(fā)生。從上面三個例子中我們可以得出一些關(guān)于函數(shù)隱式轉(zhuǎn)換的結(jié)論。

當(dāng)我們沒有重新定義toString與valueOf時,函數(shù)的隱式轉(zhuǎn)換會調(diào)用默認(rèn)的toString方法,它會將函數(shù)的定義內(nèi)容作為字符串返回。而當(dāng)我們主動定義了toString/vauleOf方法時,那么隱式轉(zhuǎn)換的返回結(jié)果則由我們自己控制了。其中valueOf的優(yōu)先級會toString高一點(diǎn)。

因此上面例子的結(jié)論就很容易理解了。建議大家動手嘗試一下。

二、補(bǔ)充知識點(diǎn)之利用call/apply封數(shù)組的map方法

map(): 對數(shù)組中的每一項(xiàng)運(yùn)行給定函數(shù),返回每次函數(shù)調(diào)用的結(jié)果組成的數(shù)組。

通俗來說,就是遍歷數(shù)組的每一項(xiàng)元素,并且在map的第一個參數(shù)(回調(diào)函數(shù))中進(jìn)行運(yùn)算處理后返回計算結(jié)果。返回一個由所有計算結(jié)果組成的新數(shù)組。

// 回調(diào)函數(shù)中有三個參數(shù)// 第一個參數(shù)表示newArr的每一項(xiàng),第二個參數(shù)表示該項(xiàng)在數(shù)組中的索引值// 第三個表示數(shù)組本身// 除此之外,回調(diào)函數(shù)中的this,當(dāng)map不存在第二參數(shù)時,this指向丟失,當(dāng)存在第二個參數(shù)時,指向改參數(shù)所設(shè)定的對象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é)?,F(xiàn)在要面臨一個難題,就是如何封裝map。

可以先想想for循環(huán)。我們可以使用for循環(huán)來實(shí)現(xiàn)一個map,但是在封裝的時候,我們會考慮一些問題。我們在使用for循環(huán)的時候,一個循環(huán)過程確實(shí)很好封裝,但是我們在for循環(huán)里面要對每一項(xiàng)做的事情卻很難用一個固定的東西去把它封裝起來。因?yàn)槊恳粋€場景,for循環(huán)里對數(shù)據(jù)的處理肯定都是不一樣的。

于是大家就想了一個很好的辦法,將這些不一樣的操作單獨(dú)用一個函數(shù)來處理,讓這個函數(shù)成為map方法的第一個參數(shù),具體這個回調(diào)函數(shù)中會是什么樣的操作,則由我們自己在使用時決定。因此,根據(jù)這個思路的封裝實(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)過程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]

在上面的封裝中,我首先定義了一個空的temp數(shù)組,該數(shù)組用來存儲最終的返回結(jié)果。在for循環(huán)中,每循環(huán)一次,就執(zhí)行一次參數(shù)fn函數(shù),fn的參數(shù)則使用call方法傳入。

在理解了map的封裝過程之后,我們就能夠明白為什么我們在使用map時,總是期望能夠在第一個回調(diào)函數(shù)中有一個返回值了。在eslint的規(guī)則中,如果我們在使用map時沒有設(shè)置一個返回值,就會被判定為錯誤。

ok,明白了函數(shù)的隱式轉(zhuǎn)換規(guī)則與call/apply在這種場景的使用方式,我們就可以嘗試通過簡單的例子來了解一下柯里化了。

三、由淺入深的柯里化

在前端面試中有一個關(guān)于柯里化的面試題,流傳甚廣。

實(shí)現(xiàn)一個add方法,使計算結(jié)果能夠滿足如下預(yù)期:

add(1)(2)(3) = 6add(1, 2, 3)(4) = 10add(1)(2)(3)(4)(5) = 15

很明顯,計算結(jié)果正是所有參數(shù)的和,add方法每運(yùn)行一次,肯定返回了一個同樣的函數(shù),繼續(xù)計算剩下的參數(shù)。

我們可以從最簡單的例子一步一步尋找解決方案。

當(dāng)我們只調(diào)用兩次時,可以這樣封裝。

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)類似,但是參數(shù)的使用被限制得很死,因此并不是我們想要的最終結(jié)果,我們需要通用的封裝。應(yīng)該怎么辦?總結(jié)一下上面2個例子,其實(shí)我們是利用閉包的特性,將所有的參數(shù),集中到最后返回的函數(shù)里進(jìn)行計算并返回結(jié)果。因此我們在封裝時,主要的目的,就是將參數(shù)集中起來計算。

來看看具體實(shí)現(xiàn)。

function add() { // 第一次執(zhí)行時,定義一個數(shù)組專門用來存儲所有的參數(shù) var _args = [].slice.call(arguments); // 在內(nèi)部聲明一個函數(shù),利用閉包的特性保存_args并收集所有的參數(shù)值 var adder = function () {var _adder = function() { [].push.apply(_args, [].slice.call(arguments)); return _adder;}; // 利用隱式轉(zhuǎn)換的特性,當(dāng)最后執(zhí)行時隱式轉(zhuǎn)換,并計算最終的值返回_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)); // 15console.log(add(1, 2, 3, 4)(5)); // 15console.log(add(1)(2)(3)(4)(5)); // 15

上面的實(shí)現(xiàn),利用閉包的特性,主要目的是想通過一些巧妙的方法將所有的參數(shù)收集在一個數(shù)組里,并在最終隱式轉(zhuǎn)換時將數(shù)組里的所有項(xiàng)加起來。因此我們在調(diào)用add方法的時候,參數(shù)就顯得非常靈活。當(dāng)然,也就很輕松的滿足了我們的需求。

那么讀懂了上面的demo,然后我們再來看看柯里化的定義,相信大家就會更加容易理解了。

柯里化(英語:Currying),又稱為部分求值,是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回一個新的函數(shù)的技術(shù),新函數(shù)接受余下參數(shù)并返回運(yùn)算結(jié)果。

接收單一參數(shù),因?yàn)橐獢y帶不少信息,因此常常以回調(diào)函數(shù)的理由來解決。 將部分參數(shù)通過回調(diào)函數(shù)等方式傳入函數(shù)中 返回一個新函數(shù),用于處理所有的想要傳入的參數(shù)

在上面的例子中,我們可以將add(1, 2, 3, 4)轉(zhuǎn)換為add(1)(2)(3)(4)。這就是部分求值。每次傳入的參數(shù)都只是我們想要傳入的所有參數(shù)中的一部分。當(dāng)然實(shí)際應(yīng)用中,并不會常常這么復(fù)雜的去處理參數(shù),很多時候也僅僅只是分成兩部分而已。

咱們再來一起思考一個與柯里化相關(guān)的問題。

假如有一個計算要求,需要我們將數(shù)組里面的每一項(xiàng)用我們自己想要的字符給連起來。我們應(yīng)該怎么做?想到使用join方法,就很簡單。

var arr = [1, 2, 3, 4, 5]; // 實(shí)際開發(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)加一個數(shù)后再連起來。那么這里就需要map來幫助我們對每一項(xiàng)進(jìn)行特殊的運(yùn)算處理,生成新的數(shù)組然后用字符連接起來了。實(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)都減去一個數(shù)組之后再連起來呢?當(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)困惑所在了。我們期望封裝一個函數(shù),能同時處理不同的運(yùn)算過程,但是我們并不能使用一個固定的套路將對每一項(xiàng)的操作都封裝起來。于是問題就變成了和封裝map的時候所面臨的問題一樣了。我們可以借助柯里化來搞定。

與map封裝同樣的道理,既然我們事先并不確定我們將要對每一項(xiàng)數(shù)據(jù)進(jìn)行怎么樣的處理,我只是知道我們需要將他們處理之后然后用字符連起來,所以不妨將處理內(nèi)容保存在一個函數(shù)里。而僅僅固定封裝連起來的這一部分需求。

于是我們就有了以下的封裝。

// 封裝很簡單,一句話搞定Array.prototype.merge = function(fn, chars) { return this.map(fn).join(chars);} var arr = [1, 2, 3, 4]; // 難點(diǎn)在于,在實(shí)際使用的時候,操作怎么來定義,利用閉包保存于傳遞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-6console.log(res2); // 0-1-2-3console.log(res3); // 2-4-6-8

大家能從上面的例子,發(fā)現(xiàn)柯里化的特征嗎?

四、柯里化通用式

通用的柯里化寫法其實(shí)比我們上邊封裝的add方法要簡單許多。

var currying = function(fn) { var args = [].slice.call(arguments, 1); return function() {// 主要還是收集所有需要的參數(shù)到一個數(shù)組中,便于統(tǒng)一計算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)); // 40console.log(sum(10, 5)); // 25五、柯里化與bind

Object.prototype.bind = function(context) { var _this = this; var args = [].prototype.slice.call(arguments, 1); return function() {return _this.apply(context, args) }}

這個例子利用call與apply的靈活運(yùn)用,實(shí)現(xiàn)了bind的功能。

在前面的幾個例子中,我們可以總結(jié)一下柯里化的特點(diǎn):

接收單一參數(shù),將更多的參數(shù)通過回調(diào)函數(shù)來搞定? 返回一個新函數(shù),用于處理所有的想要傳入的參數(shù); 需要利用call/apply與arguments對象收集參數(shù); 返回的這個函數(shù)正是用來處理收集起來的參數(shù)。

希望大家讀完之后都能夠大概明白柯里化的概念,如果想要熟練使用它,就需要我們掌握更多的實(shí)際經(jīng)驗(yàn)才行。

以上就是深入詳解JS函數(shù)的柯里化的詳細(xì)內(nèi)容,更多關(guān)于JS函數(shù)的柯里化的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: JavaScript
相關(guān)文章:
主站蜘蛛池模板: 高防护蠕动泵-多通道灌装系统-高防护蠕动泵-www.bjhuiyufluid.com慧宇伟业(北京)流体设备有限公司 | 量子管通环-自清洗过滤器-全自动反冲洗过滤器-沼河浸过滤器 | 次氯酸钠厂家,涉水级次氯酸钠,三氯化铁生产厂家-淄博吉灿化工 | 蓝牙音频分析仪-多功能-四通道-八通道音频分析仪-东莞市奥普新音频技术有限公司 | 体视显微镜_荧光生物显微镜_显微镜报价-微仪光电生命科学显微镜有限公司 | 低噪声电流前置放大器-SR570电流前置放大器-深圳市嘉士达精密仪器有限公司 | 定制异形重型钢格栅板/钢格板_定做踏步板/排水沟盖板_钢格栅板批发厂家-河北圣墨金属制品有限公司 | 六维力传感器_三维力传感器_二维力传感器-南京神源生智能科技有限公司 | 超声波电磁流量计-液位计-孔板流量计-料位计-江苏信仪自动化仪表有限公司 | 新能源汽车电机定转子合装机 - 电机维修设备 - 睿望达 | 烟台螺纹,烟台H型钢,烟台钢材,烟台角钢-烟台市正丰金属材料有限公司 | 隐形纱窗|防护纱窗|金刚网防盗纱窗|韦柏纱窗|上海青木装潢制品有限公司|纱窗国标起草单位 | 学习安徽网 | 工业CT-无锡璟能智能仪器有限公司 | 河南不锈钢水箱_地埋水箱_镀锌板水箱_消防水箱厂家-河南联固供水设备有限公司 | 流量检测仪-气密性检测装置-密封性试验仪-东莞市奥图自动化科技有限公司 | 郑州律师咨询-郑州律师事务所_河南锦盾律师事务所 | 香港新时代国际美容美发化妆美甲培训学校-26年培训经验,值得信赖! | 东莞螺丝|东莞螺丝厂|东莞不锈钢螺丝|东莞组合螺丝|东莞精密螺丝厂家-东莞利浩五金专业紧固件厂家 | 机器视觉检测系统-视觉检测系统-机器视觉系统-ccd检测系统-视觉控制器-视控一体机 -海克易邦 | 雨燕360体育免费直播_雨燕360免费NBA直播_NBA篮球高清直播无插件-雨燕360体育直播 | 结晶点测定仪-润滑脂滴点测定仪-大连煜烁| 磁粉制动器|张力控制器|气胀轴|伺服纠偏控制器整套厂家--台灵机电官网 | 食安观察网| 山东活动策划|济南活动公司|济南公关活动策划-济南锐嘉广告有限公司 | 诺冠气动元件,诺冠电磁阀,海隆防爆阀,norgren气缸-山东锦隆自动化科技有限公司 | 济南铝方通-济南铝方通价格-济南方通厂家-山东鲁方通建材有限公司 | 不锈钢搅拌罐_高速搅拌罐厂家-无锡市凡格德化工装备科技有限公司 | 中红外QCL激光器-其他连续-半导体连续激光器-筱晓光子 | 石油/泥浆/不锈钢防腐/砂泵/抽砂泵/砂砾泵/吸砂泵/压滤机泵 - 专业石油环保专用泵厂家 | 奥因-光触媒除甲醛公司-除甲醛加盟公司十大品牌 | PO膜_灌浆膜及地膜供应厂家 - 青州市鲁谊塑料厂 | 云南成考网_云南成人高考报名网 粤丰硕水性环氧地坪漆-防静电自流平厂家-环保地坪涂料代理 | 压力变送器-上海武锐自动化设备有限公司 | 锥形螺带干燥机(新型耙式干燥机)百科-常州丰能干燥工程 | 防锈油-助焊剂-光学玻璃清洗剂-贝塔防锈油生产厂家 | 小程序开发公司_APP开发多少钱_软件开发定制_微信小程序制作_客户销售管理软件-济南小溪畅流网络科技有限公司 | 澳门精准正版免费大全,2025新澳门全年免费,新澳天天开奖免费资料大全最新,新澳2025今晚开奖资料,新澳马今天最快最新图库-首页-东莞市傲马网络科技有限公司 | 圆窗水平仪|伊莉莎冈特elesa+ganter | 世界箱包品牌十大排名,女包小众轻奢品牌推荐200元左右,男包十大奢侈品牌排行榜双肩,学生拉杆箱什么品牌好质量好 - Gouwu3.com | 百度关键词优化_网站优化_SEO价格 - 云无限好排名 |