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

二、回调模式

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

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. Python---13靠谱的Pycharm安装详细教程

    昨天自学廖雪峰老师的python课程时,用的sublime编辑器,在命令行模式进行的输出,输出结果一直报错,说Python版本有问题,但在版本是满足要求的.最后在同事的电脑上运行了一下,是可以正常输出 ...

  2. LeetCode 刷题记录(6-10题)

    6 Z 字形变换(题目链接) class Solution: def convert(self, s, numRows): """ :type s: str :type ...

  3. C#利用反射调用PB编译的COM组件

    问题: 1.根据COM组件的ProgID,得到COM组件公开的类型 2.创建COM组件提供的类型的对象 3.调用执行方法 正确姿势 C#利用反射调用(后期绑定)PB编译的COM组件 C#调用COM组件 ...

  4. Flume 实战练习

    前期准备 了解Flume 架构及核心组件 Flume 架构及核心组件 Source : 收集(指定数据源从哪里获取) Channel : 聚集 Sink : 输出(把数据写到哪里去) 学习使用 Flu ...

  5. BFC 浅谈

    写在前面 Block formatting context (块级格式化上下文) 页面文档由块block构成 每个block在页面上占据自己的位置使用新的元素构建BFC overflow:hidden ...

  6. Bugku的一道注入

    继续补sqli的题 这道题与之前的题的区别是在第二部分中加了一道waf,所以需要特殊的手段来进行注入. 题目来源:http://123.206.87.240:9004/1ndex.php?id=1 第 ...

  7. Python2.7错误处理FileNotFoundError报错NameError: name 'FileNotFoundError' is not defined

    错误信息如下: 原因是FileNotFoundError是python3.0中的写法,而Python2.7中应写为IOError.

  8. java8新特性——stream笔记

    stream对象 Stream IntStream LongStream DoubleStream 创建 常用的三种方式: 使用list对象: list.stream() − 为集合创建串行流. li ...

  9. 7-34 jmu-python-是否偶数 (10 分)

    输入一个整数,判断是否偶数 输入样例: 7 输出样例: 7不是偶数 输入样例: 8 输出样例: 8是偶数 a = int(input()) if (a % 2 == 0): print('%d是偶数' ...

  10. 前端性能优化之 Composite

    摘要: 一个 Web 页面的展示,简单来说可以认为经历了 JavaScript/Style/Layout/Paint/Composite 几个步骤.本文主要深入 Composite 部分,从渲染原理. ...