函数知识总结

在这里插入图片描述

什么是函数

一般来说,一个函数是可以通过外部代码调用的一个子程序(或在递归情况下由内部函数调用)。像程序本身一样,一个函数由称为函数体的一系列语句组成。值可以传递给一个函数,函数将返回一个值。

函数首先是一个对象,并且在javascript中,函数是一等对象(first-class object)。函数可以被执行(callable,拥有内部属性[[Call]]),这是函数的本质特征。除此之外,函数可以赋值给变量,也可以作为函数参数,还可以作为另一个函数的返回值

函数基本概念

函数名

函数名是函数的标识,如果一个函数不是匿名函数,它应该被赋值函数名。

  • 函数命名需要符号javascript标识符规则,必须以字母、下划线或美元符开始,后面可以跟数字、字母、下划线、美元符。
  • 函数命名不能使用javascript保留字,保留字是javascript中具有特殊含义的标识符。
  • 函数命名应该语义化,尽量使用动宾结构,小驼峰写法。
  • 对于构造函数,我们通常写成大驼峰格式(因为构造函数与类的概念强关联)。

下面是一些不成文的约定,不成文代表它不必遵守,但是我们按照这样的约定来执行,会让开发变得更有效率。

  • _xxx_代表非标准的方法。
  • _xxx代表私有方法。

形参

形参是函数定义时约定的参数列表,由一对圆括号()包裹。

在MDN上有看到,一个函数最多可以有255个参数。

然而形参太多时,使用者总是容易在引用时出错。所以对于数量较多的形参,一般推荐把所有参数作为属性或方法整合到一个对象中,各个参数作为这个对象的属性或方法来使用。

形参的数量可以由函数的length属性获得。

function test(a,b,b){}
test.length; // 3

实参

实参是调用函数时传入的,实参的值在函数执行前被确定。

javascript在函数定义时并不会约定参数的数据类型,如果你期望函数调用时传入正确的数据类型,你必须在函数体中对入参进行数据类型判断。

function add(a, b) {
	if(typeof a !== 'number' || typeof b !== 'number') {
		throw new Error('参数必须是数字类型');
	}
}

好在TypeScripe提供了数据类型检查的能力,这一定程度上防止了意外情况发生。

实参的数量可以通过函数中arguments对象的length属性获得:

实参数量不一定与形参数量一致。

function test(a, b, c) {
	return arguments.length;
}
test(1, 2); // 2

默认参数

函数参数的默认值是undefined,如果不不传入实参,那么实际上在函数执行过程中,相应参数的值是undefined

ES6也支持在函数声明时设置参数的默认值。

function add(a, b = 2) {
	return a + b;
}
add(1); // 3

剩余参数

剩余参数语法允许我们将一个不定数据的参数表示为一个数组。

剩余参数通过剩余语法...将多个参数聚合成一个数组。

function add(a, ...args) {
	return args.reduce((prev, curr) => {
		return prev + curr;
	}, a);
}

剩余参数和arguments对象之间的区别主要有三个:

  • 剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参。
  • arguments对象不是一个真正的数组,而剩余参数是真正的Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如sort,map,forEachpop。而arguments需要借用call来实现,比如[].slice.call(arguments)
  • arguments对象还有一些附加的属性(如callee属性)。

剩余语法和展开运算符看起来相似,然而从功能上来说,是完全相反的。

剩余语法(Rest syntax)看起来和展开语法(Spread syntax)完全相同,不同点在于,剩余参数用于解构数值和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来并凝聚为单个元素。

展开语法

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];

console.log(sum(...numbers));
// expected output: 6

console.log(sum.apply(null, numbers));
// expected output: 6

arguments

函数的实际参数会被保存在一个类数组对象arguments中。

类数组(ArrayLike)对象具备一个非负的length属性,并且可以通过从0开始的索引去访问元素,让人看起来觉得就像是数组,比如NodeList,但是类数组默认没有数组的那些内置方法,比如push,pop,forEach,map

可以试试,随便找一个网站,在控制台输入:

var linkList = document.querySelectorAll('a');

会得到一个NodeList,我们也可以使用数字下标去访问其中的元素,比如listList[0]

但是NodeList不是数组,它是类数组。

Array.isArray(linkList); // false

