JS中的高階函數


JS中的高階函數

高階函數是指以函數作為參數的函數,並且可以將函數作為結果返回的函數。

1. 高階函數

  • 接受一個或多個函數作為輸入
  • 輸出一個函數

至少滿足以上一個條件的函數

在js的內置對象中同樣存在着一些高階函數,像數組的mapfilterreduce方法等,它們接受一個函數作為參數,並應用這個函數到列表的每一個元素

1.1 map

map方法接收一個函數作為參數 ,遍歷數組,並且返回一個新的數組,新的數組里的每個元素都執行map傳入的函數。

let arr = [1, 2, 3, 4];
let arr1 = arr.map(item => item * 2)
console.log(arr1);// [2, 4, 6, 8]

返回的是一個新數組arr1不改變原數組

注意:如果傳入的參數沒有返回值,則數組的每一項都會是undefind

經典題目

console.log(['1','2','3'].map(parseInt)); 

來看看上面這個代碼輸出什么

答案[1, NaN, NaN]

解析

parseInt() 函數可解析一個字符串,並返回一個整數。

當參數 radix 的值為 0,或沒有設置該參數時,parseInt()會根據該字符串來判斷數字的基數。

當忽略參數 radix , 默認的基數如下:

  • 如果 字符串 以 “0x” 開頭,parseInt() 會把 其余部分解析為十六進制的整數。
  • 如果字符串以 0 開頭,把其余部分解析為八進制或十六進制的數字。
  • 如果字符串以 1 ~ 9 的數字開頭,parseInt()將把它解析為十進制的整數

注意:基數可不是默認十進制噢!

當我們把數組傳入parseInt時,由於接收2個參數,會將數組的索引作為基數傳個parseInt,所以實質上進行的是以下幾步

parseInt('1', 0)
parseInt('2', 1)
parseInt('3', 2)

注意:如果字符串的第一個字符不能被轉換為數字,那么parseInt()會返回 NaN。

小tips

parseInt()還有很多值得注意的問題,可以使用搜索引擎再了解以下

1.2 filter

用於篩選數組

filter方法接收一個函數作為參數,通過這個函數來指定篩選數組的規則,最后返回滿足規則的新數組

在傳入的函數中有3個參數可選

參數 描述
currentValue 必須。當前元素的值
index 可選。當前元素的索引值
arr 可選。當前元素屬於的數組對象

注意

  • 不會檢測空數組
  • 不會改變原始數組
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let arr1 = arr.filter(num => {
    return num > 5
})
console.log(arr1);// [6, 7, 8, 9]

1.3 reduce

reduce能做的事情很多,但是我們平時都使用for循環之類的方法代替了,但是reduce真的高逼格

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

w3school中給出的reduce語法,這里我們常用的只有前面兩個

參數 描述
total 必需。初始值, 或者計算結束后的返回值。
currentValue 必需。當前元素
let arr = [1, 2, 3, 4]
let sum = arr.reduce((value, item) => {
    console.log(value, item);
    // 1 2 3 3 6 4 
    return value + item
})
console.log(sum);// 10

從第四行的調試中可以看出reduce函數的執行過程,在沒有初始值的情況下,將數組第一個值作為value第二個值作為item再依次往下遍歷整個數組,將返回值作為value,數組的下一位作為item,直至遍歷完成。

在這里插入圖片描述

利用ruduce實現數組去重

let arr = [1,1,2,3,4,2,5,4];
let unique = arr.reduce(function (prev, item) {
    prev.indexOf(item) === -1 && prev.push(item);
    return prev;
}, []);
console.log(unique); // [1, 2, 3, 4, 5]

通過將空數組作為prev初始值,再通過indexOf判斷數組中是否包含item,如果沒有就將item加入數組,最終返回數組

關於&&運算符,第一條語句為true則執行第二條,否則不執行

ruduce的用法遠不止這些,有興趣的可以再了解以下~


還有很多內置對象都是高階函數,這里就不一一說明了,從上面的三個方法中,已經能很直觀的感受到了函數接收函數作為參數,再返回值的過程,逼格很高也很好用

2. AOP 面向切面編程

當我們需要使用一個公共函數,並且需要在這個函數執行前后添加自己的邏輯,通常我們的做法不能是直接修改這個函數,因為它是公共函數,這時候我們可以通過AOP的方法利用高階函數和原型鏈的特點進行處理

把一些與業務無關的功能抽離出來,通過"動態植入"的方法,摻入到業務邏輯模塊中。這樣做的好處是保證業務邏輯模塊的純凈和高內聚,其次可以方便的復用功能模塊

  • 下面我們要實現在函數執行前輸出提示信息
function say(who) {
    console.log(who + ':函數執行了');
}
Function.prototype.before = function(callback) {
    return (...args) => {
        callback()
        this(...args)
    }
}
let whoSay = say.before(function() {
    console.log('你要被調用了');
})
whoSay('ljc')
// 你要被調用了
// ljc:函數執行了

