这篇,我们仍旧继续学习函数。

二、回调模式

  函数都是对象,这表示它们可以作为参数传递给其它函数。

function writeCode(callback) {
// 执行一些事务...
callback();
// ...
}
function introduceBugs() {
// ...引入漏洞
} writeCode(introduceBugs);

  请注意introduceBugs()作为参数传递给writeCode()时是不带括号的。括号表示要执行函数,而在这种情况下,我们仅需要传递该函数的应用,而让writeCode()在适当的时候来执行它(也就是说,返回以后调用)。

回调示例

  我们来看个例子,假设我们需要一个通用函数执行一些复杂逻辑后返回一个大块数据的结果。

var findNodes = function() {
var i = 100000,// 大而繁重的循环
nodes = [],// 存储结果
found;// 找到了下一个节点
while(i) {
i -= 1;
// 这里是复杂的逻辑...
nodes.push(found);
}
return nodes;
}

  保持函数的通用性并且使其返回一个DOM节点数组,而不对实际元素做任何处理,这是一个非常好的思想。修改节点的逻辑,可以在其他函数中实现。

var hide = function (nodes){
var i = 0,max = nodes.length;
for(;i < max;i += 1) {
nodes[i].style.display = "none";
}
}; //执行该函数
hide(findNodes());

  上面的实现方式是低效的。因为hide()必须再次遍历由findNodes()返回的数组节点。如果能避免这种循环,并且只要在findNodes()中便可隐藏节点,那么这将是高效的实现方式。但是如果在findNodes()中实现隐藏逻辑,由于检索和修改逻辑耦合,那么它不再是一个通用函数。对这种问题的解决方法是采用回调模式,可以将节点隐藏逻辑以回调函数方式传递给findNodes()并委托其执行:

    // 重构findNodes()以接受一个回调函数
var findNodes = function(callback) {
var i = 100000,// 大而繁重的循环
nodes = [],// 存储结果
found;// 找到了下一个节点 // 检查回调函数是否为可调用的
if(typeof callback !== "function") {
callback = false;
} while(i) {
i -= 1;
// 这里是复杂的逻辑...
// 现在运行回调函数
if(callback){
callback(found);
}
nodes.push(found);
}
return nodes;
}

  这是一种很直接的实现方法。findNodes()执行的唯一额外任务是,检查是否提供了可选回调函数,如果存在就执行。其中,回调函数是可选的,所以重构后的findNodes()仍然可以像以前一样使用。

  现在,hide()的实现要简单很多,因为他不需要遍历所有的节点:

var hide = function (node){
node.style.display = "none";
}; //执行该函数
findNodes(hide);

  如上所示,回调函数可以是一个已有的函数,也可以是一个匿名函数,可以在调用主函数时创建它。例如,下面的代码展示了如何使用同样的findNodes()函数以显示节点:

//传递一个匿名函数
findNodes(function(node) {
node.style.display = "block";
});

回调与作用域

  在前面的例子中,回调执行的语句部分如下:

callback(parameters);

  虽然在大多数情况下,这种方法都是简单而有效的,但经常存在一些场景,其回调并不是一次性的匿名函数或全局函数,而是对象的方法。如果该回调方法使用this来引用他所属的对象,这可能会导致意想不到的行为发生。

// 重构findNodes()以接受一个回调函数
var findNodes = function(callback) {
var i = 100000,// 大而繁重的循环
nodes = [],// 存储结果
found;// 找到了下一个节点 // 检查回调函数是否为可调用的
if(typeof callback !== "function") {
callback = false;
} while(i) {
i -= 1;
// 这里是复杂的逻辑...
// 现在运行回调函数
if(callback){
callback(found);
}
nodes.push(found);
}
return nodes;
}
// 上面是我复制的 var myapp = {};
myapp.color = "green";
myapp.paint = function(node) {
node.style.color = this.color;
};
findNodes(myapp.paint)

  如果你真的这样做了,那么他肯定不会按照预期那样执行。这是因为this.color没有被定义。因为findNodes()是一个全局函数,所以,this指向了全局对象。类似的,如果findNodes()是一个名为dom的对象的方法(dom.findNodes()),那么回调内部的this将指向dom,而不是预期的myapp。

  对这个问题的解决方案是传递回调函数,并且另外还传递该回调函数所属的对象:

