这是一篇关于后端 JavaScript 开发的指引,如果你对 JavaScript 的认识仍然停留在前端开发的话,你需要更新自己的知识体系了。
- 用 NPM 来做项目管理和包维护
- 用 Grunt 来做代码格式修整、Lint 和其他自动化任务
- 用 CoffeeScript 方言写更友好的 JavaScript
- 必不可少的几个库
- 开发 C++ addons 增强和扩展 Node.js
- Node.js 配置管理方式
- 开发内部 Service 推荐的部件
- Forever: Daemon 管理工具
- Node.js Cluster
- JavaScript 开发常见问题
- 更多相关参考
Web 开发框架从以前流行的 LAMP/LEMP 架构逐渐转移到 Ruby on Rails 和 Python Django 架构之上。但最近有另外几种的架构变得成熟了:比如 Golang, Clojure/leiningen, Node.js 。这三者之中我更关注 Node.js。原因是它的生态更加完善。所以最近的几个项目都是用它来实现的。Node.js 既适合做后端 API 的粘合,也适合给终端用户提供 API 。
用 NPM 来做项目管理和包维护
Node.js 的每个项目都应该有一个 package.json 配置文件。其中包含了这个项目或者库的信息和所依赖的库。一个相对完成的 package.json 文件像这样:
{ "name": "my-app", "version": "0.1.0", "private": true, "description": "My app and service", "main": "app.js", "scripts": { "start": "forever start app.js", "test": "mocha test" }, "dependencies": { "coffee-script": "=1.4.0", "express": "=3.0.6", "mocha": "=1.7.4", "underscore": "=1.4.3", "forever": "=0.10.0", "async": "", "grunt-beautify": "" }, "repository": "", "author": "Bruce Dou", "license": "BSD" }
这个文件可以在你的项目目录下执行 npm init 来按指引创建。注意其中的 scripts 部分,这里可以创建很多自定义命令。比如如此配置之后,npm start 会执行 forever start app.js 命令。
用 Grunt 来做代码格式修整、Lint 和其他自动化任务
Grunt 是一个命令行任务自动化工具。它包含了很多有用的插件。可以实现代码美化、自动化部署、自动生成 sprit、自动创建项目模板、自动压缩前端代码、自动编译 coffescript 等等你能想到的任务。假如你找不到自己需要的功能,还可以自己开发 Grunt 插件来实现。它的配置文件是位于项目根目录的 grunt.js 。一般项目都需要的功能是代码美化和 Lint,配置文件象这样:
module.exports = function (grunt) { // Project configuration. grunt.initConfig({ beautify: { files: ['grunt.js', '*.js', 'lib/*.js'] }, lint: { files: ['grunt.js', '*.js', 'lib/*.js'] }, beautifier: { options: { indentSize: 2 }, tests: { options: { indentSize: 4 } } }, jshint: { options: { curly: true, eqeqeq: true, immed: true, latedef: true, newcap: true, noarg: true, sub: true, undef: true, boss: true, eqnull: true, browser: true }, globals: { jQuery: true, Drupal: true, Backbone: true, _: true, app: true } } }); grunt.loadNpmTasks('grunt-beautify'); //grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.registerTask('default', 'beautify'); };
添加了配置文件之后可以在项目目录运行 grunt 命令自动执行任务串。grunt beautify 可以执行子任务。
用 CoffeeScript 方言写更友好的 JavaScript
CoffeeScript 提供了类似于 Ruby 的语法,简化了 JavaScript 的书写,简化了 JavaScript 中实现类和类的继承的实现。你不必再自己繁琐得通过 prototype 和 constructor,call,apply 实现类的继承。 无论用 VIM 还是 Sublime Text 都可以找到对应的实时转换工具,在写 CoffeeScript 的同时就能看到对应的 JavaScript。
必不可少的几个库
underscore
underscore 是写 JavaScript 必不可少的库。提供了一些非常常用的方法,这些方法不仅使用方便,而且提高了代码的可读性。:
用 _.each 代替 for 循环,比如:
var contents = []; for(var i in msgs) { contents.push(msgs[i].content); }
可以改写为:
var contents = []; _.each(msgs, function(el) { contents.push(el.content); });
其他常用的方法还有:
_.map 用来对数组元素进行批量转换
_.reduce 用来将数组元素合并为结果
_.pluck 用来取对象数组中的子元素,返回包含对应子元素的新数组
_.filter 用来有选择性的取数组中的某些值,返回包含符合条件的值的新数组
_.mixin 用来增加自定义函数
_.chain 用来实现函数式编程:
_.chain([1,2,3]) .map(function(v) {return v * 2;}) .reduce(function(total, v) { total += v}, 0) .value();
Async
Async.js 对常用的流程控制模式进行了封装,比如并行处理、Pipeline等等:
async.parallel 并行处理,整体等待最慢的函数返回,常用作多个后端请求的聚合或者并行处理:
... // construct call functions var callItems = []; function make_query_func(json) { return function (callback) { callBackService(json, callback); }; } _.each(messages, function (el, i) { callItems.push(make_query_func({ vmsg: messages[i].vmsg, cmsg: messages[i].cmsg })); }); // execute the call functions parallelly async.parallel(callItems, function (err, results) { main_cb(results); });
async.waterfall 可以用来将大量嵌套的函数顺序执行,前一个函数的结果作为下一个函数的参数。用它之后你的代码里将不会再出现过深的 callback 嵌套:
// old way ... var result = function(uid, callback) { get_user_ids(uid, function(err, user_ids) { get_content(user_ids, function(err, content) { content_clean(content, function(err, clean_content) { callback(err, clean_content); }); }); }); } // new way ... async.waterfall([get_user_ids, get_content, content_clean]);
开发 C++ addons 增强和扩展 Node.js
这意味着你不必担心某些逻辑的性能,也不必担心和其他框架的继承。因为所有语言或者开发栈都会提供 C/C++ 的接口。
Node.js 配置管理方式
有两种配置方式,config.js 或者 config.json 假如以 config.js 作为配置文件:
// config.js exports.config = {'a':'a val', 'b': 'b val'}; // app.js var config = require('./config').config;
如果以 config.json 作为配置文件:
// config.json {'a': 'a val', 'b': 'b val'} // app.js var config = JSON.parse(fs.readFileSync(process.cwd() + '/config.json'));
开发内部 Service 推荐的部件
- 进程统计信息,比如 uptime, memory usage, heap size, connection number, 处理的请求数量等等。这样便于和监控系统对接
- RESTful apis,推荐以 RESTful JSON 格式作为内部通信协议,这其实对大部分应用完全足够,而且容易调试。
- 配置文件。将可能会发生变化的变量放到配置文件里。
- Service Level Agreement。比如超过 100ms 的请求报错而不是继续等待返回信息。
Forever: Daemon 管理工具
既然 Node.js 是长时间运行的后台进程,缺不了进程管理工具。Forever 就是为了实现对 Node.js 的 daemon 进程进行管理的工具。常用命令:
forever start app.js 启动进程
forever restart app.js 重启进程
forever list 列出后台进程,并且列出 log 文件,可以方便的及时查看 log 文件内容。
Node.js Cluster
Node.js Cluster 是为了利用多核 CPU 的计算能力, 并且子进程可以共用同一个端口:
var cluster = require('cluster'); if (cluster.isMaster) { //start up workers for each cpu require('os').cpus().forEach(function() { cluster.fork(); }); } else { //load up your application as a worker require('./app.js'); }
JavaScript 开发常见问题:
JavaScript 是传值还是传引用?
简单说:如果参数为一个对象则为传引用,如果参数为变量或者函数则为传值。
如何用 GDB 调试自己开发的 Node.js C++ addons?
gdb –args nodejs script.js
如何捕获 uncaughtException 并打印详细错误信息?
在进程级别捕获异常:
process.on('uncaughtException', function (e) { console.trace('Error: '.red + e); console.trace(e.stack); //process.exit(); });
如何优雅结束进程?
Node.js 中对接收的 POSIX 信号做自定义操作很方便,进行进程结束前的清理工作:
process.on('SIGINT', function () { // wait connections to close process.exit(); console.log("gracefully shutting down from SIGINT (Crtl-C)".yellow); });
如何使用 Array 和 Object
对于列表类的信息,比如用户列表推荐使用 Object 而不是 Array:
var a = []; a[1000] = 1; //a is an Array which length is 1001
如何聚合多个后端服务并提供 SLA ?
用 Node.js 可以非常简单的实现对返回所用时间进行控制。比如在之前例子代码 async.parallel 的请求中设置超时定时器。对多个处理进程并发请求取其中时间最短的结果,这样可以保证返回时间都保持很短。
什么时候使用 process.nextTick() ?
- 重量使用 CPU 的函数中释放 CPU 给其他任务
- 将执行放到下一个 tick ,等待初始化
更多相关参考:
http://nodejs.org/
https://npmjs.org/
http://coffeescript.org/
http://underscorejs.org/
http://nodejs.org/api/addons.html
http://expressjs.com/
https://github.com/caolan/async
http://howtonode.org/understanding-process-next-tick
http://book.mixu.net/ch7.html
http://gruntjs.com/