最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

從柯里化分析JavaScript重要的高階函數(shù)實(shí)例

 更新時間:2022年10月15日 09:35:05   作者:掘金安東尼  
這篇文章主要為大家介紹了從柯里化分析JavaScript重要的高階函數(shù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前情回顧

我們在前篇 《?從歷史講起,JavaScript 基因里寫著函數(shù)式編程》 講到了 JavaScript 的函數(shù)式基因最早可追溯到 1930 年的 lambda 運(yùn)算,這個時間比第一臺計(jì)算機(jī)誕生的時間都還要早十幾年。JavaScript 閉包的概念也來源于 lambda 運(yùn)算中變量的被綁定關(guān)系。

因?yàn)樵?lambda 演算的設(shè)定中,參數(shù)只能是一個,所以通過柯里化的天才想法來實(shí)現(xiàn)接收多個參數(shù):

lambda x. ( lambda y. plus x y )

說這個想法是“天才”一點(diǎn)不為過,把函數(shù)自身作為輸入?yún)?shù)或輸出返回值,至今受用,也就是【高階函數(shù)】的定義。

將上述 lambda 演算柯里化寫法轉(zhuǎn)變到 JavaScript 中,就變成了:

function add(a) {
    return function (b) {
        return a + b
    }
}
add(1)(2)

所以,剖析閉包從柯里化開始,柯里化是閉包的“孿生子”。

讀完本篇,你會發(fā)現(xiàn) JavaScript 高階函數(shù)中處處是閉包、處處是柯里化~

百變柯里化

最開始,本瓜理解 柯里化 == 閉包 + 遞歸,得出的柯里化寫法是這樣的:

 let arr = []
 function addCurry() {
     let arg = Array.prototype.slice.call(arguments); // 遞歸獲取后續(xù)參數(shù)
     arr = arr.concat(arg);
      if (arg.length === 0) { // 如果參數(shù)為空,則判斷遞歸結(jié)束
          return arr.reduce((a,b)=>{return a+b}) // 求和
      } else {
          return addCurry;
      }
  }
addCurry(1)(2)(3)()

但這樣的寫法, addCurry 函數(shù)會引用一個外部變量 arr,不符合純函數(shù)的特性,于是就優(yōu)化為:

function addCurry() {
    let arr = [...arguments]
    let fn = function () {
        if(arguments.length === 0) {
	    return arr.reduce((a, b) => a + b)
        } else {
            arr.push(...arguments)
            return fn
        }
    }
    return fn
}

上述寫法,又總是要以 ‘( )’ 空括號結(jié)尾,于是再改進(jìn)為隱式轉(zhuǎn)換 .toString 寫法:

function addCurry() {
    let arr = [...arguments]
    // 利用閉包的特性收集所有參數(shù)值
    var fn = function() {
        arr.push(...arguments);
        return fn;
    };
    // 利用 toString 隱式轉(zhuǎn)換
    fn.toString = function () {
        return arr.reduce(function (a, b) {
            return a + b;
        });
    }
    return fn;
}
  • 注意一些舊版本的瀏覽器隱式轉(zhuǎn)換會默認(rèn)執(zhí)行

好了,到這一步,如果你把上述三種柯里化寫法都會手寫了,那面試中考柯里化的基礎(chǔ)一關(guān)算是過了。

然而,不止于此,柯里化實(shí)際存在很多變體, 只有深刻吃透它的思想,而非停留在一種寫法上,才能算得上“高級”、“優(yōu)雅”。

接下來,讓我們看看它怎么變?!

緩存?zhèn)鲄?/h3>

柯里化最基礎(chǔ)的用法是緩存?zhèn)鲄ⅰ?/p>

我們經(jīng)常遇到這樣的場景:

已知一個 ajax 函數(shù),它有 3 個參數(shù) url、data、callback

function ajax(url, data, callback) {
  // ...
}

不用柯里化是怎樣減少傳參的呢?通常是以下這樣,寫死參數(shù)位置的方式來減少傳參:

function ajaxTest1(data, callback) {
  ajax('http://www.test.com/test1', data, callback);
}

而通過柯里化,則是這樣:

function ajax(url, data, callback) {
  // ...
}
let ajaxTest2 = partial(ajax,'http://www.test.com/test2')
ajaxTest2(data,callback)

其中 partial 函數(shù)是這樣寫的:

