首頁  >  文章  >  web前端  >  一起來聊聊JavaScript函數柯里化

一起來聊聊JavaScript函數柯里化

WBOY
WBOY轉載
2022-03-04 18:16:101936瀏覽

本篇文章為大家帶來了關於javascript中的相關知識,其中主要介紹了JavaScript中函數柯里化的相關問題,柯里化是把接受多個參數的函數變換成接受單一參數的函數,並且傳回接受餘下的參數且傳回結果的新函數的技術,希望對大家有幫助。

一起來聊聊JavaScript函數柯里化

相關推薦:javascript教學

#一、簡單了解apply和call

  • call 和apply 都是為了改變某個函數執行時期的context 即上下文而存在的,換句話說,就是為了改變函數體內部this 的指向。
  • call 和 apply二者的作用完全一樣,只是接受參數的方式不太一樣。 call其實是apply的一種語法糖。
  • 格式:apply(context,[arguments]),call(context,param1,param2,...)

二、什麼是函數柯里化?

柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且傳回接受餘下的參數且傳回結果的新函數的技術。

在這裡舉個例子,有一個add()函數,它是用來處理我們傳給它的參數(param1,params2,…)相加求和的一個函數。

// 在这里第一个具有两个参数`x`、`y`的`add(x , y)`函数
function add(x , y){
	return x + y;
}

// 调用`add()`函数,并给定两个参数`4`和`6`
add(4,6);

// 模拟计算机操作,第一步 传入第一个参数 4
function add(4 , y){
	return 4 + y;
}

// 模拟计算机操作,第二步 传入第一个参数 6
function add(4 , 6){
	return 4 + 6;
}

如果我們將add()函數柯里化,是什麼樣子呢?這裡簡單的實作一下:

// 柯里化过的add()函数,可以接受部分参数
function add(x ,y){
	if (typeof y === 'undefined') {
		return function (newy){
			return x + newy;
		}
	}
	// 完整应用
	return x + y;
}

// 测试调用
console.log(typeof add(4)); // [Function]
console.log(add(4)(6)); // 10

// 可以创建保存函数
let saveAdd = add(4);
console.log(saveAdd(6)); // 10

從以上簡單柯里化的add()函數可以看出,函數可以接受部分函數,然後傳回一個新的函數,使其繼續處理剩下的函數。

三、寫一個公共的柯里化函數

在這裡我們創建一個公共的柯里化函數,這樣我們就不必每次寫一個函數都要在其內部實現複雜的柯里化過程。

// 定义一个createCurry的函数function createCurry(fn){
	var slice = Array.prototype.slice,
	stored_args = slice.call(arguments,1);
	
	return function () {
		let new_args = slice.call(arguments),
		args = stored_args.concat(new_args);
		return fn.apply(null,args);
	}}

在上述公共的柯里化函數中:

  • arguments,並不是一個真的數組,只是一個有length屬性的對象,所以我們從Array.prototype中藉用slice方法幫我們把arguments轉為一個真正的數組,方便我們更好的操作。
  • 當我們第一次呼叫函數createCurry的時候,其中變數stored_args 是保持了除去第一個參數以外的參數,因為第一個參數是我們需要柯里化的函數。
  • 當我們執行createCurry函數中傳回的函數時,變數new_args取得參數並轉為陣列。
  • 內部傳回的函數透過閉包存取變數stored_args中儲存的值和變數new_args的值合併為一個新的數組,並賦值給變數args
  • 最後呼叫fn.apply(null,args)方法,執行被柯里化的函數。

現在我們來測試公共的柯里化函數

// 普通函数add()
function add(x , y){
	return x + y;
}

// 柯里化得到一个新的函数
var newAdd = createCurry(add,4);
console.log(newAdd(6)); // 10


//另一种简便方式
console.log(createCurry(add,4)(6));// 10

當然這裡不限於兩個參數的柯里化,也可以多個參數:

// 多个参数的普通函数
function add(a,b,c,d){
	return a + b + c + d;
}

// 柯里化函数得到新函数,多个参数可以随意分割
console.log(createCurry(add,4,5)(5,6)); // 20

// 两步柯里化
let add_one = createCurry(add,5);
console.log(add_one(5,5,5));// 20
let add_two = createCurry(add_one,4,6);
console.log(add_two(6)); // 21

透過以上的例子,我們可以發現一個局限,那就是不管是兩個參數還是多個參數,它只能分兩步驟執行,如以下公式:

  • fn(x,y) ==> fn(x)(y);
  • fn(x,y,z,w) ==> fn(x)(y,z, w) || fn(x,y)(z,w)||…

