# 回调函数
# 异步
- 同步代码是依次执行,异步就是在同步之后执行的代码
# 高阶函数 (aop 偏函数 函数柯里化)
函数接受一个函数作为参数,并返回一个函数
aop 面向切片编程
# 回调函数的应用
装饰器:不破坏原有的功能,在执行函数之前加入些其他的事情
Function.prototype.before = function(callback){
let self = this
return function () {
callback() // before 函数的参数
self.apply(self, arguments) // 谁调用的 before this就是谁
}
}
function fn (val) {
console.log('存在一定的功能:' + val)
}
let newFn = fn.before(function() {
console.log('在fn函数执行前,做些操作')
})
newFn('你好', '不好')
// 常见场景:前端埋点 在ajax 的请求中包装一层自己的逻辑
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
有一个函数可以接收一个函数,可以根据条件执行这个函数
function after (times, callback) {
return function (){
if (--times === 0) { // 调用 3 次后执行回调函数
callback();
}
}
}
let fn = after(3, function() {
console.log('fn被调用了3次');
});
fn();
fn();
fn();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
举个回调例子
并发调用接口,两个ajax, ajax1 获取 name 信息 ,ajax2 获取 age 信息 最终输出 name + age
// 文件读取 name.txt 里存着 age.txt age.txt里存着 18
let fs = require('fs')
let school = {}
// 读取 name.txt的信息
fs.readFile('./name.txt', 'utf8', function(err, data) {
if (err) {
console.log(err, '报错了')
return
}
school.name = data // age.txt
console.log(data, '获取数-name')
})
// 读取 age.txt 的信息
fs.readFile('./age.txt', 'utf8', function(err, data) {
if (err) {
console.log(err, '报错了')
return
}
school.age = data // 18
console.log(data, '获取数据-age')
})
console.log(school, 'school数据') // {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
由于 js 是异步执行的,所以 school 是空对象,可以使用回调解决,多个异步同时执行,在某一个时刻拿到最终结果
let school = {};
// 哨兵函数
function after (times, callback) {
let result = {}
return function (key, data) {
result[key] = data
if (--times === 0) {
callback(result)
}
}
}
let newFn = after(2, function(result) { // 这个方法会在所有异步执行之后执行
console.log('最终结果:', result)
})
fs.readFile('./name.txt', 'utf8', function(err, data) {
if (err) {
console.log(err, '报错了')
return
}
// 调用函数
newFn('name', data)
console.log(data, '获取数据-name')
})
fs.readFile('./age.txt', 'utf8', function(err, data) {
if (err) {
console.log(err, '报错了')
return
}
// 调用函数
newFn('age', data)
console.log(data, '获取数据-age')
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 订阅发布模式
订阅发布模式: 先把需要订阅的内容保存在队列里, 当发布时候让数组中的函数依次执行
在订阅-发布模式中,消息的发送方,叫做发布者(pulishers),消息不会直接发送给特定的接收者,即订阅者
发布者和订阅者不知道对方的存在,需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息,换句话说,订阅-发布模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在

// 订阅 - 发布
function EventEmitter () {
this._arr = [] // 媒介
}
// 订阅
EventEmitter.prototype.on = function (callback) {
this._arr.push(callback)
}
// 发布
EventEmitter.prototype.emit = function () {
// 发布时需要让订阅(on)的方法依次执行完
// console.log(this._arr.length, '当前数组')
this._arr.forEach(fn => fn.apply(this, arguments))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
重新改造下刚刚的代码
let fs = require('fs')
let school = {}
let e = new EventEmitter()
// 先订阅
e.on(function (data, key) {
school[key] = data
if (Object.keys(school).length === 2) {
console.log(school, '输出结果')
// { age: '18', name: 'age.txt' } '输出结果'
}
})
fs.readFile('./name.txt', 'utf8', function(err, data) {
if (err) {
console.log(err, '报错了')
return
}
// 发布
e.emit(data, 'name')
})
fs.readFile('./age.txt', 'utf8', function(err, data) {
if (err) {
console.log(err, '报错了')
return
}
// 发布
e.emit(data, 'age')
})
console.log(school, 'school数据') // {} 'school数据'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 观察者模式
订阅 + 发布 = 观察者
提醒
观察者模式: 观察者 被观察者 (存放着观察者), 被观察者状态变化, 要更新自己身上的所有观察者
观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知他们
举个栗子
假设你正在找一份工作,对“香蕉公司”很感兴趣。所以你联系了他们的HR,给了他你的联系电话。HR保证如果有任何职位空缺都会通知你。这里还有几个候选人和你一样很感兴趣。所以职位空缺大家都会知道,如果你回应了他们的通知,他们就会联系你面试。
以上和“观察者模式”有什么关系呢?这里的“香蕉公司”就是Subject,用来维护Observers(和你一样的候选人),为某些event(比如职位空缺)来 通知(notify) 观察者。

// 被观察者 小宝宝
class Subject {
constructor () {
this.state = '开心'
this.arr = []
}
// 装载观察者 记录观察者
attach (observer) {
this.arr.push(observer)
}
// 更新自己的状态
setSate (newState) {
this.state = newState
this.arr.forEach(observer => observer.update(newState))
}
}
// 每个数据变化 都应该对应自己的观察者 而不是 一个数据变了 都要更新一下
// 观察者 我
class Observer {
constructor (name) {
this.name = name
}
// 这个方法是给被观察者调用的
update (newSate) {
console.log('通知到' + this.name + ' 小宝宝' + newSate + '了----')
}
}
let subject = new Subject()
let my1 = new Observer('我')
let my2 = new Observer('媳妇')
subject.attach(my1)
subject.attach(my2)
subject.setSate('饿了')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 观察者 VS 订阅发布
- 在观察者模式中,观察者是知道Subject(被观察者)的, Subject一直保持对观察者进行记录,在订阅-发布模式中,订阅者和发布者不知道对方的存在,他们只有通过消息代理进行通信
- 在订阅-发布模式中,组件是松散耦合的,观察者模式相反
- 观察者模式大多数是同步的,比如当事件触发,Subject(被观察者)就会调用观察者的方法。订阅-发布模式大多数时候是异步的(使用消息队列)
- 观察者模式需要在单个应用程序地址空间中实现,订阅-发布更像交叉应用模式

← 关于this Promise 基础实现 →