新人初步学习JAVASCIPRT时,一般都会在两个关键点上犯迷糊。第一个关键点异步,第二个关键点是回调。回调 --- callback,与其对应的是调用 ---- call。 # DEMO 上一小节中使用将`function`传入`subscribe`方法实现了:当访问请求成功时,将请求的结果赋值给title的作用。`subscribe`方法是如何做到的呢,下面的代码段展示了其基本原理。 ```javascript var demo = { subscribe: function (success, error) { // 获取随机值 var random = Math.floor(Math.random() * 100); // 根据随机值选择执行方法 if (random % 2) { // 并不知道success方法的作用也不关心它的作用 // 当满足偶数时,就调用success方法。 success(random); } else { // 并不知道error方法的作用也不关心它的作用 // 当满足奇数时,就调用error方法 error(random); } } } function success(randomNumber) { console.log('我是第一个方法,接收到的值为' + randomNumber); } function error(randomNumber) { console.log('我是第二个方法,接收到的值为' + randomNumber); } function success1(randomNumber) { console.log('success1' + randomNumber); } function error1(randomNumber) { console.log('error1' + randomNumber); } // 偶数时执行success, 奇数时执行error。 demo.subscribe(success, error); // 偶数时执行success1, 奇数时执行error1。 demo.subscribe(success1, error1); ``` 总结:JS支持将函数作用参数进行传递。被调用的方法在接收到该函数类型的参数时,决定是否调用及何时调用该参数,最终完成回调操作。这种在被调用方法中执行调用方法的模式便称为回调。而在调用某方法时,传入某个参数的函数便称为回调函数。 # 生活中的回调 **调用**情景: 以前我去KFC,在前台点了一杯3元的可乐,然后在前台等待着可乐灌装完成并递给自己。接着开始喝可乐。喝完可乐后,感觉KFC环境还可以同时还有免费的WIFI。所以我又玩了会手机、刷了会微博。 对应代码: ``` let cola = kfc.orderCola(3); ➊ this.drink(cola); ➋ this.playPhone(); ➌ this.refreshWeibo(); ➍ ``` 上述过程即是调用,我是调用的发起方,KFC是调用的执行方。KFC按我的要求进行处理,并将处理的结果返回给我,而在KFC处理我的订单的过程中,我一直未离开前台进行等待。 执行顺序永远是:➊➋➌➍ > 有些时候,我在等待的时候还不愿意从点餐口那离开,从而无意识的耽误了后面的顾客点餐桌,便形成了短暂的堵塞。是的,计算机的同步也存在堵塞这个问题。 **回调**情景: 你去KFC,在自动终端上点了一杯3元的可乐,自助终端给你了一张编号为123的凭证。然后你随便找了个地坐了下来,悠闲的计划玩玩手机,刷刷微博。KFC在可乐灌装完成后,服务员喊道:编号123的顾客请到前台聚餐。我停下刷微博的脚步,拿上可乐并开始享受它。 对应代码: ``` kfc.orderCola(3, function number123(cola: Cola) { ➊ this.drink(cola); ➍ }); this.playPhone(); ➋ this.refreshWeibo(); ➌ ``` 上述情景中,在`服务员喊道`的时刻便发生`回调`。没有回调以前,只能是我们主动向服务员发起调用,但有了`回调`以后,服务员便可以在完成灌装后主动和我们打招呼了。 上述情景的执行顺序为:➊➋➌➍。在现实生活中,这个顺序不是固定的。比如下次我再去买可乐的时候,餐厅的人没有那么多了,那么KFC的服务员灌装的时间就会小很多,所以我游戏没玩完,人就已经通知我拿可乐了。此时,执行的顺序便是➊➋➍➌。在计算机也是这样的,相同的上述代码执行两次,这两次的执行顺序也是可能不一样的。也就是说,我们并不知道KFC的服务员最近的效率如何。最近效率高,就会通知的快一些,最近效率低就会通知的慢一些。实际生活中是这样,在程序中也是这样。 > 当我们向KFC服务员发起`购买`之后,KFC服务员可以在接下来在某一个时刻来`通知`我们。这个过程中的`购买`便是`调用`,而`通知`便是`回调`。 # 回调的特点 有了现实生活中活生生的例子,相信总结一下它的规律便不难了: ① 在购买可乐的时候,形成了一个契约:可乐完成后通知我(简称通知)。 ② KFC的服务员决定什么时候通知我。 ③ 不止如此,有一天我点了几个品种想饱餐一顿,结果碰到了一个晕晕的服务员,饭都吃完了也没有通知我去拿可乐。。。 ④ 无独有偶,还有一天我点了一怀可乐,竟然先后送了两杯给我。 ⑤ 即使点餐计划相同,但环境不同、餐厅不同,最后整个事情的执行过程也不会相同。 ⑥ 点完可乐,我们无需等待,可以选择做其它的事情。 ⑦ 点完可乐,我们也可以选择呆呆着等着通知,什么也不做。 ***** ① 在发生调用时,将回调函数(通知)做完参数传入,形成了一个契约。 ② 被调用者决定什么时候执行回调函数。 ③ 回调函数可以不被执行。 ④ 回调函数可以被执行多次。 ⑤ 同一段包含有回调函数的代码,每次的执行过程都可能不同。 ⑥ 回调的第一种:异步回调 ⑦ 回调的第二种:同步回调 > 尽管现在生活中充满着异步。但由于计算机很傻很天真,所以点餐后如果你不主动的告诉它可以去做些别的事情了,那么它就会一直傻傻的等待下去。在JS的世界里,只有在两种情况下回调是异步的,即:`资源请求`以及`timeout`。在后续的章节中,我们会详细的介绍。 # 本节小测 有下述代码: ``` kfc.orderCola(3, function number123(cola: Cola) { ➊ this.drink(cola); ➋ }); kfc.orderHamburger(10, function number234(hamburger: Hamburger) { ➌ this.eat(hamburger); ➍ } this.playPhone(); ➎ this.refreshWeibo(); ➏ ``` 请判断:以下执行过程是否可能发生: 1. ➊➋➌➍➎➏ 2. ➊➌➋➍➎➏ 3. ➊➌➍➋➎➏ 4. ➊➌➋➍➏➎ 5. ➊➌➎➍➋➏ 6. ➊➌➎➋➍➏ 7. ➊➋➍➌➎➏ ## 上节答案 由于以下两个代码段等价 ~~~ constructor(private httpClient: HttpClient) { const self = this; /* 向8080端口的helloWorld路径发起请求 */ this.httpClient.get('http://localhost:8080/helloWorld') .subscribe( function success(data: { message: string }) { this.title = data.message; console.log(data); }, error); } ~~~ ~~~ export class AppComponent { ➊ constructor(private httpClient: HttpClient) { const self = this; // 常规写法,避免在回调时发生未知的异常 /* 向8080端口的helloWorld路径发起请求 */ this.httpClient.get('http://localhost:8080/helloWorld') .subscribe( success, error); } title = 'hello-world'; ➋ } function success(data: { message: string }) { ➌ this.title = data.message; ➍ console.log(data); } ~~~ 我们心中的预期:在➍处对➋赋值。 实际的执行过程:在➍处的`this`位于➌中,所以此处的`this`指的是➌,并不是➊。所以➍执行完毕后,➋的值并不会发生任何变化。