函数的部分执行
语法
多参数的函数有时需要绑定其中的一个或多个参数,然后返回一个新函数。
function add(x, y) { return x + y; }
function add7(x) { return x + 7; }
上面代码中,add7
函数其实是add
函数的一个特殊版本,通过将一个参数绑定为7
,就可以从add
得到add7
。
// bind 方法
const add7 = add.bind(null, 7);
// 箭头函数
const add7 = x => add(x, 7);
上面两种写法都有些冗余。其中,bind
方法的局限更加明显,它必须提供this
,并且只能从前到后一个个绑定参数,无法只绑定非头部的参数。
现在有一个提案,使得绑定参数并返回一个新函数更加容易。这叫做函数的部分执行(partial application)。
const add = (x, y) => x + y;
const addOne = add(1, ?);
const maxGreaterThanZero = Math.max(0, ...);
根据新提案,?
是单个参数的占位符,...
是多个参数的占位符。以下的形式都属于函数的部分执行。
f(x, ?)
f(x, ...)
f(?, x)
f(..., x)
f(?, x, ?)
f(..., x, ...)
?
和...
只能出现在函数的调用之中,并且会返回一个新函数。
const g = f(?, 1, ...);
// 等同于
const g = (x, ...y) => f(x, 1, ...y);
函数的部分执行,也可以用于对象的方法。
let obj = {
f(x, y) { return x + y; },
};
const g = obj.f(?, 3);
g(1) // 4
注意点
函数的部分执行有一些特别注意的地方。
(1)函数的部分执行是基于原函数的。如果原函数发生变化,部分执行生成的新函数也会立即反映这种变化。
let f = (x, y) => x + y;
const g = f(?, 3);
g(1); // 4
// 替换函数 f
f = (x, y) => x * y;
g(1); // 3
上面代码中,定义了函数的部分执行以后,更换原函数会立即影响到新函数。
(2)如果预先提供的那个值是一个表达式,那么这个表达式并不会在定义时求值,而是在每次调用时求值。
let a = 3;
const f = (x, y) => x + y;
const g = f(?, a);
g(1); // 4
// 改变 a 的值
a = 10;
g(1); // 11
上面代码中,预先提供的参数是变量a
,那么每次调用函数g
的时候,才会对a
进行求值。
(3)如果新函数的参数多于占位符的数量,那么多余的参数将被忽略。
const f = (x, ...y) => [x, ...y];
const g = f(?, 1);
g(2, 3, 4); // [2, 1]
上面代码中,函数g
只有一个占位符,也就意味着它只能接受一个参数,多余的参数都会被忽略。
写成下面这样,多余的参数就没有问题。
const f = (x, ...y) => [x, ...y];
const g = f(?, 1, ...);
g(2, 3, 4); // [2, 1, 3, 4];
(4)...
只会被采集一次,如果函数的部分执行使用了多个...
,那么每个...
的值都将相同。
const f = (...x) => x;
const g = f(..., 9, ...);
g(1, 2, 3); // [1, 2, 3, 9, 1, 2, 3]
上面代码中,g
定义了两个...
占位符,真正执行的时候,它们的值是一样的。