JavaScript, ECMAScript, Node.js与Koa

JavaScript算是我写的比较多的语言了,之前一直在写偏向用户能看见的这一部分,这一段时间接触了Node.js的后端部分,有些碎片性的东西想写下来当个笔记,没经过很严谨的考证,不保证没有事实性错误。

一些历史

更新:关于这部分历史建议看《JavaScript 高级程序设计(第4版)》

刚接触Node.js的时候根本看不懂文档,什么assert、http、fs什么的都是啥,只知道安装node之后有个npm在打包vue应用的时候能用到。再后来听说Node.js还能拿来写服务器,等于用一种语法同时写了前端页面和后端接口,进而接触express。又觉得这文档写的咋也看不懂,咋就写几行代码就有一个服务器了。过了一段时间有个叫Koa的东西火了,说是能减少回调函数的写法,一看文档又是看不懂,怎么才一页就写完了。从这个阶段过了很长时间,中间在各种论坛网站里搜过看过各种答案,又是举例子又是打比方的,都不太能看懂,就想按自己的理解和逻辑把这一过程的一些经验记录下来。

先说说JavaScript,这个就是最开始Brendan Eich用一个周末设计出来的语言,公司为了凑Java的热度把名字定为了JavaScript,这点可能大家都懂。然后随着网页开发的技术的发展,当时的JavaScript在各种浏览器里有各种各样的兼容技巧,兼容各个浏览器成了写网页的开发者的噩梦,为了解决这个问题,有个人写出了jQuery这个库帮开发者集中精力在自己的代码而非浏览器兼容上。

而ecmascript则是JavaScript的一种标准,它是当时跑在浏览器里的JavaScript的一个标准,ES6中的ES就是指这个,这个组织就像给app升级一样升级这门语言。

Node.js

浏览器继续向前发展,在这个过程中,谷歌的浏览器chrome在市场上站住了脚,其中跟chrome的js引擎v8优异的性能可能有关,所以有人利用v8引擎加上了点周边设施做出来了一个东西叫node.js,这个东西在官网上写着"Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine"刚接触node.js也许不理解这个写的是什么。简单点来讲,node.js可以让js脱离浏览器这个载体运行了,这是一个很好的功能,我认为是可以把node.js当作python这种语言重新认识。

前面说了,我认为node.js让JavaScript脱离浏览器运行是一个很好的功能,为什么这么说。一个最简单的情况,想用js读一个文件的内容,如果在浏览器里用js读,那不可避免地要跟DOM打交道,这就有点麻烦了。而在node.js中,只需要

const fs = require('fs')

fs.readFile('/path/to/file.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

就可以很简单的读取文件内的内容。那如果文件不是一个txt文件而是一个js文件呢?读取js文件之后对其进行一些词法分析从而达到比如压缩js、tree-shaking又或者把js转译成低版本浏览器也可以运行的代码等等。借助这种读取文件的功能,使得Babel、Webpack这种工具大放异彩。

Koa

上面那些东西是我对Node.js的周边生态的理解,因为我最近在用koa,那么以koa为例写点我对目前Node.js做后端的一些经验。

Node.js里有一个http包,利用这个http可以创建出服务:

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('hello world');
}).listen(3000, () => {
  console.log('server start at 3000');
});

但是这种写法的扩展性不是很好,比如路由:

const http = require('http');

const server = http.createServer((req, res) => {
  const { url } = req
  if (url === '/') {
    res.write('hello world');
  }
  if (url === '/person') {
    res.setHeader('Content-Type', 'application/json')
    res.write(JSON.stringify({
      name: '小明',
      age: 18
    }))
  }
  res.end()
}).listen(3000, () => {
  console.log('server start at 3000');
});

这只是一个处理GET请求路由功能的情况,实际上肯定还有很多功能如Cookie、Auth等逻辑要在这里写,这样会造成代码很混乱。所以,类似XMLHttpRequest,很多人为了效率都会选用如jQuery里的ajax或是其他如axios的库来进行网络请求。同样的,也有很多库可以让创建服务更加方便,比如之前提到的express,利用express可以用几行代码很方便地生成一个服务,并利用一种叫中间件的东西把逻辑放在一个个中间件里面让代码可读性更好。但是express早期使用和node.js一样基于回调的写法,使得回调地狱的情况并不能避免,所以Koa就出现了。它用async/await语法让代码更加可读:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log('1-start');
  await next();
  console.log('1-end')
})

