举个例子:
<button onclick="updateSync()">同步</button><button onclick="updateAsync()">异步</button><div id="output"></div><script>function updateSync(){for (var i = 0; i < 1000000; i++) { document.getElementById('output').innerhtml = i;}}function updateAsync() {var i = 0; function updateLater() {document.getElementById('output').innerHTML = (i++);if (i < 1000000) {s etTimeout(updateLater, 0);} } updateLater();}</script>
点击 同步按钮会调用 updateSync的同步函数,逻辑非常简单,循环体内每次更新 output结点的内容为 i 。如果在其他多线程模型下的语言,你可能会看到界面上以非常快的速度显示从 0到 999999后停止 。但是在 JavaScript中,你会感觉按钮按下去的时候卡了一下,然后看到一个最终结果 999999,而没有中间过程,这就是因为在 updateSync函数运行过程中 UI更新被阻塞,只有当它结束退出后才会更新 UI 。反之,当点击 异步的时候,会明显的看到 Dom在逐步更新的过程 。
从上面的例子中可以明显的看出,异步编程对于 JavaScript来说是多么多么的重要 。
异步编程有什么好处从编程方式来讲当然是同步编程的方式更为简单,但是同步有其局限性一是假如是单线程那么一旦遇到阻塞调用,会造成整个线程阻塞,导致 cpu无法得到有效利用,而浏览器的 JavaScript执行和浏览器渲染是运行在单线程中,一旦遇到阻塞调用不仅意味 JavaScript的执行被阻塞更意味整个浏览器渲染也被阻塞这就导致界面的卡死,若是多线程则不可避免的要考虑互斥和同步问题,而互斥和同步带来复杂度也很大,实际上浏览器下因为同时只能执行一段 JavaScript代码这意味着不存在互斥问题,但是同步问题仍然不可避免,以往回调风格中异步的流程控制(其实就是同步问题)也比较复杂 。浏览器端的编程方式也即是 GUI编程,其本质就是事件驱动的(鼠标点击,Http请求结束等)异步编程更为自然 。
突然有个疑问,既然如此为什么 JavaScript没有使用多线程作业呢?就此就去 google了一下 JavaScript多线程,在 HTML5推出之后是提供了多线程只是比较局限 。在使用多线程的时候无法使用 window对象 。若 JavaScript使用多线程,在 A线程中正在操作 DOM,但是 B线程中已经把该 DOM已经删除了(只是简单的小例子,可能还有很多问题,至于这些历史问题无从考究了) 。会给编程作业带来很大的负担 。就我而言我想这也就说明了为什么 JavaScript没有使用异步编程的原因吧 。
异步与回调回调到底属于异步么?会想起刚刚开始学习 JavaScript的时候常常把这两个概念混合在一起 。在搞清楚这个问题,首先要明白什么是回调函数 。
百科:回调函数是一个函数,它作为参数传递给另一个函数,并在父函数完成后执行 。回调的特殊之处在于,出现在“父类”之后的函数可以在回调执行之前执行 。另一件需要知道的重要事情是如何正确地传递回调 。这就是我经常忘记正确语法的地方 。
通过上面的解释可以得出,回调函数本质上其实就是一种设计模式,例如我们熟悉的 JQuery也只不过是遵循了这个设计原则而已 。在 JavaScript中,回调函数具体的定义为:函数 A作为参数(函数引用)传递到另一个函数 B中,并且这个函数 B执行函数 A 。我们就说函数 A叫做回调函数 。如果没有名称(函数表达式),就叫做匿名回调函数 。
简单的举个小例子:
function test (n,fn){console.log(n);fn && fn(n);}console.log(1);test(2);test(3,function(n){ console.log(n+1)});console.log(5)// 结果// 1// 2// 3// 4// 5
通过上面的代码输出的结果可以得出回调函数不一定属于异步,一般同步会阻塞后面的代码,通过输出结果也就得出了这个结论 。回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足 。
回调函数应用场景
- 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等 。
- DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题) 。
- setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
- 链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现 。
推荐阅读
- JavaScript之dayjs用法,替代moment.js
- 10个很棒的JavaScript库,提升Web开发效率
- 同步与异步Python有何不同?
- python 中日志异步发送到远程服务器
- JavaScript 里的奇葩知识
- linux异步IO编程实例分析
- 浅析Kubernetes网络模型
- 对于 JavaScript 中循环之间的技术差异概述
- Java8——异步编程
- 开源JavaScript实用日期处理库——date-fns