鸟瞰Nodejs

1,Node的包管理器:npm; 安装node环境时会自动安装。

本地模式获取一个包:npm install [package_name]

此时包被安装到当前木的node_modules子目录下。

全局模式获取一个包;npm install -g [package_name]

全局模式安装的包不能直接通过require使用,但通过npm link命令可以在当前目录创建一个指向全局包的链接。比如,如果已经通过 npm install -g express 安装了 express, 这时在工程的目录下运行命令:

npm link express

./node_modules/express -> /usr/local/lib/node_modules/express

2,第一个程序。

创建helloworld.js文件,并写入:console.log("Hello World");

打开cmd,进入hellodworld.js所在目录,运行:node hellowrold.js

3,命令行工具node。

查看帮助信息:node --help

REPL模式:即时求值的环境,类似于浏览器的控制台,直接输入node。

4,建立Http服务器。

运行以下代码,然后浏览器打开 localhost:3000

//app.js

var http = require('http');
http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write('<h1>Node.js</h1>');
  res.end('<p>Hello World</p>');
}).listen(3000);
console.log("HTTP server is listening at port 3000."); 

如果运行app.js后,再修改app.js并不会实时反映到浏览器端,必须退出重新运行。

如果想实时看到修改后的效果,可以采用supervisor。

使用方法:

安装supervisor: npm install -g supervisor

使用supervisor运行app.js: supervisor app.js

5,读取文件。

5.1异步读取文件:

//readfile.js

var fs = require('fs');
fs.readFile('file.txt', 'utf-8', function(err, data) {
  if (err) {
    console.error(err);
  } else {
    console.log(data);
  }
});
console.log('end.');

可以看到,是通过回调函数实现异步。调用时只是将I/O请求发送给了操作系统,然后继续执行后面的代码,执行完成后进入事件循环监听事件。当fs接受到I/O请求完成的事件时,事件循环会主动调用回调函数。

5.2同步读取文件:

//readfilesync.js

var fs = require('fs');
var data = fs.readFileSync('file.txt', 'utf-8');
console.log(data);
console.log('end.'); 

6,事件。

//event.js

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
 
event.on('some_event', function() {
  console.log('some_event occured.');
});
 
setTimeout(function() {
  event.emit('some_event');
}, 1000); 

7,node.js的事件循环机制。

node.js程序本身就是一堆事件的回调函数。程序的入口是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会有异步请求或直接emit事件,执行完毕后再返回事件循环,事件循环会检查事件队列中的未处理事件,直到程序结束。

8,模块和包(Module 、Package)。

8.1创建并使用模块。

//module.js

var name;
exports.setName = function(thyName) {
  name = thyName;
};
exports.sayHello = function() {
  console.log('Hello ' + name);
}; 

//getmodule.js

var myModule = require('./module');
myModule.setName('BYVoid');
myModule.sayHello(); 

注意:模块是单例的,即无论多少次require,获得的都是同一个实例。

8.2把一个对象封装到模块中

//hello.js

function Hello() {
  var name;
  this.setName = function(thyName) {
    name = thyName;
  };
  this.sayHello = function() {
    console.log('Hello ' + name);
  };
};
module.exports = Hello; 

//gethello.js

var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello(); 

原理:exports本身仅仅是一个普通的空对象,即{}。

8.3包

包就是C#中的类库。

node中一个文件夹加上接口(index.js)再加上一个配置文件就是一个包。可以通过 npm init 命令根据提示一步一步创建一个标准的package.json配置。

二、核心模块。

1,全局对象

node中的全局对象是global,所有的全局变量都是global的属性,包括console、process等

全局变量process描述当前node的进程状态,process.nextTick(callback)的功能是为时间循环设置一项任务,node会在下次事件循环时调用callback.

console.log()

console.error()

console.trace()

2, util模块

util.inherits(constructor, superConstructor) 实现对象间的原型继承,但构造函数内部创造的属性和函数都不会被继承

var util = require('util');
function Base() {
  this.name = 'base';
  this.base = 1991;
  this.sayHello = function() {
    console.log('Hello ' + this.name);
  };
}
 
Base.prototype.showName = function() {
  console.log(this.name);
};
function Sub() {
  this.name = 'sub';
}
util.inherits(Sub, Base);
var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);
 
var objSub = new Sub();
objSub.showName();
//objSub.sayHello();
console.log(objSub); 

