《node.js开发指南》(中文)观后感
发布于 8 年前 作者 DoubleSpout 58070 次浏览 最后一次编辑是 4 年前

最近在当当网上买了一本《node.js开发指南》,从学习node.js到现在看的第一本中文教程,也算献出了自己处子之身啊,哈哈。前后大约花了4,5个小时通读了node.js部分,附录部分只是略过了,谈一下感想把。

1、本书的定位: 就像书中的前言部分所述,确实是针对node.js还未入门的初学者准备的,但是有一个前提,如果之前没写过像php等后端的语言读本书可能有点迷茫。所以本书的定位人群应该是对后端脚本语言有过一定开发经验,并且熟悉javascript语法的人。

2、本书的组成部分: 个人感觉分为3块,前面一大部分是node.js介绍安装,中间一块和最后node.js的核心API介绍和热门模块,最后是一个简单的微博web应用,主要介绍了模板和mongodb的增删改查。个人感觉前面node.js安装和介绍有点多,而实际应用有点少。

3、总体感觉: 本书内容很丰富,很多知识面都谈到了,但是不够深入,真的很适合初学者。API的介绍也不够详细,仅仅介绍了几个主要的,比如cluster模块的多进程通信、buffer模块的深度应用事例以及net模块的TCP服务器都没有深入。至少是第一本中文的node.js教程,希望大家多多支持啦。

4、补充说2句(哈哈,有没有点领导的风范) 个人感觉书中有不妥的地方,列举几处,希望作者能够在改版中参考下,代表个人意见哦,可能说的不对。

A)书中75页,httprequest.js,期中代码片如下:

var contents = querystring.stringify({
  name: 'byvoid',
  email: 'byvoid[@byvoid](/user/byvoid).com',
  address: 'Zijing 2#, Tsinghua University',
});

var options = {
  host: 'www.byvoid.com',
  path: '/application/node/post.php',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length' : contents.length
  }
};

大家注意这个contents.length,当contents都是英文及半角符号时没有问题,但是如果有中文,这样计算 Content-Length 会出现问题,应改为:Buffer.byteLength(contents, ‘utf8’)。 当然这里作者使用了querystring.stringify 方法,能够自动将中文转义,所以不存在这个问题,这里可能是个小坑,要注意哦

B)书中第115页,代码片段如下:

   // 讀取 users 集合
    db.collection('users', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      // 爲 name 屬性添加索引
      collection.ensureIndex('name', {unique: true});
      // 寫入 user 文檔
      collection.insert(user, {safe: true}, function(err, user) {
        mongodb.close();
        callback(err, user);
      });
    });

这边我感觉是作者的一个失误,因为这个代码片段属于User.prototype.save,是用户注册执行的方法。这里每注册一个用户就为集合users建立一次name的唯一索引实在欠妥,应该在app启动时初始化对数据库建立索引,并且应该先判断索引是否已经存在,而不必每次插入数据库创建一遍索引。

C)同样是上面的一段代码,我们看期中绿色部分 大家注意到了{safe:true}这个设置了吧,作者没有说明这个{safe:true}的含义,我在这边简单介绍下。 1、不加{safe:true}表示我们mongodb客户端向数据库抛出这句insert语句后,立刻执行回调函数,而不去关心是否真的插入成功,类似往河里扔石头,扔了就告诉妈妈,我扔石头了。 2、加上{safe:true}表示mongodb数据库已经成功执行这条insert语句,并且插入完成或者报错后执行回调函数,类似往河里扔石头,看见水花起来了,或者不小心砸到河上的船了,然后告诉妈妈。

D)书中114页,这样写到: req.flash是express提供的一个奇妙的工具,通过它保存的变量只会在用户当前和下次的请求中被访问… … 作者如此描述,感觉req.flash很厉害,瞬间让我产生了好奇,因为当初我写 rrestjs 框架时借鉴了很多expressjs的源代码,并没有发现有req.flash,翻看expressjs3.0的源码发现没有req.flash的方法,于是在connect-flash模块中找到了它,贴下它的源码:

