# 回调函数

# 异步

  • 同步代码是依次执行,异步就是在同步之后执行的代码

# 高阶函数 (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 的请求中包装一层自己的逻辑
1
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();
1
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数据') // {}

1
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')
})

1
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))
}

1
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数据'

1
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('饿了')

1
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(被观察者)就会调用观察者的方法。订阅-发布模式大多数时候是异步的(使用消息队列)
  • 观察者模式需要在单个应用程序地址空间中实现,订阅-发布更像交叉应用模式

静态图片