util.inspect(object,[showHidden],[depth],[colors])是一个将任意对象转换 为字符串的方法, 通常用于调试和错误输出。

util还提供了util.isArray()、 util.isRegExp()、 util.isDate()、util.isError() 四个类型测试工具,以及 util.format()、util.debug() 等工具

3,events模块。

监听和触发事件:

var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent', function(arg1, arg2) {
  console.log('listener1', arg1, arg2);
});
emitter.on('someEvent', function(arg1, arg2) {
  console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent', 'byvoid', 1991); 

EventEmitter提供的函数:

EventEmitter.on(event, listener)

EventEmitter.emit(event, [arg1], [arg2], [...])

EventEmitter.once(event, listener)

EventEmitter.removeListener(event, listener)

EventEmitter.removeAllListeners([event])

4,文件操作 - fs模块

fs.readFile(filename,[encoding],[callback(err,data)])

fs.readFileSync(filename, [encoding])

fs.open(path, flags, [mode], [callback(err, fd)])

fs.read(fd, buffer, offset, length, position, [callback(err, bytesRead,

buffer)])

5,http模块

//app.js

var http = require('http');
http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write('<h1>Node.js</h1>');
  res.end('<p>Hello World</p>');
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

http客户端:

http.request(options, callback) 发起 HTTP 请求,以下是发送post请求的代码:

var http = require('http');
var querystring = require('querystring');
var contents = querystring.stringify({
  name: 'byvoid',
  email: 'byvoid@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
  }
};
var req = http.request(options, function(res) {
  res.setEncoding('utf8');
  res.on('data', function (data) {
    console.log(data);
  });
});
req.write(contents);
req.end();

http.get(options, callback)更加简便的方法用于处 理GET请求

var http = require('http');
http.get({host: 'www.byvoid.com'}, function(res) {
  res.setEncoding('utf8');
  res.on('data', function (data) {
    console.log(data);
  });
});

http.request 或 http.get创建的对象是http.ClientRequest,请求完成返回的对象是http.ClientRequest

三、web开发

1,使用Express框架。

类似于asp.net ,express实现的功能:路由控制、模版解析支持、动态视图、缓存等功能。

express默认支持的模版引擎:jade、ejs

安装express: > npm install -g express

安装express命令行工具: >npm install -g express-generator

查看帮助信息: > express --help

建立web基本结构(使用jade模版引擎):> express myWeb

建立web基本结构(使用ejs模版引擎): > express -e myWeb2

进入刚建立的目录并初始化:>cd myWeb

:> npm install

启动:> npm start

此时浏览http://localhost:3000 可以看到第一个界面了。

关闭服务器 ctrl+c

创建的代码中,app.js是工程的入口。

2,路由控制。

app.js中,引用路由:var routes = require('./routes/index');

把Url“/”映射到路由:app.use('/', routes);

routes/index.js中路由的写法:

var express = require('express');

var router = express.Router();

/* GET home page. */

router.get('/', function(req, res) {

res.render('index', { title: 'Express' });

});

module.exports = router;

路由匹配:

/user/[username] :

app.get('/user/:username', function(req, res) {

res.send('user: ' + req.params.username);

});

3,视图。

布局、局部视图跟asp.net mvc一样。

<ul><%- partial('listitem', items) %></ul>

视图助手(asp.net mvc中的helper 方法)

var util = require('util');
app.helpers({
  inspect: function(obj) {
    return util.inspect(obj, true);
  }
});
app.dynamicHelpers({
  headers: function(req, res) {
    return req.headers;
  }
});
app.get('/helper', function(req, res) {
  res.render('helper', {
    title: 'Helpers'
  });
});
<%=inspect(headers)%> 

4,数据库访问(以MongoDB为例)。

4.1 MongoDB。

MongoDB以文档的形式存储数据,一下是一个文档的示例:

{ "_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ),

"uid" : 2004,

"username" : "byvoid",

"net9" : { "nickname" : "BYVoid",

"surname" : "Kuo",

"givenname" : "Carbo",

"fullname" : "Carbo Kuo",

"emails" : [ "byvoid@byvoid.com", "byvoid.kcp@gmail.com" ],

"website" : "http://www.byvoid.com",

"address" : "Zijing 2#, Tsinghua University" }

}

4.2 连接数据库。

安装MongoDB.

package.json中添加依赖代码:

"dependencies": {

"express": "~4.2.0",

"static-favicon": "~1.0.0",

"morgan": "~1.0.0",

"cookie-parser": "~1.0.1",

"body-parser": "~1.0.0",

"debug": "~0.7.4",

"jade": "~1.3.0",

"mongodb":">=0.9.9"

}

运行: > cd myWeb

> npm install

工程目录中创建settings.js文件,代码如下:

module.exports = {

cookieSecret: 'microblogbyvoid',

db: 'microblog',

host: 'localhost',

};

注:db是数据库名称,host是数据库的地址,cookieSecret用于Cookie加密与数据库无关。

创建models子目录,并在其中添加db.js文件,内容是:

var settings = require('../settings');
var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
module.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_PORT, {})); 