function partial(fn, ...presetArgs) { // presetArgs 是需要先被綁定下來的參數(shù)
  return function partiallyApplied(...laterArgs) { //  ...laterArgs 是后續(xù)參數(shù)
        let allArgs =presetArgs.concat(laterArgs) // 收集到一起
        return fn.apply(this, allArgs) // 傳給回調(diào)函數(shù) fn
  }
}

柯里化固定參數(shù)的好處在:復(fù)用了原本的 ajax 函數(shù),并在原有基礎(chǔ)上做了修改,取其精華,棄其糟粕,封裝原有函數(shù)之后,就能為我所用。

并且 partial 函數(shù)不止對 ajax 函數(shù)有作用,對于其它想減少傳參的函數(shù)同樣適用。

緩存判斷

我們可以設(shè)想一個通用場景,假設(shè)有一個 handleOption 函數(shù),當(dāng)符合條件 'A',執(zhí)行語句:console.log('A');不符合時,則執(zhí)行語句:console.log('others')

轉(zhuǎn)為代碼即:

const handleOption = (param) =>{
     if(param === 'A'){
         console.log('A')
     }else{
         console.log('others')
     }
}

現(xiàn)在的問題是:我們每次調(diào)用 handleOption('A'),都必須要走完 if...else... 的判斷流程。比如:

const handleOption = (param) =>{
     console.log('每次調(diào)用 handleOption 都要執(zhí)行 if...else...')
     if(param === 'A'){
         console.log('A')
     }else{
         console.log('others')
     }
}
handleOption('A')
handleOption('A')
handleOption('A')

控制臺打印:

有沒有什么辦法,多次調(diào)用 handleOption('A'),卻只走一次 if...else...?

答案是:柯里化。

const handleOption = ((param) =>{
     console.log('從始至終只用執(zhí)行一次 if...else...')
     if(param === 'A'){
         return ()=>console.log('A')
     }else{
         return ()=>console.log('others')
     }
})
const tmp = handleOption('A')
tmp()
tmp()
tmp()

控制臺打?。?/p>

這樣的場景是有實(shí)戰(zhàn)意義的,當(dāng)我們做前端兼容時,經(jīng)常要先判斷是來源于哪個環(huán)境,再執(zhí)行某個方法。比如說在 firefox 和 chrome 環(huán)境下,添加事件監(jiān)聽是 addEventListener 方法,而在 IE 下,添加事件是 attachEvent 方法;如果每次綁定這個監(jiān)聽,都要判斷是來自于哪個環(huán)境,那肯定是很費(fèi)勁。我們通過上述封裝的方法,可以做到 一處判斷,多次使用。

肯定有小伙伴會問了:這也是柯里化?

嗯。。。怎么不算呢?

把 'A' 條件先固定下來,也可叫“緩存下來”,后續(xù)的函數(shù)執(zhí)行將不再傳 'A' 這個參數(shù),實(shí)打?qū)嵉模喊讯鄥?shù)轉(zhuǎn)化為單參數(shù),逐個傳遞。

緩存計(jì)算

我們再設(shè)想這樣一個場景,現(xiàn)在有一個函數(shù)是來做大數(shù)計(jì)算的:

const calculateFn = (num)=>{
    const startTime = new Date()
    for(let i=0;i<num;i++){} // 大數(shù)計(jì)算
    const endTime = new Date()
    console.log(endTime - startTime)
    return "Calculate big numbers"
}
calculateFn(10_000_000_000)

這是一個非常耗時的函數(shù),在控制臺看看,需要 8s+

如果業(yè)務(wù)代碼中需要多次用到這個大數(shù)計(jì)算結(jié)果,多次調(diào)用 calculateFn(10_000_000_000) 肯定是不明智的,太費(fèi)時。

一般的做法就是聲明一個全局變量,把運(yùn)算結(jié)果保存下來:

比如 const resNums = calculateFn(10_000_000_000)

如果有多個大數(shù)運(yùn)算呢?沿著這個思路,即聲名多個變量:

const resNumsA = calculateFn(10_000_000_000)
const resNumsB = calculateFn(20_000_000_000)
const resNumsC = calculateFn(30_000_000_000)

我們講就是說:奧卡姆剃刀原則 —— 如無必要、勿增實(shí)體。

申明這么多全局變量,先不談?wù)純?nèi)存、占命名空間這事,就把 calculateFn() 函數(shù)的參數(shù)和聲名的常量名一一對應(yīng),都是一個麻煩事。

有沒有什么辦法?只用函數(shù),不增加多個全局常量,就實(shí)現(xiàn)多次調(diào)用,只計(jì)算一次?