如果需要實現后置通知,只需要將6,7行換以下就可以了

實現的原理

在調用公共函數時,傳入我們需要執行提前執行的函數,在內部函數執行前先調用該函數

3. 偏函數

當一個函數有很多參數時,調用該函數就需要提供多個參數,如果可以減少參數的個數,就能簡化該函數的調用,降低調用該函數的難度。

  • 實現3個數求和
function sum(a, b, c){
	return a + b + c;
}
sum(1, 2, 3) // 6

在調用時我們需要傳入3個參數,好像有些許麻煩,下面我們用偏函數的做法

創建一個新的partial函數,這個新函數可以固定住原函數的部分參數,從而減少調用時的輸入的參數,讓我們的調用更加簡單

function sum(a, b, c) {
    return a + b + c
}

function partial(sum, c) {
    return function (a, b) {
        return sum(a, b, c)
    }
}
let partialSum = partial(sum, 3)// -> 6

高階函數除了可以接收函數作為參數外,還可以將函數作為結果返回,偏函數就是固定了函數的一個或多個參數,返回一個新的函數接收剩下的參數,以此來簡化函數的調用。

Function.prototype.bind 函數就是一個偏函數的典型代表,它接受的第二個參數開始,為預先添加到綁定函數的參數列表中的參數

4. 函數柯里化

與偏函數不同,柯里化是把接收多個參數的函數轉換成多個只接收一個參數的函數。

我們從一個簡單的例子來認識函數柯里化

function add(a, b) {
    return a + b;
}
add(1, 2) // 3 普通做法 一次傳入兩個參數

// 假設有一個 curring 函數可以做到柯里化
function curring(){}
curring(1)(2) // 我們通過這樣的方式來接受參數,這樣就實現了柯里化

接下來我們來看看利用柯里化來實現

function curring(x) {
    return return y => x + y
}
curring(1)(2)  // => 3

4.1 函數柯里化的作用

要真正理解柯里化還是得看示例

4.1.1 參數復用

我們先看一段短短的代碼,這段代碼中,實現了輸入輸出個人信息的功能,通過myInfo函數將參數拼接返回,這實際上很簡單,但是當用很多很多的用戶信息時,需要一直傳遞着個人信息這個參數,這樣顯然是不合理的

function myInfo(inf, name, age) {
    return `${inf}${name}${age}`
}
const myInfo1 = myInfo('個人信息', 'ljc', '19')
console.log(myInfo1); // 個人信息:ljc19

下面我們通過柯里化技術來解決

function myInfoCurry(inf) {
    return (name, age) => {
        return `${inf}${name}${age}`
    }
}
let myInfoName = myInfoCurry('個人信息')
const myInfo1 = myInfoName('ljc', '19')
const myInfo2 = myInfoName('ljcc','19')
console.log(myInfo2); // 個人信息:ljcc119
console.log(myInfo1); // 個人信息:ljc19

這個就是柯里化技術的作用之一了,參數復用,個人感覺還是很好用的

在上面代碼的基礎上,我們可以繼續擴展我們的信息,就像這樣,利用一個函數就可以實現多個功能

let myInfoSex = myInfoCurry('愛好')
const myInfo3 = myInfoSex('看球賽','聽歌')
console.log(myInfo3); // 愛好:看球賽聽歌

4.1.2 提前返回

這個特性是用來對瀏覽器的監聽事件兼容性做一些判斷並初始化,解決有些瀏覽器對addEventListener存在的兼容性問題,所以在使用之前做一次判斷,之后就可以省略了

const whichEvent = (function () {
    if (window.addEventListener) {
        return function (ele, type, listener, useCapture) {
            ele.addEventListener(type, function (e) {
                listener.call(ele, e)
            }, useCapture)
        }
    } else if (window.attachEvent) {
        return function (ele, type, handler) {
            ele.attachEvent('on' + type, function (e) {
                handler.call(ele, e)
            })
        }
    }
})()

由於使用了立即執行函數,即使觸發多次事件依舊只會觸發一次if條件判斷

4.1.3 延遲執行

下面我們通過一道例題來了解

編寫一個add函數實現下面的功能

add(1)(2)(3) = 6

add(1, 2, 3)(4) = 10

add(1)(2)(3)(4)(5) = 15

function add(...args) {
    let inner = function () {
        args.push(...arguments);
        inner.toString = function () {
            return args.reduce((prev, cur) => {
                return prev + cur
            })
        }
        return inner
    }
    return inner
}
console.log(add(1)(2)(3)); // f 6

這段代碼中涵蓋的知識面很多,核心的部分在於inner.toString這里,利用了當返回一個函數時返回的是它的字符串形式,所以我們可以利用這個特性來自定義我們的返回值


以上就是關於高階函數的全部內容了,這部分的知識有點難,可能理解的不夠深入,如果有什么好的方法,可以留言討論一下

參考文獻:JavaScript Web前端開發指南

部分代碼實現學習於b站,以及GitHub等平台


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM