新人初步学习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`指的是➌,并不是➊。所以➍执行完毕后,➋的值并不会发生任何变化。
- 序言
- 第一章:Hello World
- 第一节:Angular准备工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二节:Hello Angular
- 第三节:Spring Boot准备工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四节:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven国内源配置
- 4 package与import
- 第五节:Hello Spring Boot + Angular
- 1 依赖注入【前】
- 2 HttpClient获取数据【前】
- 3 数据绑定【前】
- 4 回调函数【选学】
- 第二章 教师管理
- 第一节 数据库初始化
- 第二节 CRUD之R查数据
- 1 原型初始化【前】
- 2 连接数据库【后】
- 3 使用JDBC读取数据【后】
- 4 前后台对接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三节 CRUD之C增数据
- 1 新建组件并映射路由【前】
- 2 模板驱动表单【前】
- 3 httpClient post请求【前】
- 4 保存数据【后】
- 5 组件间调用【前】
- 第四节 CRUD之U改数据
- 1 路由参数【前】
- 2 请求映射【后】
- 3 前后台对接【前】
- 4 更新数据【前】
- 5 更新某个教师【后】
- 6 路由器链接【前】
- 7 观察者模式【前】
- 第五节 CRUD之D删数据
- 1 绑定到用户输入事件【前】
- 2 删除某个教师【后】
- 第六节 代码重构
- 1 文件夹化【前】
- 2 优化交互体验【前】
- 3 相对与绝对地址【前】
- 第三章 班级管理
- 第一节 JPA初始化数据表
- 第二节 班级列表
- 1 新建模块【前】
- 2 初识单元测试【前】
- 3 初始化原型【前】
- 4 面向对象【前】
- 5 测试HTTP请求【前】
- 6 测试INPUT【前】
- 7 测试BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后台对接【前】
- 第三节 新增班级
- 1 初始化【前】
- 2 响应式表单【前】
- 3 测试POST请求【前】
- 4 JPA插入数据【后】
- 5 单元测试【后】
- 6 惰性加载【前】
- 7 对接【前】
- 第四节 编辑班级
- 1 FormGroup【前】
- 2 x、[x]、{{x}}与(x)【前】
- 3 模拟路由服务【前】
- 4 测试间谍spy【前】
- 5 使用JPA更新数据【后】
- 6 分层开发【后】
- 7 前后台对接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五节 选择教师组件
- 1 初始化【前】
- 2 动态数据绑定【前】
- 3 初识泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再识单元测试【前】
- 7 其它问题
- 第六节 删除班级
- 1 TDD【前】
- 2 TDD【后】
- 3 前后台对接
- 第四章 学生管理
- 第一节 引入Bootstrap【前】
- 第二节 NAV导航组件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三节 footer组件【前】
- 第四节 欢迎界面【前】
- 第五节 新增学生
- 1 初始化【前】
- 2 选择班级组件【前】
- 3 复用选择组件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校验【后】
- 7 唯一性校验【后】
- 8 @PrePersist【后】
- 9 CM层开发【后】
- 10 集成测试
- 第六节 学生列表
- 1 分页【后】
- 2 HashMap与LinkedHashMap
- 3 初识综合查询【后】
- 4 综合查询进阶【后】
- 5 小试综合查询【后】
- 6 初始化【前】
- 7 M层【前】
- 8 单元测试与分页【前】
- 9 单选与多选【前】
- 10 集成测试
- 第七节 编辑学生
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 功能开发【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成测试
- 7 @Input 异步传值【前】
- 8 值传递与引入传递
- 9 @PreUpdate【后】
- 10 表单验证【前】
- 第八节 删除学生
- 1 CSS选择器【前】
- 2 confirm【前】
- 3 功能开发与测试【后】
- 4 集成测试
- 5 定制提示框【前】
- 6 引入图标库【前】
- 第九节 集成测试
- 第五章 登录与注销
- 第一节:普通登录
- 1 原型【前】
- 2 功能设计【前】
- 3 功能设计【后】
- 4 应用登录组件【前】
- 5 注销【前】
- 6 保留登录状态【前】
- 第二节:你是谁
- 1 过滤器【后】
- 2 令牌机制【后】
- 3 装饰器模式【后】
- 4 拦截器【前】
- 5 RxJS操作符【前】
- 6 用户登录与注销【后】
- 7 个人中心【前】
- 8 拦截器【后】
- 9 集成测试
- 10 单例模式
- 第六章 课程管理
- 第一节 新增课程
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 async管道【前】
- 4 优雅的测试【前】
- 5 功能开发【前】
- 6 实体监听器【后】
- 7 @ManyToMany【后】
- 8 集成测试【前】
- 9 异步验证器【前】
- 10 详解CORS【前】
- 第二节 课程列表
- 第三节 果断
- 1 初始化【前】
- 2 分页组件【前】
- 2 分页组件【前】
- 3 综合查询【前】
- 4 综合查询【后】
- 4 综合查询【后】
- 第节 班级列表
- 第节 教师列表
- 第节 编辑课程
- TODO返回机制【前】
- 4 弹出框组件【前】
- 5 多路由出口【前】
- 第节 删除课程
- 第七章 权限管理
- 第一节 AOP
- 总结
- 开发规范
- 备用