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

二、回调模式

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

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并发一

    简介 从创建以来,JAVA就支持核心的并发概念如线程和锁.这篇文章会帮助从事多线程编程的JAVA开发人员理解核心的并发概念以及如何使用它们. (博主将在其中加上自己的理解以及自己想出的例子作为补充) ...

  2. python通用读取vcf文件的类(可以直接复制粘贴使用)

    前言   处理vcf文件的时候,需要多种切割,正则匹配,如果要自己写其实会比较麻烦,并且每次还得根据vcf文件格式或者需要读取的值不同要修改相应的代码.因此很多人会选择一些python的vcf的库,但 ...

  3. GIT 使用(一):安装和配置

    安装和配置 Table of Contents 1. 安装 2. 配置 1. 初次运行 Git 前的配置 小结 2. 用户信息 3. 别名 4. 查看已经存在的配置 3. 获取帮助 4. 参考与扩展阅 ...

  4. USB小白学习之路(5) HID鼠标程序

    HID鼠标程序 1. 特别注意 需要特别注意,各个例程中的设备描述符,配置描述符等各种描述符都是已经配置好了的,我们需要做的只是在例程中将代码修改为自己需要的部分即可,一般情况下是不可以串搭配的. 2 ...

  5. Maven使用和配置

    Maven使用和配置 一.maven安装和概念 maven安装 maven编译(compile) 执行测试用例(test) maven打包(package) maven依赖管理 1.maven安装 官 ...

  6. Java Web环境配置

    准备工作 jdk-8u241 apache-tomcat-9.0.31-windows-x64.zip Eclipse IDE for Enterprise Java Developers 关于版本选 ...

  7. 添加Windows 10开机启动项:No Hyper-V

    在Windows 10 1903版本加入了一项沙盒功能,1903版本以上的系统可以在控制面板-程序和功能-启用或关闭Windows功能中勾选Windows 沙盒选项,根据操作重启后即可打开沙盒功能. ...

  8. springboot自动装配原理回顾、配置文件分析

    配置文件 spring boot官方文档 官方外部配置文件说明参考文档 自动配置原理分析 1. SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfigurat ...

  9. 全差分运算放大器ADA4930的分析(1)

    AD转换芯片的模拟信号输入端方式为:全差分.伪差分.单端输入,其中全差分输入的效果最佳,现阶段ADC转换器为了提高其性能,建议用户使用全差分的输入方式.(AD7982.ADS8317等都能实现信号的全 ...

  10. 仿segmentfault-table横向滚动

    问题描述 自己的博客在用移动端访问时,如果table的列数足够多会显示不全,如下图红圈所示 正常情况如图 解决过程 使用chrome发现segmentfault的解决方法是在table上套一个tabl ...