将arguments转化为数组:

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES2015
const args = Array.from(arguments);
const args = [...arguments];

回到主题,arguments也是类数组argumentlength是由实参的数量决定,而不是由形参的数量决定。

function add(a, b){
	console.log(arguments.length);
	return a + n=b;
}
add(1, 2, 3, 4); // 4 

arguments也是一个和严格模式有关联的对象。

  • 非严格模式下,arguments里的元素和函数参数都是指向同一个值的引用,对arguments的修改,会直接影响函数参数。

    function test(obj) {
    	arguments[0] = '传入的实参是一个对象,但是被我变成字符串了';
    	console.log(obj);
    }
    test({name: 'a'});
    // 这里打印的是字符串,而不是对象
    
  • 严格模式下,arguments是函数参数的副本,对arguments的修改不会影响函数参数。但是arguments不能重新被赋值,在严格模式下,也不能使用arguments.callerarguments.callee,限制了对调用栈的检测能力。

函数体

函数体(FunctionBody)是函数的主体,其中的函数代码(function code)是由一对花括号{}包裹。函数体可以为空,也可以由任意条javascript语句组成。

函数的调用方式

大体来说,函数的调用形式分为以下四种:

作为普通函数

函数作为普通函数被调用,这是函数调用的常用形式。

function add(a, b) {
	return a + b;
}
add(); // 调用

作为普通函数调用时,如果在非严格模式下,函数执行时,this指向全局对象,对于浏览器而言则是window对象,如果在严格模式下,this的值则是undefined

作为对象的方法

函数也可以作为对象的成员,这种情况下,该函数通常被称为对象方法。当函数作为对象的方法被调用时,this指向该对象,此时便可以通过this访问对象的其他成员变量或方法。

var counter = {
	num: 0,
	increase: function() {
		this.num++;
	}
}
counter.increase();

作为构造函数

函数配合new关键字使用时就成了构造函数。构造函数用于实例化对象,构造函数的执行过程大致如下:

  1. 首先创建一个新对象,这个新对象的_proto_属性指向构造函数的prototype属性。
  2. 此时构造函数的this指向这个新对象。
  3. 执行构造函数种的代码,一般是通过this给新对象添加新的成员属性或方法。
  4. 最后返回这个新对象。

实例化对象也可以通过一些技巧来简化,比如在构造函数中显示地return另一个对象。

通过call,apply调用

applycall是函数对象的原型方法,挂载于Function.prototype。利用这两个方法,我们可以显示地绑定一个this作为调用上下文,同时也可以设置函数调用时的参数。

applycall的区别在于:提供参数的形式不同,apply方法接受的是一个参数数组call方法接受的是参数列表

someFunc.call(obj, 1, 2, 3);
someFunc.apply(obj, [1, 2, 3]);

注意,在非严格模式下使用call或者apply时,如果第一个参数被指定为null或者undefined,那么函数执行时的this指向全局对象(浏览器环境是window);如果第一个参数被指定为原始值,该原始值会被包装。

call是用来实现继承的重要方法。在子类构造函数中,通过call来调用父类构造函数,以使对象实例获得来自父类构造函数的属性或方法。

function Father() {
	this.nationality = 'Han';
}
Father.prototype.propA = '我是父类原型上的属性';
function Child() {
	Father.call(this);
}
Child.prototype.propB = '我是子类原型上的属性';
var child = new Child();
child.nationality; // Han

call、apply、bind

JavaScript中,callapplybindFunction对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向,从而可以达到移花接木的效果。

call(thisArgs[, args…])

该方法可以传递一个thisArgs参数和一个参数列表,thisArgs指定了函数在运行期的调用者,也就是函数中的this对象,而参数列表会被传入调用函数中。
thisArgs的取值有以下四种情况:

  1. 不传,或者传nullundefined,函数中的this指向window对象。

    function a() {
        console.log(this); //输出函数a中的this对象
    }
    function b() {} //定义函数b
    
    a.call(); // window
    a.call(null); // window
    a.call(undefined); // window	
    
  2. 传递另一个函数的函数名,函数中的this指向这个函数的引用

    function a() {
        console.log(this); //输出函数a中的this对象
    }
    function b() {} //定义函数b
    
    a.call(b); // function b(){}
    
  3. 传递字符串、数值或布尔类型的基础类型,函数中的this指向其对应的包装对象,如StringNumberBoolean

    function a() {
        console.log(this); //输出函数a中的this对象
    }
    function b() {} //定义函数b
    
    a.call(1); // Number
    a.call(''); // String
    a.call(true); // Boolean
    
  4. 传递一个对象,函数中的this指向这个对象。

    function a() {
       console.log(this); //输出函数a中的this对象
    }
    var obj = {
    	name: 'tom'
    };
    
    a.call(obj); // Object
    

