深入理解JavaScript系列(49):Function模式(上篇)

介绍

本篇主要是介绍Function方面使用的一些技巧(上篇),利用Function特性可以编写出很多非常有意思的代码,本篇主要包括:回调模式、配置对象、返回函数、分布程序、柯里化(Currying)。

回调函数

在JavaScript中,当一个函数A作为另外一个函数B的其中一个参数时,则函数A称为回调函数,即A可以在函数B的周期内执行(开始、中间、结束时均可)。

举例来说,有一个函数用于生成node

复制代码 代码如下:

var complexComputation = function () { /* 内部处理,并返回一个node*/};

有一个findNodes函数声明用于查找所有的节点,然后通过callback回调进行执行代码。

复制代码 代码如下:

var findNodes = function (callback) {
var nodes = [];

var node = complexComputation();

// 如果回调函数可用,则执行它
if (typeof callback === "function") {
callback(node);
}

nodes.push(node);
return nodes;
};

关于callback的定义,我们可以事先定义好来用:

复制代码 代码如下:

// 定义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/faild上定义callback,以便在ajax调用成功或者失败的时候做进一步处理,代码如下(本代码基于jquery1.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

偏应用

这里的偏应用,其实是将参数的传入工作分开进行,在有的时候一系列的操作可能会有某一个或几个参数始终完全一样,那么我们就可以先定义一个偏函数,然后再去执行这个函数(执行时传入剩余的不同参数)。

举个例子,代码如下:

复制代码 代码如下:

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

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进行初始化的技巧。

时间: 2015-03-02

深入理解JavaScript系列(47):对象创建模式(上篇)

介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度.一般我们在进行对象层级定义的时候,经常是这样的: 复制代码 代码如下: var app = app || {}; app.moduleA = app.moduleA || {}; app.moduleA.subModule = app.moduleA.subModule || {}; app.mod

深入理解JavaScript系列(3) 全面解析Module模式

简介 Module模式是JavaScript编程中一个非常通用的模式,一般情况下,大家都知道基本用法,本文尝试着给大家更多该模式的高级使用方式. 首先我们来看看Module模式的基本特征: 模块化,可重用 封装了变量和function,和全局的namaspace不接触,松耦合 只暴露可用public的方法,其它私有方法全部隐藏 关于Module模式,最早是由YUI的成员Eric Miraglia在4年前提出了这个概念,我们将从一个简单的例子来解释一下基本的用法(如果你已经非常熟悉了,请忽略这一节

深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点

具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以帮助你写出更好的,更易于理解和维护的代码,这些代码在几个月或是几年之后再回过头看看也是会觉得很自豪的. 书写可维护的代码(Writing Maintainable Code ) 软件bug的修复是昂贵的,并且

深入理解JavaScript系列(2) 揭秘命名函数表达式

前言 网上还没用发现有人对命名函数表达式进去重复深入的讨论,正因为如此,网上出现了各种各样的误解,本文将从原理和实践两个方面来探讨JavaScript关于命名函数表达式的优缺点. 简单的说,命名函数表达式只有一个用户,那就是在Debug或者Profiler分析的时候来描述函数的名称,也可以使用函数名实现递归,但很快你就会发现其实是不切实际的.当然,如果你不关注调试,那就没什么可担心的了,否则,如果你想了解兼容性方面的东西的话,你还是应该继续往下看看. 我们先开始看看,什么叫函数表达式,然后再说一

深入理解JavaScript系列(4) 立即调用的函数表达式

前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下"自执行"这个叫法,本文对这个功能的叫法也不一定完全对,主要是看个人如何理解,因为有的人说立即调用,有的人说自动执行,所以你完全可以按照你自己的理解来取一个名字,不过我听很多人都叫它为"自执行",但作者后面说了很多,来说服大家称呼为"立即调用的函数表达式". 本文英文原文地址:http://benalman

理解javascript中的MVC模式

MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程序的业务逻辑相关的数据以及对数据处理的方法.模型有对数据直接访问的权利.模型不依赖 "视图" 和 "控制器", 也就是说 模型它不关心页面如何显示及如何被操作. 视图:视图层最主要的是监听模型层上的数据改变,并且实时的更新html页面.当然也包括一些事件的注册或者ajax请求操作(发布事件),都是放在视图

深入理解JavaScript系列(50):Function模式(下篇)

介绍 本篇我们介绍的一些模式称为初始化模式和性能模式,主要是用在初始化以及提高性能方面,一些模式之前已经提到过,这里只是做一下总结. 立即执行的函数 在本系列第4篇的<立即调用的函数表达式>中,我们已经对类似的函数进行过详细的描述,这里我们只是再举两个简单的例子做一下总结. 复制代码 代码如下: // 声明完函数以后,立即执行该函数 (function () {     console.log('watch out!'); } ()); //这种方式声明的函数,也可以立即执行 !functio

深入理解JavaScript系列(36):设计模式之中介者模式详解

介绍 中介者模式(Mediator),用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互. 主要内容来自:http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#mediatorpatternjavascript 正文 软件开发中,中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信.一般,如果系统有很多子模块需要直接沟通,

深入理解JavaScript系列(38):设计模式之职责链模式详解

介绍 职责链模式(Chain of responsibility)是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止. 也就是说,请求以后,从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者.提交请求的对象并不明确知道哪一个对象将会处理它--也就是该请求有一个隐式的接受者(implicit receiver).根据运行时刻,任一候选者都可以响应相应的请求,候选者的数目是任意