答案是:柯里化。

代碼如下:

function cached(fn){
  const cacheObj = Object.create(null); // 創(chuàng)建一個對象
  return function cachedFn (str) { // 返回回調(diào)函數(shù)
    if ( !cacheObj [str] ) { // 在對象里面查詢,函數(shù)結(jié)果是否被計(jì)算過
        let result = fn(str);
        cacheObj [str] = result; // 沒有則要執(zhí)行原函數(shù),并把計(jì)算結(jié)果緩存起來
    }
    return cacheObj [str] // 被緩存過,直接返回
  }
}
const calculateFn = (num)=>{
    console.log("計(jì)算即緩存")
    const startTime = new Date()
    for(let i=0;i<num;i++){} // 大數(shù)計(jì)算
    const endTime = new Date()
    console.log(endTime - startTime) // 耗時
    return "Calculate big numbers"
}
let cashedCalculate = cached(calculateFn) 
console.log(cashedCalculate(10_000_000_000)) // 計(jì)算即緩存 // 9944 // Calculate big numbers
console.log(cashedCalculate(10_000_000_000)) // Calculate big numbers
console.log(cashedCalculate(20_000_000_000)) // 計(jì)算即緩存 // 22126 // Calculate big numbers
console.log(cashedCalculate(20_000_000_000)) // Calculate big numbers

這樣只用通過一個 cached 緩存函數(shù)的處理,所有的大數(shù)計(jì)算都能保證:輸入?yún)?shù)相同的情況下,全局只用計(jì)算一次,后續(xù)可直接使用更加語義話的函數(shù)調(diào)用來得到之前計(jì)算的結(jié)果。

此處也是柯里化的應(yīng)用,在 cached 函數(shù)中先傳需要處理的函數(shù)參數(shù),后續(xù)再傳入具體需要操作得值,將多參轉(zhuǎn)化為單個參數(shù)逐一傳入。

緩存函數(shù)

柯里化的思想不僅可以緩存判斷條件,緩存計(jì)算結(jié)果、緩存?zhèn)鲄?,還能緩存“函數(shù)”。

設(shè)想,我們有一個數(shù)字 7 要經(jīng)過兩個函數(shù)的計(jì)算,先乘以 10 ,再加 100,寫法如下:

const multi10 = function(x) { return x * 10; }
const add100 = function(x) { return x + 100; }
add100(multi10(7))

用柯里化處理后,即變成:

const multi10 = function(x) { return x * 10; }
const add100 = function(x) { return x + 100; }
const compose = function(f,g) { 
    return function(x) { 
        return f(g(x))
    }
}
compose(add100, multi10)(7)

前者寫法有兩個傳參是寫在一起的,而后者則逐一傳參。把最后的執(zhí)行函數(shù)改寫:

let compute = compose(add100, multi10)
compute(7)

所以,這里的柯里化直接把函數(shù)處理給緩存了,當(dāng)聲明 compute 變量時,并沒有執(zhí)行操作,只是為了拿到 ()=> f(g(x)),最后執(zhí)行 compute(7),才會執(zhí)行整個運(yùn)算;

怎么樣?柯里化確實(shí)百變吧?柯里化的起源和閉包的定義是同宗同源。正如前文最開始所說,柯里化是閉包的一對“孿生子”。

我們對閉包的解釋:“閉包是一個函數(shù)內(nèi)有另外一個函數(shù),內(nèi)部的函數(shù)可以訪問外部函數(shù)的變量,這樣的語法結(jié)構(gòu)是閉包。”與我們對柯里化的解釋“把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(或部分)的函數(shù),并且返回接受余下的參數(shù)和返回結(jié)果的新函數(shù)的技術(shù)”,這兩種說法幾乎是“等效的”,只是從不同角度對 同一問題 作出的解釋,就像 lambda 演算和圖靈機(jī)對希爾伯特第十問題的解釋一樣。

同一問題:指的是在 lambda 演算誕生之時,提出的:怎樣用 lambda 演算實(shí)現(xiàn)接收多個參數(shù)?

防抖與節(jié)流

好了,我們再來看看除了其它高階函數(shù)中閉包思想(柯里化思想)的應(yīng)用。首先是最最常用的防抖與節(jié)流函數(shù)。

防抖:就像英雄聯(lián)盟的回城鍵,按了之后,間隔一定秒數(shù)才會執(zhí)行生效。