这是call的核心功能,它允许你在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性,例子:

var a = {
	name: 'tom',
	say: function() {
		console.log('Hi, function a');
	}
}
function b(name) {
	console.log('Post Params: ' + name);
	console.log('I: ' + this.name);
	this.say();
}
b.call(a, 'test'); 
// Post Params: test
// I: tom
// Hi, function a

当执行b.call时,字符串test作为参数传递给了函数b,由于call的作用,函数b中的this指向了对象a,因此相当于调用了对象a上的函数b,而实际上a中并没有定义b

apply(thisArgs[,args[]])

apply和call的唯一区别是第二个参数的传递方式不同,apply的第二个参数必须是一个数组(或者类数组),而call允许传递一个参数列表,值得注意的是,虽然apply接受的是一个参数列表,但在传递给调用函数时,却是以参数列表的形式传递。例子:

function b(a, b, c) {
	console.log(a, b, c);
}
b.apply(null, [1, 2, 3]); // 1 2 3

this指向问题

分析this的指向,首先要确定当前执行代码的环境。

全局环境中的this指向

全局环境中,this指向全局对象(视宿主环境而定,浏览器是window,Node是global)。

函数中的this指向

函数中this的指向取决于函数的调用形式,在一些情况下也受到严格模式的影响。

  • 作为普通函数调用:严格模式下,this的值是undefined,非严格模式下,this指向全局对象
  • 作为方法调用:this指向所属对象。
  • 作为构造函数调用:this指向实例化的对象。
  • 通过callapplybind调用:如果指定了第一个参数thisArgthis的值就是thisArg的值(如果是原始值,会包装为对象);如果不传thisArg,要判断严格模式,严格模式下thisundefined,非严格模式下this指向全局对象。

函数声明和函数表达式

函数声明

函数声明是独立的函数语句

function test() {}

函数声明存在提升现象,如变量提升一般,对于同名的情况,函数声明优于变量声明(前者覆盖后者,我说的是声明阶段哦);

函数表达式

函数表达式不是独立的函数语句,常作为表达式的一部分,比如赋值表达式。

函数表达式可以是命名的,也可以是匿名的。

var a = function test(){}

var b = function() {}

匿名函数就是没有函数名的函数,它不能单独使用,只能作为表达式的一部分使用。匿名函数常以IIFE(立即执行函数表达式)的形式使用。

(function(){console.log('IIFE');}())

函数声明提升

// 函数声明语法
f('hello');
function f(name) {
	console.log(name);
}
// 控制台打印 hello

// 函数表达式语法
f('hello');
var f = function(name) {
	console.log(name);
}
// 会报错Uncaught ReferenceError: f is not defined(…)
// 错误信息显示说f没有被定义

为什么同样的代码,函数声明和函数表达式存在着差异呢?
这是因为,函数声明有一个非常重要的特征:函数声明提升函数声明语句将会被提升到外部脚本或外部函数作用域的顶部。正是因为这个特征,所以可以把函数声明放在调用它的语句后面。例如:

var getName = function() {
	console.log(2);
}
function getName() {
	console.log(1);
}
getName();

可能有人会觉得最后输出的结果是1。让我们分析一下,这个例子涉及了变量声明提升函数声明提升。正如前面说到的函数声明提升,函数声明function getName(){}的声明会被提前到顶部。而函数表达式var getName = function(){}则表现出变量声明提升。因此在这种情况下,getName也是一个变量,因此这个变量的声明也将提升到顶部,而变量的赋值依旧保留在原来的位置。

需要注意的是:函数优先虽然函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量

因此上面的函数可以转化为下面的样子:

function getName() {
	console.log(1);
}
var getName;
getName = function() {
	console.log(2);
}

