本文深入解析JavaScript中的核心函数模式。通过回调函数实现异步处理,利用配置对象简化参数传递,借助返回函数构建状态计数器。重点讲解偏应用与柯里化的原理及通用实现,展示如何将多参数函数转化为单参数链式调用,提升代码复用性与灵活性。
引言
引言
本文主要探讨JavaScript中Function模式的高级应用技巧(上篇)。利用Function的特性,开发者可以构建出极具创意且高效的代码结构。本文将重点涵盖回调模式、配置对象模式、返回函数、偏应用(Partial Application)以及柯里化(Currying)这五大核心主题。
回调函数模式
回调函数
在JavaScript中,当函数A被作为另一个函数B的参数传入时,函数A即被称为回调函数。这意味着函数A可以在函数B的执行周期内的任意阶段(开始、中间或结束时)被调用执行。
例如,假设存在一个用于生成节点对象的函数。
相关代码如下:
var complexComputation = function () { /* 内部处理,并返回一个node*/};接着定义一个名为findNodes的函数,用于查找所有节点,并通过回调函数执行后续逻辑。
相关代码如下:
var findNodes = function (callback) {
var nodes = [];
var node = complexComputation();
// 如果回调函数可用,则执行它
if (typeof callback === "function") {
callback(node);
}
nodes.push(node);
return nodes;
};
关于回调函数的定义,我们可以预先定义好一个处理函数来复用:
相关代码如下:
// 定义callback
var hide = function (node) {
node.style.display = "none";
};
// 查找node,然后隐藏所有的node
var hiddenNodes = findNodes(hide);
此外,也可以直接在调用时通过匿名函数定义回调,示例如下:
相关代码如下:
// 使用匿名函数定义callback
var blockNodes = findNodes(function (node) {
node.style.display = 'block';
});日常开发中使用频率最高的场景之一,莫过于jQuery的ajax方法调用。通过在done或fail回调中定义处理逻辑,可以在ajax请求成功或失败时执行进一步操作。以下代码基于jQuery 1.8版本:
相关代码如下:
var menuId = $("ul.nav").first().attr("id");
var request = $.ajax({
url: "script.php",
type: "POST",
data: {id : menuId},
dataType: "html"
});
//调用成功时的回调处理
request.done(function(msg) {
$("#log").html( msg );
});
//调用失败时的回调处理
request.fail(function(jqXHR, textStatus) {
alert( "Request failed: " + textStatus );
});
配置对象模式
配置对象
如果一个函数(或方法)仅接收一个参数,且该参数为对象字面量,这种模式被称为配置对象模式。例如以下代码:
相关代码如下:
var conf = {
username:"shichuan",
first:"Chuan",
last:"Shi"
};
addPerson(conf);在addPerson函数内部,可以灵活使用conf对象的属性值,这通常用于初始化工作。例如jQuery中的ajaxSetup方法正是采用这种方式实现:
相关代码如下:
// 事先设置好初始值
$.ajaxSetup({
url: "/xmlhttp/",
global: false,
type: "POST"
});
// 然后再调用
$.ajax({ data: myData });
此外,许多jQuery插件也采用这种传参形式。当未传递参数时,函数将使用默认值。
返回函数与闭包
返回函数
返回函数模式指的是一个函数的返回值是另一个函数,或者根据特定条件动态创建的新函数。示例代码如下:
相关代码如下:
var setup = function () {
console.log(1);
return function () {
console.log(2);
};
};
// 调用setup 函数
var my = setup(); // 输出 1
my(); // 输出 2
// 或者直接调用也可
setup()();利用闭包特性,可以在setup函数内部维护一个私有计数器,每次调用时递增计数器。代码如下:
相关代码如下:
var setup = function () {
var count = 0;
return function () {
return ++count;
};
};
// 用法
var next = setup();
next(); // 返回 1
next(); // 返回 2
next(); // 返回 3
偏应用函数详解
偏应用
偏应用(Partial Application)是指将参数传入的过程分离。在某些场景下,一系列操作可能始终包含相同的一个或几个参数。此时,我们可以先定义一个偏函数,随后再执行该函数(传入剩余的不同参数)。
举例如下:
相关代码如下:
var partialAny = (function (aps) {
// 该函数是自执行函数表达式的结果,并赋值给了partialAny变量
function func(fn) {
var argsOrig = aps.call(arguments, 1);
return function () {
var args = [],
argsPartial = aps.call(arguments),
i = 0;
// 遍历所有原始参数集,
// 如果参数是partialAny._ 占位符,则使用下一个函数参数对应的值
// 否则使用原始参数里的值
for (; i < argsOrig.length; i++) {
args[i] = argsOrig[i] === func._
? argsPartial.shift()
: argsOrig[i];
}
// 如果有额外参数,则添加到尾部
return fn.apply(this, args.concat(argsPartial));
};
}
// 用于占位符设置
func._ = {};
return func;
})(Array.prototype.slice);
使用方式如下:
相关代码如下:
// 定义处理函数
function hex(r, g, b) {
return '#' + r + g + b;
}
//定义偏函数, 将hex的第一个参数r作为不变的参数值ff
var redMax = partialAny(hex, 'ff', partialAny._, partialAny._);
// 新函数redMax的调用方式如下,只需传入2个参数:
console.log(redMax('11', '22')); // "#ff1122"如果觉得partialAny._过长,可以使用__代替。
相关代码如下:
var __ = partialAny._;
var greenMax = partialAny(hex, __, 'ff');
console.log(greenMax('33', '44'));
var blueMax = partialAny(hex, __, __, 'ff');
console.log(blueMax('55', '66'));
var magentaMax = partialAny(hex, 'ff', __, 'ff');
console.log(magentaMax('77'));这样使用,代码更加简洁。
柯里化与通用实现
柯里化
柯里化(Currying)是函数式编程的重要特性,它将多个参数的处理转化为单个参数的处理,类似于链式调用。
以一个简单的add函数为例:
相关代码如下:
function add(x, y) {
var oldx = x, oldy = y;
if (typeof oldy === "undefined") { // partial
return function (newy) {
return oldx + newy;
}
}
return x + y;
}这样调用方式变得多样化,例如:
相关代码如下:
// 测试
typeof add(5); // "function"
add(3)(4); // 7
// 也可以这样调用
var add2000 = add(2000);
add2000(10); // 2010接下来,我们定义一个通用的currying函数:
相关代码如下:
// 第一个参数为要应用的function,第二个参数是需要传入的最少参数个数
function curry(func, minArgs) {
if (minArgs == undefined) {
minArgs = 1;
}
function funcWithArgsFrozen(frozenargs) {
return function () {
// 优化处理,如果调用时没有参数,返回该函数本身
var args = Array.prototype.slice.call(arguments);
var newArgs = frozenargs.concat(args);
if (newArgs.length >= minArgs) {
return func.apply(this, newArgs);
} else {
return funcWithArgsFrozen(newArgs);
}
};
}
return funcWithArgsFrozen([]);
}
借助此工具,我们可以灵活定义业务逻辑,例如定义加法:
相关代码如下:
var plus = curry(function () {
var result = 0;
for (var i = 0; i < arguments.length; ++i) {
result += arguments[i];
}
return result;
}, 2);使用方式多种多样:
相关代码如下:
plus(3, 2) // 正常调用
plus(3) // 偏应用,返回一个函数(返回值为3+参数值)
plus(3)(2) // 完整应用(返回5)
plus()(3)()()(2) // 返回 5
plus(3, 2, 4, 5) // 可以接收多个参数
plus(3)(2, 3, 5) // 同理以下是减法的示例:
相关代码如下:
var minus = curry(function (x) {
var result = x;
for (var i = 1; i < arguments.length; ++i) {
result -= arguments[i];
}
return result;
}, 2);若想交换参数顺序,可以这样定义:
相关代码如下:
var flip = curry(function (func) {
return curry(function (a, b) {
return func(b, a);
}, 2);
});
});
总结
总结
JavaScript中的Function具备诸多特殊功效,利用闭包及arguments参数特性可实现多种技巧。下一篇我们将继续介绍利用Function进行初始化的技巧。