findNodes(myapp.paint,myapp);

  然后,当然还需要修改findNodes()以绑定所传递进入的对象:

var findNodes = function(callback,callback_obj) {
// ...
if(typeof callback === 'function') {
callback.call(callback_obj,found);
}
// ...
};

  我们发现应用了call(),后面会详细的讲解call()和apply()的相关内容。

  上面的方法,可以稍加优化,无需两次输入该对象的名称:

findNodes('paint',myapp);

  然后,findNodes()内部会有一点小小的变动:

var findNodes = function(callback,callback_obj) {
// 我们加了一个额外的判断
if(typeof callback === 'string') {
callback = callback_obj[callback];
}
// ...
if(typeof callback === 'function') {
callback.call(callback_obj,found);
}
// ...
};

异步事件监听

  回调模式有许多常见用途,比如,当附加一个事件监听器到页面上的一个元素时,实际上提供了一个回调函数指针,该函数将会在时间发生时被调用。下面是一个简单的例子,展示了当监听到文档点击事件时如何传递回调函数console.log()。

document.addEventListener("click", console.log, false);

  大多数的客户端浏览器编程都是事件驱动的。比如页面加载完成会触发load事件、用户与页面交互时,将会触发比如click、keypress、mouseover、mousemove等。JavaScript特别适合于事件驱动编程,因为回调模式支持程序以异步方式运行,也就是说,可以乱序方式运行。

超时

  使用回调模式的另一个例子是,当使用浏览器的window对象所提供的超时方法:setTimeout()和setInterval()。这些方法也会接受并执行回调函数:

var thePlotThickens = function() {
console.log("500ms later...");
}; setTimeout(thePlotThickens,500);

  再次强调,这里函数thePlotThickens是如何以变量方式传递的,传递该函数时并没有带括号,因为并不想立即执行该函数,而只是想指向该函数以便setTimeout()在以后使用。

库中的回调模式

  回调模式是一种简单而又强大的模式,当设计一个库时他可以派上用场。进入软件库的代码应该尽可能地是通用和可服用的代码。而回调可以帮助实现这种通用化。不需要预测和实现能想到的每一项功能,因为这样会迅速使库膨胀,而绝大多数用户永远不会需要其中大量的功能。相反,可以专注于核心功能并提供“挂钩”形式的回调函数,这将使您很容易的构建、扩展,以及自定义库方法。

三、返回函数

  函数也是对象,因此它们也可以被用做返回值。这表示一个函数并不需要以某种数据值或数据数组作为执行结果返回。函数可以返回另一个更专门的函数,也可以按需创建另一个函数,这取决于其输入。

  

var setup = function () {
alert(1);
return function() {
alert(2);
};
}; // 使用setup函数
var my = setup(); // alerts 1
my(); // alerts 2 // 由于setup()包装了返回函数,它创建了一个闭包,可以使用这个闭包存储一些私有数据,
// 而这些数据仅可被该返回函数访问,但外部代码却无法访问。
// 下面是一个计数器的例子,每次当调用该函数时,它会产生一个递增的值: var setup1 = function() {
var count = 0;
return function () {
return (count += 1);
};
};
// 用法
var next = setup1();
console.log(next());
console.log(next());
console.log(next());

  

  这篇主要介绍了回调函数和返回函数。这两种在实际的开发中都十分有价值。一定要认真看哦。下一篇,我们来学习下自定义函数以及即时函数。