getName();

所以最终的输出结果是2

纯函数

  • 纯函数是具备幂等性(对于相同的参数,任何时间执行纯函数都将得到同样的结果),它不会引起副作用。

  • 纯函数与外部的关联应该都来源于函数参。如果一个函数直接依赖了外部变量,那它就不是纯函数,因为外部变量是可变的,那么纯函数的执行结果就不可控了。

    // 纯函数
    function pure(a, b) {
      return a + b;
    }
    // 非纯函数
    function impure(c) {
      return c + d
    }
    var d = 10;
    pure(1, 2); // 3
    impure(1); // 11
    d = 20;
    impure(1); // 21
    pure(1, 2); // 3
    

惰性函数

相信大家在兼容事件监听时,都写过这样的代码。

function addEvent(element, type, handler) {
	if(window.addEventListener) {
		element.addEventListener(type, handler, false);
	} else if(window.attachEvent) {
		window.attachEvent('on' + type, handler);
	} else {
		element['on' + type] = handler;
	}
}

仔细看下,我们会发现,每次调用addEvent,都会做一次if-else的判断,这样的工作显然是重复的,这个时候就用到惰性函数了。

惰性函数表示函数执行的分支只会在函数第一次调用的时候执行。后续我们所使用的就是这个函数执行的结果。惰性函数,是针对优化频繁使用的函数。常用于函数库的编写、单例模式之中。

利用惰性函数的思维,改造代码:

function addEvent(element, type, handler) {
	if(window.addEventListener) {
		addEvent = function(element, type, handler) {
			element.addEventListener(type, handler, false);
		}
	} else if(window.attachEvent) {
		addEvent = function(element, type, handler) {
			window.attachEvent('on' + type, handler);
		}
	} else {
		addEvent = function(element, type, handler) {
			element['on' + type] = handler;
		}
	}
	addEvent(element, type, handler);
}

这段代码看起来有点low,但是它确实减少了重复判断。在这种方式下,函数第一次执行时才确定真正的值。

我们还可以利用IIFE提前确定函数真正的值。

var addEvent = (function() {
	if(window.addEventListener) {
		return function(element, type, handler) {
			element.addEventListener(type, handler, false);
		}
	} else if(window.attachEvent) {
		return function(element, type, handler) {
			window.attachEvent('on' + type, handler);
		}
	} else {
		return function(element, type, handler) {
			element['on' + type] = handler;
		}
	}
}())

高阶函数

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

看到这里,大家都应该意识到了,平时使用过很多高阶函数。数组的一些高阶函数使用得尤为频繁。

[1, 2, 3, 4].forEach(function(item, index, arr) {
	console.log(item, index, arr);
});
[1, 2, 3, 4].map(item => `aa${item}`);

可以发现,传入forEachmap的就是一个函数。我们自己也可以封装一些复用的高阶函数。

我们知道Math.max可以求出参数列表中最大的值。然而很多时候,我们需要处理的数据并不是1,2,3,4这么简单,而是对象数组。

假设有这么一个需求,存在一个数组,数组元素都是表示人的对象,我们想从数组中选出年纪最大的人。

这个时候,就需要一个高阶函数来完成。

/**
/**
 * 根据求值条件判断数组中最大的项
 * @param {Array} arr 数组
 * @param {String|Function} iteratee 返回一个求值表达式,可以根据对象属性的值求出最大项,比如item.age。也可以通过自定义函数返回求值表达式。
 */
function maxBy(arr, interatee) {
	let values = [];
	if(typeof interatee === 'string'){
		values = arr.map(item => item[interatee]);
	} else if(typeof interatee == 'function') {
		values = arr.map((item, index) => {
			return interatee(item, index, arr);
		});
	}
	const maxOne = Math.max(...values);
	const index = values.findIndex(v => v === maxOne);
	return arr[index];
}

利用这个高阶函数,我们就可以求出数组中年纪最大的人。

var list = [
	{name: 'li', age: 10},
	{name: 'wang', age: 20},
	{name: 'zhang', age: 12}
];
var maxItem = maxBy(list, 'age');

我们甚至可以定义更复杂的求值规则,比如我们需要根据一个字符串类型的属性来判定优先级。这个时候,就必须传一个自定义的函数作为参数了。