如果我們想要更靈活一點:

  • fn(x,y) = => fn(x)(y);
  • fn(x,y,z) ==> fn(x,y)(z) || fn(x)(y)(z) ;
  • fn(x,y,z,w) ==> fn(x,y)(z)(w) || fn(x)(y)(z)(w) || …;

我們該怎麼實現呢?

四、創建一個靈活的柯里化函數

經過以上練習,我們發現我們創建的柯里化函數存在一定局限性,我們希望函數可以分為多步驟執行:

// 创建一个可以多步执行的柯里化函数,当参数满足数量时就去执行它:
// 函数公式:fn(x,y,z,w) ==> fn(x)(y)(z)(w);
let createCurry = (fn,...params)=> {
	let args = parsms || [];
	let fnLen = fn.length; // 指定柯里化函数的参数长度
	
	return (...res)=> {
		// 通过作用域链获取上一次的所有参数
		let allArgs = args.slice(0);
		// 深度拷贝闭包共用的args参数,避免后续操作影响(引用类型)
		allArgs.push(...res);
		if(allArgs.length < fnLen){
		   // 当参数数量小于原函数的参数长度时,递归调用createCurry函数
		   return createCurry.call(this,fn,...allArgs);
		}else{
		  // 当参数数量满足时,触发函数执行
		  return fn.apply(this,allArgs);
		}
	}
}


// 多个参数的普通函数
function add(a,b,c,d){
	return a + b + c + d;
}

// 测试柯里化函数

let curryAdd = createCurry(add,1);
console.log(curryAdd(2)(3)(4)); // 10

以上我們已經實現了靈活的柯里化函數,但是這裡我們又發現了一個問題:

  • 如果我第一次就把參數全部傳入,但是它並沒有回傳結果,而是一個函數(function)。
  • 只有我們再次將傳回的函數呼叫一次才能傳回結果:curryAdd(add,1,2,3,4)();
  • 可能有人說如果是全部傳參,就呼叫原來的add()函數就行了,這也是一種辦法;但是我們在這裡既然是滿足參數數量,對於這種情況我們還是處理一下。

在這裡我們只需要在返回函數前做一下判斷就行了:

let createCurry = (fn,...params)=> {
	let args = parsms || [];
	let fnLen = fn.length; // 指定柯里化函数的参数长度
	
	if(length === _args.length){
	   // 加入判断,如果第一次参数数量以经足够时就直接调用函数获取结果
           return fn.apply(this,args);
        }
	return (...res)=> {
		let allArgs = args.slice(0);
		allArgs.push(...res);
		if(allArgs.length < fnLen){
		   return createCurry.call(this,fn,...allArgs);
		}else{
		  return fn.apply(this,allArgs);
		}
	}}

以上可以算是完成了一個靈活的柯里化的函數了,但是這裡還不算很靈活,因為我們不能控制它什麼時候執行,只要參數數量夠它就自動執行。我們希望實現一個可以控制它執行的時機該怎麼辦?

五、寫一個可控制的執行時間的柯里化函數

我們這裡直接說明一下函數公式:

  • fn(a,b,c) ==> fn(a)(b)(c )();
  • fn(a,b,c) ==> fn(a);fn(b);fn(c );fn();
  • 当我们参数足够时它并不会执行,只有我们再次调用一次函数它才会执行并返回结果。在这里我们在以上例子中加一个小小的条件就可以实现。
  • // 当参数满足,再次执行时调用函数
    let createCurry = (fn,...params)=> {
    	let args = parsms || [];
    	let fnLen = fn.length; // 指定柯里化函数的参数长度
    	
    	//当然这里的判断需要注释掉,不然当它第一次参数数量足够时就直接执行结果了
    	//if(length === _args.length){
    	   // 加入判断,如果第一次参数数量以经足够时就直接调用函数获取结果
               //return fn.apply(this,args);
            //}
    	return (...res)=> {
    		let allArgs = args.slice(0);
    		allArgs.push(...res);
    		// 在这里判断输入的参数是否大于0,如果大于0在判断参数数量是否足够,
    		// 这里不能用 && ,如果用&& 也是参数数量足够时就执行结果了。
    		if(res.length > 0 || allArgs.length < fnLen){
    		   return createCurry.call(this,fn,...allArgs);
    		}else{
    		  return fn.apply(this,allArgs);
    		}
    	}
    }
    
    
    // 多个参数的普通函数
    function add(a,b,c,d){
    	return a + b + c + d;
    }
    
    // 测试可控制的柯里化函数
    
    let curryAdd = createCurry(add,1);
    console.log(curryAdd(2)(3)(4)); // function
    console.log(curryAdd(2)(3)(4)()); // 10
    console.log(curryAdd(2)(3)()); // 当参数不足够时返回 NaN

    相关推荐:javascript学习教程

    以上是一起來聊聊JavaScript函數柯里化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除