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


Posted in Javascript onMarch 04, 2015

介绍

本篇主要是介绍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进行初始化的技巧。

Javascript 相关文章推荐
自制轻量级仿jQuery.boxy对话框插件代码
Oct 26 Javascript
javascript跨域刷新实现代码
Jan 01 Javascript
JQUERY的属性选择符和自定义选择符使用方法(二)
Apr 07 Javascript
JS分页控件 可用于无刷新分页
Jul 23 Javascript
jQuery事件绑定和委托实例
Nov 25 Javascript
jquery序列化方法实例分析
Jun 10 Javascript
JavaScript中Function函数与Object对象的关系
Dec 17 Javascript
jQuery插件HighCharts绘制简单2D折线图效果示例【附demo源码】
Mar 21 jQuery
详解Angular2 之 结构型指令
Jun 21 Javascript
JavaScript反射与依赖注入实例详解
May 29 Javascript
vue在线动态切换主题色方案
Mar 26 Javascript
vscode中使用npm安装babel的方法
Aug 02 Javascript
js实现百度联盟中一款不错的图片切换效果完整实例
Mar 04 #Javascript
jQuery中大家不太了解的几个方法
Mar 04 #Javascript
深入理解JavaScript系列(48):对象创建模式(下篇)
Mar 04 #Javascript
js计算德州扑克牌面值的方法
Mar 04 #Javascript
深入理解JavaScript系列(47):对象创建模式(上篇)
Mar 04 #Javascript
深入理解JavaScript系列(46):代码复用模式(推荐篇)详解
Mar 04 #Javascript
深入理解JavaScript系列(45):代码复用模式(避免篇)详解
Mar 04 #Javascript
You might like
通过具体程序来理解PHP里面的抽象类
2010/01/28 PHP
YII视图整合kindeditor扩展的方法
2016/07/13 PHP
thinkPHP统计排行与分页显示功能示例
2016/12/02 PHP
给事件响应函数传参数的四种方式小结
2013/12/05 Javascript
jQuery选择器源码解读(三):tokenize方法
2015/03/31 Javascript
Javascript中typeof 用法小结
2015/05/12 Javascript
javascript实现的简单计时器
2015/07/19 Javascript
纯js和css完成贪吃蛇小游戏demo
2016/09/01 Javascript
Angular ng-repeat遍历渲染完页面后执行其他操作详细介绍
2016/12/13 Javascript
vue this.reload 方法 配置
2018/09/12 Javascript
js实现多图和单图上传显示
2019/12/18 Javascript
React Native中ScrollView组件轮播图与ListView渲染列表组件用法实例分析
2020/01/06 Javascript
Vue props中Object和Array设置默认值操作
2020/07/30 Javascript
Python实现的金山快盘的签到程序
2013/01/17 Python
Python多进程并发与多线程并发编程实例总结
2018/02/08 Python
Python3实现爬虫爬取赶集网列表功能【基于request和BeautifulSoup模块】
2018/12/05 Python
详解python读取和输出到txt
2019/03/29 Python
Django使用模板后无法找到静态资源文件问题解决
2019/07/19 Python
Python3 实现爬取网站下所有URL方式
2020/01/16 Python
django实现更改数据库某个字段以及字段段内数据
2020/03/31 Python
Python unittest单元测试框架及断言方法
2020/04/15 Python
解决matplotlib.pyplot在Jupyter notebook中不显示图像问题
2020/04/22 Python
Python正则表达式如何匹配中文
2020/05/27 Python
Python astype(np.float)函数使用方法解析
2020/06/08 Python
浅析python中的del用法
2020/09/02 Python
HTML5为输入框添加语音输入功能的实现方法
2017/02/06 HTML / CSS
将SVG图引入到HTML页面的实现
2019/09/20 HTML / CSS
巴西食品补充剂在线零售商:Músculos na Web
2017/08/07 全球购物
大学系主任推荐信范文
2013/12/24 职场文书
2013的个人自我评价
2013/12/26 职场文书
社会实践心得体会
2014/01/03 职场文书
遗产继承公证书
2014/04/09 职场文书
525心理健康活动总结
2015/05/08 职场文书
小学教师教学随笔
2015/08/14 职场文书
合作意向书范本
2019/04/17 职场文书
简历自我评价范文
2019/04/24 职场文书