const list = [
  {name: '小明', priority: 'middle'},
  {name: '小红', priority: 'low'},
  {name: '小李', priority: 'high'}
]
const maxItem = maxBy(list, function(item) {
  const { priority } = item
  const priorityValue = priority === 'low' ? 1 : priority === 'middle' ? 2 : priority === 'high' ? 3 : 0
  return priorityValue;
});

maxBy接受的参数最终都应该能转化为一个Math.max可度量的值,否则就灭有可比性了。

要理解这样的高阶函数,我们可以认为传给高阶函数的函数就是一个中间件,他把数据预处理好了,然后再转交给高阶函数继续运算

柯里化

说柯里化之前,先抛出一个疑问,如何实现一个add函数,使得一个add函数可以灵活调用和传参,支持下面的调用示例呢?

add(1, 2, 3); // 6
add(1); // 1
add(1)(2); // 3
add(1, 2)(3); // 6
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10

要解答这样的疑问,还是要先明白什么是柯里化。

在计算机科学中,柯里化(Currying)是把接受多个参数的函数,变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

这段解释看着还是挺懵逼的,不如举个例子:

本来有这么一个求和函数dynamicAdd(),接受任意个参数。

function dynamicAdd() {
	return [...arguments].reduce((prev, curr) => {
		return prev + curr;
	}, 0);
}

现在需要通过柯里化把它变成一个新的函数,这个新的函数预置了第一个参数,并且可以在调用时继续传入剩余参数。

看到这,我感觉有点似曾相识,预设参数的特性与bind很相像。那么我们不如用bind的思路来实现。

function curry(fn, firstArg) {
	// 返回一个新函数
	return function() {
		// 新函数调用时会继续传参
		var restArgs = [].splice.call(arguments);
		// 参数合并,通过apply调用原函数
		return fn.apply(this, [firstArg, ...restArgs]);
	}
}

接着我们通过一些例子来感受一下柯里化。

// 柯里化,预设参数10
var add10 = curry(dynamicAdd, 10);
add10(5); // 15

var add20 = curry(dynamicAdd, 20);
add20(5); // 25

// 也可以对一个已经柯里化的函数add10继续柯里化,此时预置参数10即可
var anotherAdd20 = curry(add10, 10);
anotherAdd20(5); // 25

可以发现,柯里化是在一个函数的基础上进行变换,得到一个新的预置了参数的函数。最后在调用新函数是,实际上还是会调用柯里化前的原函数

并且柯里化得到的新函数可以继续被柯里化,像俄罗斯套娃的感觉。

实际使用时也会出现柯里化的变体,不局限于只预置一个参数

function curry(fn) {
	// 保存预置参数
	var presetArgs = [].slice.call(arguments, 1)
	// 返回一个新函数
	return function() {
		// 新函数调用时会继续传参
		var restArgs = [].splice.call(arguments);
		// 参数合并,通过apply调用原函数
		return fn.apply(this, [...presetArgs, ...restArgs]);
	}
}

其实Function.protoype.bind就是一个柯里化的实现。不仅如此,很多流行的库都大量使用了柯里化的思想。

实际应用中,被柯里化的原函数的参数可能是定长的,也可能是不定长的。

参数定长的柯里化

假设存在一个原函数fn,fn接受三个参数a,b,c,那么函数fn最多被柯里化三次(有效地绑定参数算一次)。

function fn(a, b, c) {
	return a + b + c;
}
var c1 = curry(fn, 1);
var c2 = curry(c1, 2);
var c3 = curry(c2, 3);
c3(); // 6
// 再次柯里化也没有意义,原函数只需要三个参数
var c4 = curry(c3, 4);
c4();

也就是说,我们可以通过柯里化缓存的参数数量,来判断是否达到了执行时机。那么我们就得到了一个柯里化的通用模式。

function curry(fn) {
	// 获取原函数的参数长度
	const argLen = fn.length;
	// 保存预置参数
	const presetArgs = [].slice.call(arguments, 1);
	// 返回一个新函数
	return function() {
		// 新函数调用时会继续传参
		const restArgs = [].slice.call(arguments);
		const allArgs = [...presetArgs, ...restArgs];
		if(allArgs.length >= argLen) {
			// 如果参数够了,就执行原函数
			return fn.apply(this, allArgs);
		} else {
			// 否则继续柯里化
			return curry.call(null, fn, ...allArgs);
		}
	}
}