app.use(async (ctx, next) => {
  console.log('2-start');
  ctx.body = 'Hello World';
  console.log('2-end');
}).listen(3000);

如果比较一下Koa和express的官网不难发现,express里面有现成的View层可以用,如果不需要这个View层而且想折腾的话就可以选择像Koa这种除了处理请求之外啥都没有的库。Koa就是一个只对Node.js中的http包进行了一些包装,暴露出一个context对象用于在各个中间件中传递,这个context对象里有请求和响应对象的一些代理,所以对这个contect对象进行操作相当于操作koa的reqest和response对象。这里提到了中间件这个概念,所谓中间件其实就是提供一个函数,这个函数Koa会帮你保管起来,在适当的时候调用,并没有那么神秘。koa的网站最后有一个middleware的网页,初看会让人不知道是干啥的,这个还要从koa的极简来看,因为除了处理请求和响应并没有其他额外的功能,所以其他的功能只能另外找插件来做。

又由于Koa只是对http包做了一些包装,所以像原生node处理起来比较费事的如路由和处理POST请求等问题对于koa也是存在的,所以需要借助一些别的库,比如路由有@koa/router、处理请求体有koa-bodyparser等。借助这些库,可以让Koa的代码结构更加层次化,不会是一团糟。比如有如下代码:

// app.js
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const router = require('./routes')

const app = new Koa();

app.use(bodyParser())
    .use(router.routes())
    .use(router.allowedMethods())
    .listen(3000, () => {
        console.log('server is running at localhost:3000');
    });
// routes.js
const Router = require('@koa/router');
const router = new Router();
const connection = require('./database')

router.get('/', async (ctx, next) => {
    ctx.body = 'server ok';
    await next();
})

router.get('/class', async (ctx, next) => {
    var res = await connection('SELECT * FROM class')
    ctx.set('Content-Type', 'application/json')
    ctx.body = JSON.stringify(res)
    await next();
})

module.exports = router;
// db.js
var mysql = require('mysql2');
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'news'
});

module.exports = connection;

这样让app.js就负责中间件和启动服务,routes.js就负责接受请求和查询数据库,db.js就负责数据库的配置,这样来是不是比把代码写在一起更适合阅读呢。实际上,如果应用更复杂,比如请求一个用户评论的接口,这个接口需要对用户进行身份认证,这时routes.js里既然处理请求,又要验证身份,又要进行评论操作,对路由层面来说太重了,这时要把路由拆开来,分成路由-控制器-服务的三层,这时的app.js的代码基本不变,路由里的代码可能是这样的:

// routes.js
const controller = require('controller')
const Router = require('@koa/router');
const router = new Router();

router.post('/comment/:id', controller.user.comment)

module.exports = router;
// controller.js
const Service = require('service')

class UserController extends Controller {
  async comment(data) {
    const res = await service.comment.create(data);
    // 设置响应内容和响应状态码
    ctx.body = { id: res.id };
    ctx.status = 201;
  }
}

看见没有,这个时候路由器就只是把请求转发给相应的控制器,在控制器里要么返回响应,要么进一步转发给service。如果你用过thinkphp之类的框架,这个结构对你来说应该就有点熟悉了。

还有一个地方没提到,为什么我这里的实例代码变成了类表示的,其实这里我改动了egg.js的controller层的一段代码。因为如果真等到需要把路由和控制器分开的时候,大概率是需要一个框架来规定各个组件落到何处的,这时可以试试egg.js这个框架。

总结

这篇文章比较碎,但把node.js的历史顺带JavaScript的历史都提了一下,虽然了解技术发展的历史不能让我们直接掌握一门技术,但对为什么技术这么发展是有帮助的。

然后对我最近一直在了解的Koa为例介绍了一下用Node.js进行服务开发的一些理解。

参考

  1. Koa源码浅析,是一篇不错的介绍Koa实现的文章
  2. 每日一个npm包 —— koa-compose,对上一篇文章没有细讲的koa-compose中间件进行分析,看完这篇就会更进一步了解中间件的原理
  3. 一次 HTTP 传输解析从node.js层面上讲解http请求从接受到响应的步骤
  4. egg.js文档层级划分很清晰的一个框架,适合折腾完Koa之后实际用的一个框架
posted @ 2022-03-24 21:16  学学代码记记笔记  阅读(83)  评论(0编辑  收藏  举报