function debounce(fn, delay) {
    delay = delay || 200;
    let timer = null;
    return function() {
        let arg = arguments;
        // 每次操作時,清除上次的定時器
        clearTimeout(timer);
        timer = null;
        // 定義新的定時器,一段時間后進(jìn)行操作
        timer = setTimeout(function() {
            fn.apply(this, arg);
        }, delay);
    }
};
var count = 0;
window.onscroll = debounce(function(e) {
    console.log(e.type, ++count); // scroll
}, 500);

節(jié)流函數(shù):就像英雄聯(lián)盟的技能鍵,是有 CD 的,一段時間內(nèi)只能按一次,按了之后就要等 CD;

// 函數(shù)節(jié)流,頻繁操作中間隔 delay 的時間才處理一次
function throttle(fn, delay) {
    delay = delay || 200;
    let timer = null;
    // 每次滾動初始的標(biāo)識
    let timestamp = 0;
    return function() {
        let arg = arguments;
        let now = Date.now();
        // 設(shè)置開始時間
        if (timestamp === 0) {
            timestamp = now;
        }
        clearTimeout(timer);
        timer = null;
        // 已經(jīng)到了delay的一段時間,進(jìn)行處理
        if (now - timestamp >= delay) {
            fn.apply(this, arg);
            timestamp = now;
        }
        // 添加定時器,確保最后一次的操作也能處理
        else {
            timer = setTimeout(function() {
                fn.apply(this, arg);
                // 恢復(fù)標(biāo)識
                timestamp = 0;
            }, delay);
        }
    }
};
var count = 0;
window.onscroll = throttle(function(e) {
    console.log(e.type, ++count); // scroll
}, 500);

代碼均可復(fù)制到控制臺中測試。在防抖和節(jié)流的場景下,被預(yù)先固定住的變量是 timer。

lodash 高階函數(shù)

lodash 大家肯定不陌生,它是最流行的 JavaScript 庫之一,透過函數(shù)式編程模式為開發(fā)者提供常用的函數(shù)。

其中有一些封裝的高階函數(shù),讓一些平平無奇的普通函數(shù)也能有相應(yīng)的高階功能。

舉幾個例子:

// 防抖動
_.debounce(func, [wait=0], [options={}])
// 節(jié)流
_.throttle(func, [wait=0], [options={}])
// 將一個斷言函數(shù)結(jié)果取反
_.negate(predicate) 
// 柯里化函數(shù)
_.curry(func, [arity=func.length])
// 部分應(yīng)用
_.partial(func, [partials])
// 返回一個帶記憶的函數(shù)
_.memoize(func, [resolver])
// 包裝函數(shù)
_.wrap(value, [wrapper=identity])

研究源碼你就會發(fā)現(xiàn),_.debounce 防抖、_.throttle 節(jié)流上面說過,_.curry 柯里化上面說過、_.partial 在“緩存?zhèn)鲄?rdquo;里說過、_.memoize 在“緩存計(jì)算”里也說過......

再舉一個例子:

現(xiàn)在要求一個函數(shù)在達(dá)到 n 次之前,每次都正常執(zhí)行,第 n 次不執(zhí)行。

也是非常常見的業(yè)務(wù)場景!JavaScript 實(shí)現(xiàn):

function before(n, func) {
  let result, count = n;
  return function(...args) {
    count = count - 1
    if (count > 0) result = func.apply(this, args)
    if (count <= 1) func = undefined
    return result
  }
}
const fn= before(3,(x)=>console.log(x))
fn(1) // 1
fn(2) // 2
fn(3) // 不執(zhí)行

反過來:函數(shù)只有到 n 次的時候才執(zhí)行,n 之前的都不執(zhí)行。

function after(n, func) {
  let count = n || 0
  return function(...args) {
    count = count - 1
    if (count < 1) return func.apply(this, args)
  }
}
const fn= after(3,(x)=>console.log(x))
fn(1) // 不執(zhí)行
fn(2) // 不執(zhí)行
fn(3) // 3 

全是“閉包”、全是把參數(shù)“柯里化”。

細(xì)細(xì)體會,在控制臺上敲一敲、改一改、跑一跑,下次或許你就可以自己寫出這些有特定功能的高階函數(shù)了。

結(jié)語

綜合以上,可見由函數(shù)式啟發(fā)的“閉包”、“柯里化”思想對 JavaScript 有多重要。幾乎所有的高階函數(shù)都離不開閉包、參數(shù)由多轉(zhuǎn)逐一的柯里化傳參思想。所在在很多面試中,都會問閉包,不管是一兩年、還是三五年經(jīng)驗(yàn)的前端程序員。定義一個前端的 JavaScript 技能是初級,還是中高級,這是其中很重要的一個判斷點(diǎn)。