JavaScript 模式》读书笔记(4)— 函数2的更多相关文章

  1. JavaScript模式读书笔记 第4章 函数

    2014年11月10日 1.JavaScript函数具有两个特点: 函数是第一类对象    函数能够提供作用域         函数即对象,表现为:         -1,函数能够在执行时动态创建,也 ...

  2. JavaScript模式读书笔记 文章3章 文字和构造

    1.对象字面量     -1.Javascript中所创建的自己定义对象在任务时候都是可变的.能够从一个空对象開始,依据须要添加函数.对象字面量模式能够使我们在创建对象的时候向其加入函数.       ...

  3. 《你不知道的javascript》读书笔记2

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...

  4. 《编写可维护的javascript》读书笔记(中)——编程实践

    上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...

  5. C语言深度解剖读书笔记(6.函数的核心)

    对于本节的函数内容其实就没什么难点了,但是对于函数这节又涉及到了顺序点的问题,我觉得可以还是忽略吧. 本节知识点: 1.函数中的顺序点:f(k,k++);  这样的问题大多跟编译器有关,不要去刻意追求 ...

  6. Javascript & JQuery读书笔记

    Hi All, 分享一下我学JS & JQuery的读书笔记: JS的3个不足:复杂的文档对象模型(DOM),不一致的浏览器的实现和便捷的开发,调试工具的缺乏. Jquery的选择器 a. 基 ...

  7. 《你不知道的javascript》读书笔记1

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...

  8. JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)

    this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...

  9. JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)

    前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...

  10. SQL反模式读书笔记思维导图

    在写SQL过程以及设计数据表的过程中,我们经常会走一些弯路,会做一些错误的设计.<SQL反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...

随机推荐

  1. 分析Java中的length和length()

    在不适用任何带有自动补全功能的IDE的情况下,我们怎么获取一个数组的长度?如何获取字符串的长度? 这里我们先举用实例去分析一下:int[] arr=new int[3]:System.out.prin ...

  2. Android(四)-JVM与DVM区别

    JVM与DVM区别 1.由来 Android是基于java的既然java已经有了java虚拟机,为什么android还要弄一个DVM了?最重要的就是版权问题,一开始就是用的 JVM,没过多久就被SUN ...

  3. loadrunner通过web的post请求方法测接口

    loadrunner通过web的post请求方法测接口 loginapi() 模拟APP发送请求给Cloud, Action() "Name=input","Value= ...

  4. 我的学习归纳方法(以学习Maven为例)

    以我的个人角度来看待学习这件长久的事,希望对你有帮助,也希望你能提一下你的意见 本文初衷 把自己模板化 以此篇为引,与同行沟通心得,所以在此严重要求如果你有对应的心得还请能回复下,真心感谢!(鞠躬) ...

  5. Twins:眼红红 - fancybot的博客

    "我最初面红.现在双眼通红" TWINS 听听歌.小时候.听得最最最多的就是TWINS的了 跳过图片 跳过歌词 眼红红 Twins 仍然怀念他 一起去学结他 一对黑眼圈 回望中三暑 ...

  6. 运输层协议TCP和UDP

    运输层协议TCP和UDP 一.用户数据报协议 UDP 1.1.UDP 概述 UDP 只在 IP 的数据报服务之上增加了很少一点的功能,即端口的功能和差错检测的功能. 虽然 UDP 用户数据报只能提供不 ...

  7. jvm GC算法和种类

    1.GC     垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了. jvm 中,程序计数器.虚拟 ...

  8. 关于AJAX方法

    ajax的方法每次都记不住这次特意找了资料做了归总: 在这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数, ...

  9. 使用IDEA创建Maven整合SSM

    创建数据库 CREATE DATABASE `ssmbuild`; USE `ssmbuild`; DROP TABLE IF EXISTS `books`; CREATE TABLE `books` ...

  10. BeetleX之XRPC远程委托调用

    BeetleX.XRPC是基于接口的远程通讯组件,它不紧可以把接口提供客户端调用,同样也支持服务端创建客户端的接口实例并主动调用客户端的方法.接口有着非常的规范性和约束性,但前提你是必须制定相应的接口 ...