注:通过module.exports输出了创建的数据库链接。

使用:

var mongodb = require('./db');
 
function User(user) {
  this.name = user.name;
  this.password = user.password;
};
module.exports = User;
 
User.prototype.save = function save(callback) {
  // 存入 Mongodb 的文档
  var user = {
    name: this.name,
    password: this.password,
  };
  mongodb.open(function(err, db) {
    if (err) {
      return callback(err);
    }
    // 读取 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.get = function get(username, callback) {
  mongodb.open(function(err, db) {
    if (err) {
      return callback(err);
    }
    // 读取 users 集合
    db.collection('users', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      // 查找 name 属性为 username 的文档
      collection.findOne({name: username}, function(err, doc) {
        mongodb.close();
        if (doc) {
          // 封装文档为 User 对象
          var user = new User(doc);
          callback(err, user);
        } else {
          callback(err, null);
        }
      });
    });
  });
}; 

四,其他。

1,模块加载机制。

核心模块已经被编译成二进制代码,可以直接require获取,如 require('fs');

require文件模块的时候,如果指明了路径则按指定的加载,如果没有指定,则去node_modules目录加载。

即使多次require同一个模块, 加载到的仍是同一个对象,因为node根据实际文件名把加载过的文件模块缓存了。

2,日志功能。

启用日志功能,需要以产品模式运行express : > NODE-ENV=production node app.js

记录访问日志和错误日志:

var fs = require('fs');

var accessLogfile = fs.createWriteStream('access.log', {flags: 'a'});

var errorLogfile = fs.createWriteStream('error.log', {flags: 'a'});

至于错误日志,需要单独实现错误响应,修改如下:

app.configure('production', function(){

app.error(function (err, req, res, next) {

var meta = '[' + new Date() + '] ' + req.url + '\n';

errorLogfile.write(meta + err.stack + '\n');

next();

});

});

3,javascript的作用域。

JavaScript的作用域是由函数来决定的,if、for语句中的花括号不是独立的作用域。

if (true) {

var somevar = 'value';

}

console.log(somevar); // 输出 value

在一个函数中定义的变量只对这个函数内部可见。

4,call 和 apply 的用法。

var someuser = {

name: 'byvoid',

display: function(words) {

console.log(this.name + ' says ' + words);

}

};

var foo = {

name: 'foobar'

};

someuser.display.call(foo, 'hello'); // 输出 foobar says hello

5,bind的用法。

var someuser = {
  name: 'byvoid',
  func: function() {
    console.log(this.name);
  }
};
 
var foo = {
  name: 'foobar'
};
 
foo.func = someuser.func;
foo.func(); // 输出 foobar
 
foo.func1 = someuser.func.bind(someuser);
foo.func1(); // 输出 byvoid
 
func = someuser.func.bind(foo);
func(); // 输出 foobar
 
func2 = func;
func2(); // 输出 foobar 
 
bind绑定参数列表:
var person = {
  name: 'byvoid',
  says: function(act, obj) {
    console.log(this.name + ' ' + act + ' ' + obj);
  }
};
 
person.says('loves', 'diovyb'); // 输出 byvoid loves diovyb
 
byvoidLoves = person.says.bind(person, 'loves');
byvoidLoves('you'); // 输出 byvoid loves you 

6, === 和 == 的区别。

==包含隐式转换。

7,原型、原型链。

prototype 、 _proto_

Object 、Function

Object.prototype 是所有对象的祖先,Function.prototype 是所有函数的原型,包括构造函数。

参考:《Node.js从入门到精通》