對閉包概念模糊不清的、或者只會背概念的 => 初級

會寫防抖、節(jié)流、或柯里化等高階函數(shù)的 => 中級

深刻理解高階函數(shù)封裝思想、能自主用閉包封裝高階函數(shù) => 高級

以上就是從柯里化分析JavaScript重要的高階函數(shù)實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 柯里化高階函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用微信小程序顯示用戶當(dāng)前地理位置的詳細(xì)代碼

    使用微信小程序顯示用戶當(dāng)前地理位置的詳細(xì)代碼

    這篇文章主要介紹了使用微信小程序顯示用戶當(dāng)前地理位置的詳細(xì)代碼,在微信小程序的開發(fā)中不可避免的會遇到需要顯示用戶地理位置的操作,本文將簡單介紹如何在微信小程序中顯示用戶當(dāng)前的地理位置,需要的朋友可以參考下
    2022-06-06
  • 純javaScript、jQuery實(shí)現(xiàn)個性化圖片輪播【推薦】

    純javaScript、jQuery實(shí)現(xiàn)個性化圖片輪播【推薦】

    本文主要介紹了純javaScript、jQuery實(shí)現(xiàn)個性化圖片輪播的方法,并在文章結(jié)尾附上源碼下載。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • 讓getElementsByName適應(yīng)IE和firefox的方法

    讓getElementsByName適應(yīng)IE和firefox的方法

    讓getElementsByName適應(yīng)IE和firefox的方法...
    2007-09-09
  • JavaScript簡單修改窗口大小的方法

    JavaScript簡單修改窗口大小的方法

    這篇文章主要介紹了JavaScript簡單修改窗口大小的方法,涉及javascript中moveTo與resizeTo方法的相關(guān)使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • Promise掃盲貼

    Promise掃盲貼

    這篇文章主要介紹了Promise掃盲貼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • JavaScript 浮動定位提示效果實(shí)現(xiàn)代碼

    JavaScript 浮動定位提示效果實(shí)現(xiàn)代碼

    本來想做一個集合浮動定位和鼠標(biāo)跟隨的tooltips效果,但發(fā)現(xiàn)定位和鼠標(biāo)跟隨在一些關(guān)鍵的地方還是不同的,還是分開來吧。
    2009-09-09
  • 移動端H5開發(fā) Turn.js實(shí)現(xiàn)很棒的翻書效果

    移動端H5開發(fā) Turn.js實(shí)現(xiàn)很棒的翻書效果

    這篇文章主要為大家詳細(xì)介紹了Turn.js實(shí)現(xiàn)很棒的翻書效果,對Turn.js翻書效果的實(shí)現(xiàn)進(jìn)行總結(jié),感興趣的小伙伴們可以參考一下
    2016-06-06
  • js 通用javascript函數(shù)庫整理

    js 通用javascript函數(shù)庫整理

    js 通用javascript函數(shù)庫整理,學(xué)習(xí)js的朋友可以參考下。
    2011-08-08
  • TypeScript聲明合并的實(shí)現(xiàn)

    TypeScript聲明合并的實(shí)現(xiàn)

    本文主要介紹了TypeScript聲明合并的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 詳解JavaScript中數(shù)組的reduce方法

    詳解JavaScript中數(shù)組的reduce方法

    js函數(shù)中有三個在特定場合很好用的函數(shù):reduce(),map(),filter()。而數(shù)組經(jīng)常用到的方法有push、join、indexOf、slice等等,但是有一個經(jīng)常被我們忽略的方法:reduce,這個方法簡直強(qiáng)大的不要不要的。下面通過這篇文章來一起學(xué)習(xí)學(xué)習(xí)吧。
    2016-12-12

最新評論

金华市| 台东市| 蚌埠市| 沁源县| 阿巴嘎旗| 岢岚县| 河源市| 霍林郭勒市| 大荔县| 张家港市| 渑池县| 巩留县| 黑龙江省| 黔江区| 贵阳市| 大足县| 策勒县| 崇文区| 金昌市| 旬阳县| 北流市| 博罗县| 宁国市| 兴城市| 安达市| 静乐县| 阿克苏市| 盘锦市| 渭源县| 临漳县| 固阳县| 平安县| 墨竹工卡县| 隆林| 和林格尔县| 溧水县| 海淀区| 安化县| 东阿县| 万山特区| 合山市|