这样一来,我们的写法就可以支持以下形式。

function fn(a, b, c) {
	return a + b + c;
}
var curried = curry(fn);
curried(1, 2, 3); // 6
curried(1, 2)(3); // 6
curried(1)(2, 3); // 6
curried(1)(2)(3); // 6
curried(7)(8)(9); // 24

参数不定长的柯里化

解决了上面的问题,我们难免会问自己,假设原函数的参数不定长呢,这种情况如和柯里化?

首先,我们需要理解参数不定长是指函数声明时不约定具体的参数,而在函数体中通过arguments获取实参,然后进行运算。就像下面这种。

function dynamicAdd() {
	return [...arguments].reduce((prev, curr) => {
		return prev + curr;
	}, 0);
}

回到最开始的问题,怎么支持下面的所有调用形式?

add(1, 2, 3); // 6
add(1); // 1
add(1)(2); // 3
add(1, 2)(3); // 6
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10

思考了一阵,我发现在参数不定长的情况下,要同时支持1~N次调用还是挺难的。add(1)在一次调用后我们可以返回一个值,但它也可以作为函数接着调用add(1)(2),甚至可以继续add(1)(2)(3)。那么我们实现add函数时,到底是返回一个函数,还是返回一个值呢?这让人挺犯难的,我也不能预测这个函数将如何被调用啊。

而且我们可以拿上面的成果来验证下:

curried(1)(2)(3)(4);

运行上面的代码会报错:Uncaught TypeError: curried(...)(...)(...) is not a function,因为执行到curried(1)(2)(3),结果就不是一个函数了,而是一个值,一个值当然是不能作为函数继续执行的。

所以如果要支持参数不定长的场景,已经柯里化的函数在执行完毕时不能返回一个值,只能返回一个函数,同时要让JS引擎在解析得到的这个结果时,能求出我们预期的值。

我们实现的curry应该满足:

  1. 经curry处理,得到一个新函数,这一点不变。

    // curry是一个函数
    var curried = curry(add);
    
  2. 新函数执行后仍然返回一个结果函数。

    // curried10也是一个函数
    var curried10 = curried(10);
    var curried30 = curried10(20);
    
  3. 结果函数可以被JS引擎解析,得到一个预期的值。

    curried10; // 10
    

好,关键点在于3,如何让Javascript引擎按我们的预期进行解析,这就回到Javascript基础了。在解析一个函数的原始值时,会用到toString

我们知道,console.log(fn)可以把函数fn的源码输出,如下所示:

console.log(fn)
ƒ fn(a, b, c) {
  return a + b + c;
}

那么我们只要重写toString,就可以巧妙地实现我们的需求了。

function curry(fn) {
	// 保存预置参数
	const presetArgs = [].slice.call(arguments, 1);
	// 返回一个新函数
	function curried() {
		// 新函数调用时会继续传参
		const restArgs = [].slice.call(arguments);
		const allArgs = [...presetArgs, ...restArgs];
		return curry.call(null, fn, ...allArgs);
	}
	curried.toString = function() {
		return fn.apply(null, presetArgs);
	}
	return curried;
}

function dynamicAdd() {
  return [...arguments].reduce((prev, curr) => {
    return prev + curr
  }, 0)
}
var add = curry(dynamicAdd);
add(1)(2)(3)(4) // 10
add(1, 2)(3, 4)(5, 6) // 21

柯里化总结

柯里化是一种函数式编程思想,实际上在项目中可能用得少,或者说用得不深入,但是如果你掌握了这种思想,也许在未来的某个时间点,你会用得上!

大概来说,柯里化有如下特点:

  • 简洁代码:柯里化应用在较复杂的场景中,有简洁代码,可读性高的优点。
  • 参数复用:公共的参数已经通过柯里化预置了。
  • 延迟执行:柯里化时只是返回一个预置参数的新函数,并没有立刻执行,实际上在满足条件后才会执行。
  • 管道式流水线编程:利于使用函数组装管道式的流水线工序,不污染原函数。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值