var format = require('util').format;
... ...
function _flash(type, msg) {
  if (this.session === undefined) throw Error('req.flash() requires sessions');
  var msgs = this.session.flash = this.session.flash || {};
  if (type && msg) {
    // util.format is available in Node.js 0.6+
    if (arguments.length > 2 && format) {
      var args = Array.prototype.slice.call(arguments, 1);
      msg = format.apply(undefined, args);
    }
    return (msgs[type] = msgs[type] || []).push(msg);
  } else if (type) {
    var arr = msgs[type];
    delete msgs[type];
    return arr || [];
  } else {
    this.session.flash = {};
    return msgs;
  }
}

简单分析下: 如果没有开启session功能,则直接抛出异常,也就是说这个req.flash是存放在session里的,所以并没有像作者写的那么神奇,顿时很失落。 1.如果type和msg都传参了,如果参数长度大于2,则执行format函数将msg字符串处理一下。然后往msgs[type]数组中插入msg字符串。 2.如果只有一个参数,则删除这个msgs[type],返回改数组,这里就是作者说的一次性的关键。 3.清空msgs对象,返回整个msgs对象。 其实这个函数还是很巧妙的,保证了req.flash的一次性使用,节能环保,呵呵,当然没有作者说的那样神奇啦。

E)书中第124页,代码片段如下:

function Post(username, post, time) {
  this.user = username;
  this.post = post;
  if (time) {
    this.time = time;
  } else {
    this.time = new Date();
  }
};

作者直接使用 new Date()方法存放了时间。在数据库存放时间格式上一直很有争议,我个人一直是存时间戳的,原因有如下几个: 1.存储空间的节约,时间戳要比格林威治时间存储空间更小 2.便于比较和排序 3.如果有其他语言比如php也要共享这部分数据,只需要把js生成的时间戳除以1000既可以转化为时间格式了,php的time()返回是以秒为单位的时间戳。 所以建议这边作者改为 Date.now()

最后当然是广告时间了: 看到作者为了判断除了首页,其他页面都需要用户登录写了如下代码片段:

app.get('/reg', checkNotLogin);
... ...
app.post('/reg', checkNotLogin);
... ...
app.post('/login', checkNotLogin);
... ...
app.get('/logout', checkLogin);
... ...
app.post('/post', checkLogin);
... ...

如果应用的页面越来越多,这样的代码会越来越来长,如果你使用了 rrestjs 框架,情况就完全不一样了,我们看下使用rrestjs框架的实现,我们看下代码片段:

  var http = require('http'),    
  rrest = require('rrest'),
  server = http.createServer(rrest(function (req, res) {
  var noCheckUrl = ['index', 'other'];//rrestjs会默认把'/'认为'/index'
  if(!~req.path[0].indexOf(noCheckUrl) && !isLogin(req, res)) return res.redirect('/login')
  //normal routes
 })).listen(rrest.config.listenPort);

只需要在入口处判断哪些请求url需要验证登录即可全部搞定,无需重复的代码拷贝。

rrestjs框架地址:http://www.rrestjs.com/

末尾要给作者提一下啊,书中第6页,node.js和php+nginx对比测试的数据我的名字打错 ,应该是snoopyxdy,而不是snoopyxd。而且测试的数据没有把硬件以及node,linux,php的版本号以及网络环境等相关因素写进去,不够严谨。

另外关于nginx+node.js的小建议: 之前我曾经开4个node.js绑定4个CPU然后nginx开4个进程,绑定4个CPU,结果悲剧了,压测一直卡死,原来是node.js和nginx抢CPU造成的,后来楚汉分界,nginx用前2个CPU,而node.js拿后两个cpu。另外nginx也像汽车一样,要先热车,重启后先随便压测几次,然后过一会水温就上来了,就可以踩油门了,哈哈。

最后希望大家多多支持中文的node.js教程,都去买正版书籍把,《node.js开发指南》真是一本难得中文node.js好教材!推荐一个。记得哦,买正版的,别去下D版的pdf啊!

我的博客原文地址

45 回复

看来LZ看的很仔细!

a 计算 的是编码后的长度,理论是应该是没有问题的

给你看下测试结果吧,当然前提是UTF-8 测试代码:

var engStr = 'abcdefgX'
var chineseStr = 'abcdefg我'
console.log("engStr's length :" + engStr.length);
console.log("chineseStr's length :" + chineseStr.length);

