在JS裏函數是一等公民,它跟其餘類型(字符,數字,數組,對象)等同樣,都是能夠直接當成參數傳遞或者當成返回值返回的。活用函數是一個前端開發必備的基礎技能。javascript
舉個簡單的例子,假設咱們要打印出一個數組的全部元素的話,大部分人可能會這樣作:前端
function printWithLoop(arr) {
for (let i = 0, len = arr.length; i < len; i++) {
console.dir(arr[i]);
}
}複製代碼
當你對高階函數(higher-order function)有一點了解以後,你可能會習慣用forEach:java
function printWithIterator(arr) {
(arr || []).forEach(it => {
console.dir(it);
});
}複製代碼
上面的例子又能夠進一步優化爲:git
function simplePrint(arr) {
(arr || []).forEach(console.dir);
}複製代碼
此處能夠有道面試題:若是上面的console.dir替換成console.log的話上面3種作法仍是等價的麼?github
柯里化,又稱部分求值,簡單的說就是在函數運行時把參數吃進來,並同時返回一個新的函數(該函數經過閉包的特性能夠訪問到前面調用時吃進來的參數),供後續調用。web
柯里化跟參數息息相關。參數有2個概念:面試
function A(a, b) {}
// a跟b就是形參
console.log(A.length); //2複製代碼
function B(a, b) {
console.log(arguments);
}
B(1,2,3); // 1,2,3複製代碼
形參就是這個函數指望的參數,實參就是這個函數調用時實際獲得的參數。有了這2個數據咱們就能夠作不少事情了。數組
假設咱們有個求3個數字之和的函數:閉包
function sum(x, y, z) {
console.log(x + y + z);
}
sum(1,2,3); //6複製代碼
若是我想實現下面這樣的效果:app
sum(1,2,3); //6
sum(1)(2,3); //6
sum(1,2)(3); //6複製代碼
思考一下規律,其實就是當sum函數獲得了它指望的參數(i.e. 3個參數)時,它就返回結果。要否則就返回一個新的函數(e.g. sum(1)跟sum(1,2))持續吃進新參數。這時候就用到柯里化的思想。
function curry(fn) { //暫時不考慮上下文this的狀況
return function f(...args) {
//傳進來的參數個數很多於形參個數,調用並返回結果
if(args.length >= fn.length) {
return fn.apply(this, args);
} else {
//傳進來的參數個數少於形參個數,返回一個閉包
return function(...arr) {
return f.apply(this, args.concat(arr));
}
}
}
}
let sumWithCurry = curry(sum);
sumWithCurry(1,2,3); //6
sumWithCurry(1)(2,3); //6
sumWithCurry(1,2)(3); //6複製代碼
Function.prototype.bind方法除了有綁定執行語境this的功能外,還有柯里化的功能。
function sayHi(greeting, ending) {
console.log(`My name is ${this.name}, ${greeting}. ${ending}!`);
}
//這裏已經把greeting吃進去了
let fn = sayHi.bind({name: 'mike'}, 'Love you');
fn('Thanks!'); // My name is mike, Love you. Thanks!!複製代碼
實際工做中能夠巧用柯里化去寫更優雅的代碼。
function print(arr) {
console.log(arr.join('|'));
}
let arr = [1,2,3];
setTimeout(function(){
print([1,2,3]);
}, 1000);
// 1|2|3複製代碼
等價於
function print(arr) {
console.log(arr.join('|'));
}
let arr = [1,2,3];
setTimeout(print.bind(null, arr), 1000);
// 1|2|3複製代碼