本文出自http://blog.csdn.net/yeahq
,转载请注明出处
由于其运行环境的特殊性,Javascript大量使用异步的通信机制,凡是涉及到网络调用和事件机制的代码都会涉及。在异步通信的环境下编码经常会用到
回调函数。Javascript由于有函数式语言的一些特点使得它在Javascript里面实现回调函数非常的优雅和自然,包括函数作为一级的对象、匿
名函数、闭包机制等。但是要体会到个中的优雅,需要先融汇贯通这些机制。如果是初学者学习这些东西可能比有编程经验的人少很多障碍,认为事情本来就该是这
个样子。但是,对于长期使用过程式语言编码(比如传统的C/C++程序员),又没有接触过函数式语言的程序员来说,可能需要阅读一道思维的小坎。这件事情
有时候会造成一定的困扰,因为“老手”程序员会想:毕竟我已经懂得一套能写程序的方法,大家都说语言之间差别不重要,毕竟C++里面也有使用异步调用的时
候,主要注意一下语法的区别就好了。所以最终就变成了使用Javascript来模仿别的过程式语言,这样的结果最终很有可能是写出很别扭的程序给自己添
堵。本文尝试用几个例子说明异步通信的环境用Javascript写回调函数很使用类似C语言写回调函数的区别,以及为什么Javascript原生要更
适合做这件事情。(简单起见,下面例子中的代码均为伪代码,并不一定严格符合C/C++或者Javascript的语法,但是笔者尽量写得与语法要求接
近。)
我们首先从C/C++的同步调用开始,假设我们要写一个函数,向远方的服务器发送一个字符串形式得命令,并且从服务器得到一个字符串作为响应。例1就展示了使用C语言在同步同步通信的机制下代码的样子。
例1 使用C语言的编码方式实现调用访问远程的接口
在
例1中,get_data_v1执行了准备数据、创建了socket、建立连接、发送请求、接收响应并最终使用use函数处理接收到的数据,一切都显得很
自然。为了方便说明问题,我们将这个通信的过程封装一下,将整个建立连接并收发包的过程封装成一个叫send_and_recv的函数。
例2 将通信过程封装成独立的函数,简化业务流程代码
例
2和例1很类似,不过是对通信过程进行封装了,并且ip-port对也变成了一个叫addr的地址结构体。改动以后处理过程变得更简单,剩下准备数据、通
信和处理结果三步。现在,我们开始进入正题,现在我们假设这个通信过程变成异步的,它接收一个回调函数用于处理取得的数据。如例3所示。
例3 将通信过程变成异步调用
在
例3中,假设使用了一个异步的通信过程send_and_recv_async,最后一个参数callback是一个回调函数指针。然后,当接收到响应以
后,send_and_recv_async会调用callback并传入接收到的数据。相比例2,这个get_data的过程被异步通信过程一分为二:
前半段为准备请求,后半段是处理结果。事实上,对将同步通信方式变成异步以后,都会涉及到将原来完整处理过程一分为二的问题。在两段程序没有什么相互依赖
的情况下,这样的分解不会造成什么问题。但是,如果处理结果的过程依赖于一些外部参数,那么情况就会变得很复杂。我们先来看看在同步通信的情况下,程序的
样子,见例4。
例4 假设处理结果的时候依赖外部参数
在例4中,我们的结果处理过程use依赖于传入的两个参数a和b。现在我们来看看例4的程序如果使用异步通信会怎样,见例5。
例5 加上参数依赖后再变成异步调用
例
5中有两个版本,get_data_v5a假设了通信机制可以透传a和b两个参数给回调函数,get_data_v5b则使用了两个全局变量来传递处理结
果所需的参数。两个都不见得是很好的方法,get_data_v5a的问题是,异步通信的机制不见得能提供这种透传机制,除非程序员自己封装;即使程序员
自己封装,那也意味着如果要实现多个处理数据的过程(像get_data)那就要实现多个异步调用的过程(send_and_recv_async),代
码复杂且复用性差不好维护。而全局变量的版本也好不到哪里去,使用这种全局的机制,意味着不必要的信息暴露,也就有被别的地方错修改的问题,同时这个函数
还变成不可重入的。即使将全局机制封装在一个类里面,每次初始化一个对象,可以改善依然不能解决信息暴露的问题,同时还带来了管理这多个对象的复杂性。
两种方法相比而言,貌似透传的机制要稍好一些。我们对get_data_v5a略做修改,使得它通信过程能够有更广泛的复用。
例6 使用一个closure对象打包过程中的参数
例
6里面使用了一个叫closure的结构,假设这个结构是个通用的数据容器,可以容纳我们使用的个中类型的任意数量的参数。增加了这一个万能的数据容器参
数以后,异步通信过程只要能透传这么一个数据容器就能够很好支持个中各样的参数透传的需求。这个数据容器由于是在get_data函数内部产生的局部变
量,不会污染全局数据或者比get_data更大的作用域。这种受限的可见性不仅提高了代码的可维护性,还恢复了函数的可重入性。
至此我
们关于回调机制的实现的假想代码可以说已经达到比较优雅的程度了,仅仅还有一朵小乌云。那就是我们忽略了C/C++语言里面并没有原生实现这个超级结构,
同样我们依然还有一点点麻烦就是还需要指定要透传的参数。考虑到原本从准备数据到通信再到处理结果是一个完整统一的过程,原本不需要区分什么数据是前半端
使用的什么数据是后半段使用的,只要让前半端和后半段共享一个上下文在大部分情况下就能满足需求了。所以现实情况下我们只能做一些妥协,使用个中折衷方案
来使得程序能运行起来。同样,考虑到回调函数和启动函数的关系,给回调函数命名也不是那么优雅的事情,因为毕竟它们只是同一个过程的两半,却要使用两个名
字,合理一点就应该叫get_data_first和get_data_second,或者get_data_trigger和
get_data_result_handler。如果接口多的话,就会有很多这种某过程first和某过程second,或者某过程trigger和某
过程result_handler。能不能某过程就象同步那样使用一个名字呢?我们的设想真的就没有办法达到吗?答案是否定的,在Javascript能
够帮助我们实现我们所有的设想。见例7。
例7 Javascript的异步调用
例
7是使用Javascript实现类似例6的功能,仅仅存在一些细微的差别。例6的场景下可能更多使用TCP或者UDP作为通信协议,而在例7使用的则是
浏览器提供的XHR对象实现的HTTP协议。这点差别并不会影响我们对于异步通信下回调函数实现机制的讨论,只要他们的通信机制都是异步的就可以了。例7
中使用注释的形式标注了例6里面使用的一些参数的名字以暗示它们的对应关系,方便比较这两个例子。我们看到了,在Javascript里面我们所有的设想
都变成了现实。(1)首先关于能够透传一切的超级结构,Javascript中实现了闭包的机制,保证了在这种内部的函数对象可以访问到定义它的环境能访
问到的所有数据,也就是在例7中的匿名回调函数可以访问到get_data_js中能访问到的所有数据。当然,这里重要的是局部数据,如a和b。如果是全
局数据的话并不需要通过闭包也能访问到。而且这个过程是Javascript的运行环境提供的,对于程序员是透明的,程序员并不需要指定哪些参数需要透
传。(2)不需要再为回调函数命名,因为Javascript支持匿名函数的定义,可以像定义变量一样定义函数。而这个最终导致了我们在使用异步通信机制
的时候和使用同步的通信机制及其接近,没有多余的名字,没有不必要的可见性。
对编写代码方式的影响
使用
Javascript来使用异步通信机制或者其他异步机制时,应该像同步代码一样编写,将准备数据、通信以及处理结果三步放到一个完成的流程下面,保持代
码逻辑的高内聚。代码也不至于因为使用了异步通信的机制而变得四分五裂。从上面例子可以看到,这个过程需要做的仅仅时将数据处理的部分封装在一个匿名函数
中,程序员不需要关心数据透传的过程,更加不应该越俎代庖地去实现任何过程数据地保持以及数据透传的工作。因为多一行代码就多一个可能的错误来源。
分享到:
相关推荐
JavaScript的回调机制讲解.pdf
对于熟悉Java等其他编程语言的开发者而言,回调函数的这一概念与Java中的接口回调相似,这将有助于他们更快地理解并运用JavaScript的回调机制。在Java中,回调通常是通过接口实现的,而JavaScript中则使用函数指针来...
本文将详细讲解如何使用回调方式查询数据库记录。 首先,我们需要了解数据库查询的基本概念。数据库是一个存储数据的系统,通过SQL(结构化查询语言)与之交互。查询通常涉及从数据库中检索满足特定条件的数据。...
在这种混合开发模式下,就需要让Web页面与原生应用进行交互,而JavaScript的回调机制正是解决这一问题的关键。 为了说明如何实现这个机制,我们可以设想这样一个场景:一个应用展示用户的好友列表,列表是Web页面,...
本文通过讲解自定义回调函数示例代码,深入理解JavaScript中的回调函数使用方法及其实现原理。 1. 回调函数的定义 回调函数是一个在某个时刻会被调用的函数。在JavaScript中,函数可以作为一等公民,这意味着它们...
另外,JavaScript还支持匿名函数和立即执行函数表达式,这在编写回调函数或封装代码时非常有用。 在《Head First JavaScript源码》中,你可能会看到关于对象和原型的讲解。JavaScript的面向对象特性基于原型,对象...
4. **异步编程**:详述了事件循环、回调函数、Promise、async/await等异步处理方式,帮助开发者应对JavaScript的非阻塞I/O特性。 5. **正则表达式**:介绍了JavaScript中的正则表达式语法和用法,用于字符串的匹配...
7. **Promise和async/await**:Promise解决了异步编程中的回调地狱问题,而async/await则进一步提升了异步代码的可读性和易维护性。 8. **事件和事件处理**:JavaScript通过事件模型来处理用户交互,如点击、提交、...
理解事件循环机制和事件委托策略,能有效提高性能并避免回调地狱。 此外,JavaScript的异步编程模型,如Promise和async/await,是现代JavaScript开发不可或缺的部分。它们用于处理非阻塞I/O操作,使得代码更加线性...
5. **处理响应**: 当服务器返回数据时,通过回调函数处理这些数据并更新页面内容。 #### 示例代码 下面是一个简单的示例,展示了如何使用JavaScript创建一个异步请求,并处理响应数据。 ```javascript function ...
JavaScript是一种单线程语言,但是通过异步编程机制如回调函数、Promise和async/await,可以有效地处理非阻塞任务,避免UI冻结,提高应用性能。 #### 模块化编程 随着项目规模的增长,模块化编程变得尤为重要。...
1. **JavaScript基础与特性**:首先,书中会介绍JavaScript的基础知识,包括变量、数据类型、函数、对象等,同时也涵盖了JavaScript的异步处理机制,如回调函数、事件循环和Promise。 2. **作用域与闭包**:Zakas...
接着,深入到JavaScript的异步编程,包括回调函数、Promise以及async/await。这些内容是现代Web开发中的核心概念,因为它们解决了JavaScript单线程模型下的阻塞问题,使得开发者可以优雅地处理异步操作。 除此之外...
作者讨论了一些处理并发问题的策略,如事件循环和回调函数。 总的来说,《Effective JavaScript》提供了对JavaScript深入理解的宝贵资源,通过68个具体的方法,它不仅揭示了语言的复杂性,还给出了实用的建议,帮助...
此外,高级主题如闭包、原型链、异步编程(回调函数、Promise、async/await)也可能包含其中,帮助初学者构建坚实的JavaScript基础。 "JavaScript Help_cn.chm"是JavaScript的帮助文档,可能是英文版的中文翻译。这...
这通常涉及事件循环、回调函数、Promise、async/await等技术。学习者将学会如何避免回调地狱,提高代码可读性和可维护性。 模块化是现代JavaScript项目的重要组成部分。教程会介绍CommonJS、AMD以及ES6的模块系统,...
本书还会深入讲解JavaScript的异步编程,特别是回调函数、Promise和async/await。这些都是现代JavaScript开发中不可或缺的部分,用于处理网络请求、定时任务和其他非阻塞操作。 除此之外,《JavaScript - The ...
异步编程是JavaScript的重要特性,常见的有回调函数、Promise和async/await。这些机制可以帮助开发者处理耗时的操作,如网络请求,而不阻塞程序的其他部分。 "Head First JavaScript"这本书会详细讲解这些概念,并...
接着,John Resig深入讲解了原型和原型链,这是JavaScript继承机制的基础。通过实例,读者可以了解到如何利用原型创建对象,实现类的模拟,以及如何通过原型链进行属性查找,这些都是JavaScript面向对象编程的重要...
4. **异步编程**:讨论事件循环、回调函数、Promise和async/await,这些都是JavaScript处理非阻塞I/O操作的关键。 5. **错误处理**:如何有效地捕获和处理运行时错误,以提高代码的健壮性。 6. **DOM操作**:...