console.log("engStr's byte length :" + Buffer.byteLength(engStr, 'utf8'));
console.log("chineseStr's byte length :" + Buffer.byteLength(chineseStr, 'utf8'));

运行结果:

[root@localhost test]# node len.js 
engStr's length :8
chineseStr's length :8
engStr's byte length :8
chineseStr's byte length :10

可见当有中文的时候 Buffer.byteLength 是不同的

哈哈,期待你的大作登场啊

querystring.stringify编码后的,你这么 仔细看书,怎么 没看这个 前提

var querystring=require(‘querystring’);

var o={name:‘abcdefg我’}; var s=querystring.stringify(o);

console.log(s.length); //21 console.log(Buffer.byteLength(s)); //21

擦,没注意,是我错了,谢谢啊

嗯,是我没仔细,修改了文章了。

http://www.rrestjs.com/ 进不去, 提示备案问题ei…

支持下楼主。。。想请问下:1:对于路由分发的,您上面的方法确实不错,但是在express 下,有什么好的建议吗?我想到的是将各个例如route.index,route.user等这些都模块化出来,然后根据url 去require,就是类似您源码中的那种方法。还有别的思路吗??2:楼主您有空也在你的博客中发表下你写这个框加还有你那个站的思路啊教程等,好让我们这些小白学习下

楼主分明是来给rrestjs打广告来的。。。娃哈哈 <hr> 那个req.flash()是express 2.x中有的,在Migrating from 2.x to 3.x中有说明:

  • req.flash() (just use sessions: req.session.messages = [‘foo’] or similar)
  • connect-flash can be used as middleware to provide req.flash()

https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x

妹啊,我又得挂到国外服务器上去了

这都被看穿了,nae太不给力了,一直跳备案,准备转投国外去了,有好的没啊?

这不需要攻略啊,nginx反向代理配置一下就搞定了

好多地方跟楼主想的一样。

@jaicc 最简单的方法,app.all,然后匹配*,这样所有请求都会到这个入口了

@snoopy github上的 courser也是干类似 工作的,感觉rrestjs重复了。。。

@snoopy 遇到socket.io就很麻烦了

作用是差不多的,我之前也在expressjs上修改过类似功能,后来干脆自己重新建了个项目rrestjs呵呵,谢谢捧场哦~

哈哈,狗熊所见略同。。

同样期待~ 最好能比这本书稍微在深入一点点

其实我很期待楼主你能写本nodejs的书啊~

。。。还没这个水平啊,呵呵,就写写水文吧~

多謝樓主支持,寫錯樓主的名字實在抱歉,我會向出版社交涉確保在下次重印前修正。

req.flash

之前 也用restjs 开发过相关应用, 想比 express 确实有改进,但是api文档不是很详细。

关于LZ 所说的E new date() 与 Date.now() 具体有什么区别还不是很明白,可否详解下?

或者给个详解地址。

呵呵,仅代表个人意见啊,你酌情参考下吧~

苏大大有什么问题啊?

额,new date()返回的是一个date对象的实例,可以有getYear,getMonth等等的方法和一些属性,而Date.now()直接返回一个数字时间戳。 在数据库存储方面就是我以上说的几种区别,存储大小,方便比较,方便共享数据。

@snoopy

我在 普通的JS里面 Date.now() 是一个时间戳。

但是, 我如果 将 mongoose 中 default 设置为 Date.now ,在MongoDB中保存的数据竟然会是new Date() 的结果. 这个是 mongoose的原因还是 MongoDB?

@snoopy

我在 普通的JS里面 Date.now() 是一个时间戳。

但是, 我如果 将 mongoose 中 default 设置为 Date.now ,在MongoDB中保存的数据竟然 会是new Date() 的结果. 这个是 mongoose的原因还是 MongoDB?

PS:

发现论坛一个bug , 为什么上面的回复 没换行 就显示不出来了……

@jonzlx 应该是mongoose的缘故,我不用这个的,还是用mongo-native那个模块,

mongoose就是基于它的封装。我给你看运行结果:

