Node.js && JavaScript 面试经常使用的设计模式二

观察者模式

这是一个很是有趣的模式,它容许你经过对某个输入做出反应来响应,而不是主动地检查是否提供了输入。换句话说,使用此模式,能够指定你正在等待的输入类型,并被动地等待,直到提供该输入才执行代码。git

在这里,观察者是一个对象,它们知道想要接收的输入类型和要响应的操做,这些对象的做用是“观察”另外一个对象并等待它与它们通讯。github

另外一方面,可观察对象将让观察者知道什么时候有新的输入可用,以便他们能够对它作出反应(若是适用的话)。若是这听起来很熟悉,那是由于Node.js中处理事件的任何东西都是这种模式。数据库

下面的代码是一段HTTP Server的实现express

const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Your own server here');
})

server.on('error', err => {
    console.log(“Error:: “, err)
})

server.listen(3000, '127.0.0.1', () => {
  console.log('Server up and running');
})
复制代码

在上面的代码中隐藏了观察者模式。服务器对象将充当可观察对象,而回调函数是实际的观察者。你能够看到,这种模式很是适合这种HTTP异步调用。这种模式的另外一个普遍使用的用例是触发特定事件。这种模式能够在任何容易异步触发事件(例如错误或状态更新)的模块上找到。例如数据库驱动程序,甚至套接字。io,它容许你在本身代码以外触发的特定事件上设置观察者。服务器

下面咱们简单的实现一个EventEmitter类来实现观察者模式:app

class eventEmitter {
  constructor() {
    this.eventObj = {}
  }
  on(evName, fn) {
    this.eventObj[evName] = this.eventObj[evName] ? this.eventObj[evName].concat(fn) : [fn]
  }
  emit(evName, ...params) {
    for (let fn of this.eventObj[evName]) {
      fn.apply(null, params)
    }
  }
}
复制代码
const event = new eventEmitter()
event.on('error', err => {
  console.log(err, 1)
})
event.on('error', err => {
  console.log(err, 2)
})

event.emit('error')
复制代码

上面的例子中,error事件做为一个被观察对象,回调函数是观察者,当触发on事件时,观察者会被加入到一个队列中,当error事件触发时,回调函数(观察者)会受到通知,而且依次被调用。异步

职责链模式

职责链模式是Node.js世界中不少人使用过的一种模式,他们甚至没有意识到这一点。函数

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。post

下面是一个很是基本的实现这个模式,你能够看到在底部,咱们有四个可能的值须要处理,可是咱们不在意谁来处理它们,咱们只须要,至少,一个函数来使用它们,所以咱们只是寄给链,让链中的每个函数决定他们是否应该使用它或忽略它。ui

function processRequest(r, chain) {

    let lastResult = null
    let i = 0
    do {
     lastResult = chain[i](r)
     i++
    } while(lastResult != null && i < chain.length)
    if(lastResult != null) {
     console.log("Error: request could not be fulfilled")
    }
}

let chain = [
    function (r) {
     if(typeof r == 'number') {
         console.log("It's a number: ", r)
         return null
     }
     return r
    },
    function (r) {
     if(typeof r == 'string') {
         console.log("It's a string: ", r)
         return null
     }
     return r
    },
    function (r) {
     if(Array.isArray(r)) {
         console.log("It's an array of length: ", r.length)
         return null
     }
     return r
    }
]

processRequest(1, chain)
processRequest([1,2,3], chain)
processRequest('[1,2,3]', chain)
processRequest({}, chain)
复制代码

输出的结果是:

It's a number:  1
It's an array of length:  3
It's a string:  [1,2,3]
Error: request could not be fulfilled
复制代码

使用例子

在咱们常常使用的开发库中,这种模式最明显的例子是ExpressJS的中间件。使用该模式,其实是在设置一系列函数(中间件),这些函数计算请求对象并决定对其运行一个函数或者忽略它。能够将该模式看做上面示例的异步版本,在这个版本中,不是检查函数是否返回值,而是检查将哪些值传递给它们调用的下一个回调。

var app = express();

app.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next(); //call the next function on the chain
})
复制代码

中间件是这种模式的一种特殊实现,由于能够认为全部中间件均可以完成请求,而不是链中的一个成员。然而,其背后的原理是同样的。

下面咱们来本身简单实现一个中间件函数

class App {
  constructor() {
    this.middleware = []
    this.index = 0
  }

  use(fn) {
    this.middleware.push(fn)
  }

  exec() {
    this.next()
  }

  next() {
    if (this.index < this.middleware.length) {
      const fn = this.middleware[this.index]
      this.index++
      fn.call(this, this.next.bind(this))
    }
  }
}

const app = new App()

app.use(function (next) {
  console.log(1)
  next()
})

app.use(function (next) {
  console.log(2)
})

app.exec()
复制代码

输出结果

1,2
复制代码

当使用use函数时,在中间件队列中加入回调函数,在执行时,能够调用next()来进入下一个中间件。

上一篇咱们理解了IIFE工厂模式还有单例模式

关注做者github,第一时间得到更新。

相关文章
相关标签/搜索