JavaScript 模式》读书笔记(4)— 函数2
这篇,我们仍旧继续学习函数。
二、回调模式
函数都是对象,这表示它们可以作为参数传递给其它函数。
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的更多相关文章
- JavaScript模式读书笔记 第4章 函数
2014年11月10日 1.JavaScript函数具有两个特点: 函数是第一类对象 函数能够提供作用域 函数即对象,表现为: -1,函数能够在执行时动态创建,也 ...
- JavaScript模式读书笔记 文章3章 文字和构造
1.对象字面量 -1.Javascript中所创建的自己定义对象在任务时候都是可变的.能够从一个空对象開始,依据须要添加函数.对象字面量模式能够使我们在创建对象的时候向其加入函数. ...
- 《你不知道的javascript》读书笔记2
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...
- 《编写可维护的javascript》读书笔记(中)——编程实践
上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...
- C语言深度解剖读书笔记(6.函数的核心)
对于本节的函数内容其实就没什么难点了,但是对于函数这节又涉及到了顺序点的问题,我觉得可以还是忽略吧. 本节知识点: 1.函数中的顺序点:f(k,k++); 这样的问题大多跟编译器有关,不要去刻意追求 ...
- Javascript & JQuery读书笔记
Hi All, 分享一下我学JS & JQuery的读书笔记: JS的3个不足:复杂的文档对象模型(DOM),不一致的浏览器的实现和便捷的开发,调试工具的缺乏. Jquery的选择器 a. 基 ...
- 《你不知道的javascript》读书笔记1
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...
- JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)
this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- SQL反模式读书笔记思维导图
在写SQL过程以及设计数据表的过程中,我们经常会走一些弯路,会做一些错误的设计.<SQL反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...
随机推荐
- Java 去掉字符串中的重复数据
有一组字符串比如 北京,北京,上海,上海,上海,武汉-------->要得到 北京,上海,武汉 怎么去掉里面重复的字符串? function DelRepetStr(String ...
- oppo互联网招聘-各类软件测试
一.服务端测试专家 关键词:安全测试.白盒测试.性能测试.自动化.持续集成.服务端 岗位职责: 主导多个高日活产品的测试方案: 试点和推广自动化和持续集成: 改善测试相关流程和规范. 职位要求: 计算 ...
- CSS 双飞翼布局
10 Jul 2016 » CSS 双飞翼布局:总共分三栏,左侧栏Left,中间主栏Main,右侧栏Right 第一步,建立三个div,不过注意,中间Main需要加一个wrap div. 整个结构看起 ...
- 天哪!毫无思绪!令人感到恐惧的数学(水题?)(TOWQs)
这道题的题目描述灰常简单,第一眼看以为是一道十分水的题目: 但是!!!(我仔细一看也没有发现这背后隐藏着可怕的真相~) 下面给出题目描述: 给出一个整数x,你可以对x进行两种操作.1.将x变成4x+3 ...
- 关于C++类中的三兄弟(pretect、private、public)
1.public修饰的成员变量 在程序的任何地方都可以被访问,就是公共变量的意思,不需要通过成员函数就可以由类的实例直接访问 2.private修饰的成员变量 只有类内可直接访问,私有的,类的实例要通 ...
- [工具] Docker安装及portainer GUI
一.Docker Engine安装 1.安装流程 1)移除旧版本(如果有旧版本) yum remove docker \ docker-client \ docker-client-latest \ ...
- java基础知识点补充---二维数组
#java基础知识点补充---二维数组 首先定义一个二维数组 int[][] ns={ {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, {13,14,15,16} }; 实现遍 ...
- unittest实战(三):用例编写
# coding:utf-8import unittestfrom selenium import webdriverimport timefrom ddt import ddt, data, unp ...
- 使用nestjs集成grpc具体操作
两个程序中, 提供grpc服务的称为服务端, 调用grpc服务的为客户端, 以下是grpc服务端和客户端的代码编写 1. 创建两个nestjs项目demo1(端口: 3000)和demo2(端 ...
- python之路-基本数据类型之str字符串
1.概念 python中用',",''',"""引起来的内容称为字符串,可以保存少量数据并进行相应的操作 #先来看看str的源码写了什么,方法:按ctrl+鼠标 ...