[root@localhost ~]# mongo
MongoDB shell version: 2.0.5
connecting to: test
use test
switched to db test
db.test.insert({time:Date.now()}) 
db.test.insert({time:Date.now()}) 
db.test.find()
{ "id" : ObjectId("500dc302bafc11f7f2ecf253"), "time" : 1343079170501 }
{ "id" : ObjectId("500dc30abafc11f7f2ecf254"), "time" : 1343079178438 }
db.test.drop()
true

@snoopy

了解了, 谢了~

Blockquote

别去下D版的pdf啊!

Blockquote

这算是提醒咩?

@wenbob 利用comet的话nginx也没问题的

Buffer.byteLength(contents, ‘utf8’) 这个地方我遇到过,在做模拟登陆的时候,从说我post的值不对,当然我没用querystring.stringify。

最后通过调试。。才发现是长度问题,少传了不少东西。。【其中有中文】。。

哎。。

这个地方是很坑爹的,我还碰到过更坑爹的。当时和php做接口,node直接转发浏览器的http请求,由于带有gzip头,所以发到php那面的nginx上,返回都是乱码,查了半天错误,最后发现就是gzip的http头在捣鬼,最后去掉gzip头转发一切正常。

@snoopy 。。。。gizp这个我也遇到过,远程读取http的js文件,回来的全是gzip的乱码- -。根本用不了,也查了好久。

@xiaojue 呵呵同情啊~都被坑过。

@snoopy 用二级域名先吧。

看来,是高手啊,这在读中

多谢分享,这书也在读

感谢分享!读的时候也发现了一些类似的问题,自己google解决了,但是感觉在google的过程中收获也很大!

在学习中,也遇到了layout模板的问题,在express3中layout在3.0中取消了需要安装个express-partial的东西, 另外也可以设置模板的后缀,方便开发 app.set(‘views’, __dirname + ‘/views’); app.engine(’.html’, require(‘ejs’).__express); app.set(‘view engine’, ‘html’); app.use(partials());

“dependencies”: { “express”: “", “ejs”: "”, “express-partials”: “", “mongodb”: "” , “connect-mongo”: “", " connect-flash":"” }

我是用类似这样的方法做的,写一个router.js文件,然后在app.js中调用那个exports函数:

var util = require("util");

var index = require("./controller/index");
var user = require("./controller/user");
var api = require("./controller/api");
var customer = require("./controller/customer");
var district = require("./controller/district");

var backboneGroup = [ user.isLoggedIn, index.index ];

var get = {
    "/" : backboneGroup,
    "/index" : backboneGroup,

    /** 用户相关 */
    "/user/login.cbw" : user.login,
    "/user/logout.cbw" : user.logout,
    "/user/getuserinfo.cbw" : [ user.isLoggedIn, user.getUserInfo ],

    /** API相关 */
    "/api/motto" : api.motto,

    /** 客户相关 */
    "/api/customer/mine/:page" : [ user.isLoggedIn, customer.myCustomers ],
    "/api/customer/public/:page" : [ user.isLoggedIn, customer.publicCustomers ],
    "/api/customer/blacklist/:page" : [ user.isLoggedIn, customer.blacklistCustomers ],
    "/api/customer/absorb/:customerid" : [ user.isLoggedIn, customer.absorb ],
    "/api/customer/release/:customerid" : [ user.isLoggedIn, customer.release ],
    "/api/customer/toBlack/:customerid" : [ user.isLoggedIn, customer.toBlack ],

    /** 地区相关 */
    "/api/district/allprovince.cbw" : [ user.isLoggedIn, district.allprovince ],
    "/api/district/city/:province" : [ user.isLoggedIn, district.getCity ],

    /** JS 整合 */
    "/api/js.cbw" : api.getJavascript
};

var post = {
    "/user/chklogin.cbw": user.chklogin
};

/**
 * 创建路由
 * @param app
 */
exports.createRoute = function(app) {
    var r = [ get, post ];
    for(var I = 0; I < 2; I++) {
        var arr = r[I];
        for(var key in arr) {
            if(typeof arr[key] === "function") {
                if(I === 0) app.get(key, arr[key]);
                else app.post(key, arr[key]);
            }
            else if(util.isArray(arr[key])) {
                for(var i = 0; i < arr[key].length; i++) {
                    if(I === 0) app.get(key, arr[key][i]);
                    else app.post(key, arr[key][i]);
                }
            }
        }
    }
};
回到顶部