赵工的个人空间


专业技术部分转网页计算转业余爱好部分


  网站建设

首页 > 专业技术 > 网站建设 > Node.js功能和使用
Node.js功能和使用

Node.js是一个JavaScript运行环境runtime,运行于服务器端,可以使用其API库进行服务器端开发。Node.js实际上是对Google V8引擎进行了封装,拥有异步非阻塞特性,在长连接、多请求环境下有明显优势。

一、Node.js的安装配置:

1.安装:
在官网下载Node.js的安装软件,windows环境下建议下载msi后缀的版本,然后执行。

2.配置环境变量:
需要将Node.js的启动文件路径加入window环境变量中。

3.检查安装使用成功:
在window命令界面,输入命令node -v,如果显示Node.js的版本信息,说明已经安装成功。

4.运行脚本文件:
使用命令node 文件名,就可以执行js脚本代码。如node program.js。
如果想快速运行一些简单语句,可以使用-e参数。示例:
node -e "console.log(new Date());"

5.hello world:
使用Node.js建立一个简单的服务器,其中使用了http模块。
在一个文件夹中创建app文件夹,在文件夹中创建app.js文件,输入代码:
var http=require('http');
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
代码中应用require调用HTTP模块中的方法和属性,监听接口有端口号和服务器地址两个参数。只要端口未被其他应用程序占用,都可以作为Node.js的服务端口。
进入app文件夹,运行node app.js指令,运行成功后启动本地IP的1337端口服务器,屏幕输出信息Server running at http://127.0.0.1:1337。
打开浏览器,输入server服务器地址:http://127.0.0.1:1337,Web页面中将可以看到Hello World字符串。

二、Node.js语法:

Node.js建立在Google Chrome的V8引擎机器ECMAScript之上,其语法与前端的JavaScript非常类似,包括对象、函数、方法等。

1.数据类型:
JavaScript的基本类型只有下面几种,String、Number、Boolean、Undefined、Null、RegExp,其它都是object。String、Number、Boolean类型中都有相关方法进行转换。
1)Buffer:
Buffer是Node.js添加的一种类型,做数据存储非常有效,如从文件系统中读取内容或接收网络包内容等。
2)对象字面量:
var obj={color""green",type:"suv",owner:{...}}
var obj=function(){
    this.color:"green",
    this.type:"suv",
    this.owner:{...}
}

2.函数:
在Node.js中,函数也是对象,可以当作变量来使用,可以拥有属性和方法。
1)定义函数:
有三种方式定义函数:
⑴定义具名函数:
function f(){
    console.log('Hi');
    return true;
}
⑵定义匿名函数并赋值给一个变量:
var f=function(){
    console.log('Hi');
    return true;
}
⑶同时使用前两种方式:
var f=function f(){
    console.log('Hi');
    return true;
}
return语句是可选的,如果不使用,函数被调用后返回undefined。
2)为函数添加属性:
f.boo=1;
3)函数作为参数传递:
JavaScript中,函数也是对象,可以把函数作为参数传递给其他函数,一般是作为回调:
var convertNum=function(num){
return num+10;
}
var processNum=function(num,fn){
return fn(num);
}
processNum(10,convertNum);

3.数组:
数组也是对象,从全局Array.prototype中继承了一些特殊方法。JavaScript数组可看作是具有唯一数值索引的对象:
var arr=[];
var arr2=[1,"Hi",{a:},function(){console.log('boo');}];
var arr3=new Array();
var arr4=new Array(1,"Hi",{a:2},function(){console.log('boo');});

4.原型特性:
JavaScript中没有类,对象可以直接从其他对象处继承,称为原型继承。Functional继承模式:
var user=function(ops)}
return {firstName:ops.name||'John',lastName:ops.name||'Doe',
      email:ops.email||'test@test.com', name:function(){return this.firstName+this.lastName}
    };
}
var agency=function(ops){
    ops=ops||{};
    var agency=user(ops);
    agency.customers=ops.customers||0;
    agency.isAgency=true;
    return agency;
}

5.编码规范:
推荐的编码规范,虽然不影响程序执行结果,但对团队开发或参与开源项目是很重要。很多开源项目对格式有严格要求,如request中需要包含分号,不允许使用首行注释格式等。
1)分号:
一般情况下可以选择是否使用分号,但循环结构中或括号开头的一行,要求使用分号:
for(var i=0;i++;i<n)
;(function(){...}())
立即调用函数表达式IIFE前面一定要有分号。
2)驼峰式:
JavaScript一般使用驼峰式命名,类使用首字母大写,变量则使用首字母小写。
3)命名:
_和$都是命名时可以使用的合法字符,建议私有属性和方法以_开头,虽然并没有实际作用。
4)逗号:
一般使用逗号先行。如:
var obj={firstName:"john"
    ,lastName:"Smith"
    ,email:"johnsmith@gmail.com"
}
5)缩进:
通常使用1个tab、4个空格或2个空格来缩进。
6)空格:
一般情况下,在=、+、{和}前会加一个空格,方法调用时不加空格,定义匿名函数时要加空格。

6.全局变量和保留字:
浏览器端的JavaScript有window对象,Node.js则没有,为开发者提供了新的对象process、global、module.exports和exports。
Node.js和JavaScript的一些区别:
1)获取进程相关信息:
每个运行的Node.js脚本本质上都是一个进程,使用ps aux|grep 'node'命令可以输出当前机器上所有运行中的Node.js程序。开发者可以在程序中通过使用process对象来方便地获取一些与进程相关的信息。如:
node -e "console.log(process.pid)"
node -e "console.log(process.cwd())"
其中,pid为进程id,cwd()为当前目录。
2)在Node.js中访问全局空间:
Node.js默认所有的东西都是本地的,需要访问全局对象时才会使用global对象,需要导出时必须特别明确声明。

7.导入和导出模块:
1)导入模块:
Node.js的核心模块,文件名前不需要添加路径,这种方法也适用于node_modules目录下的各个模块。对于其他文件,需要指明路径,一般使用相对路径,以.开头,如:
var keys=require('./keys.js'),
    message=require('./routes/messages.js');
导入文件时,可能会使用__direname和path.join(),如:
require(path.join(__direname, , 'routes', 'message'));
使用path.join()可以生成带有斜杠语法的路径,会根据系统不同而生成斜杠或反斜杠。如果require()指向的是一个目录,Node.js就会尝试读取目录下的index.js文件。
__dirname是使用该全局变量文件的绝对路径,而process.cwd是运行脚本进程的绝对路径。当在不同目录下运行程序时,二者的值可能并不相同。
2)使用exports导出:
在Node.js中要导出一个对象,需要使用exports.name=object。示例:
var messages={
    find:function(req,res,next){...},
    add:function(req,res,next){...},
    format:'title'|date|author
};
exports.messages=messages;
当在文件中需要引入上述脚本(文件路径route/messages.js),使用方法:
var messages=require('./routes/messages.js');
这种方法适合调用构造器。
3)使用module.exports:
如果要为express.js配置属性,需要使用module.exports。
module.exports=function(app){
    app.set('port',process.env.PORT||3000);
    app.set('views',__dirname+'/views');
    app.set('view engine','jade');
    return app;
)
引入上述模块文件,使用:
var app=express();
var config=require('./config/index.js');
app=config(app);
也可以使用一种简单形式:
var app=express();
require('./config/index.js')(app);

8.浏览器API辅助工具:
Node.js中有很多工具函数来自于浏览器端的JavaScript API,最常用的一些来自于String、Array和Math对象。
1)Array:
·some()和every():对数组做判断
·join()和concat():将数组转换为字符串
·pop()、push()、shift()和unshift():栈和队列的相关操作
·map():遍历数组并处理相关元素
·filter():查询数组元素
·sort():对元素进行排序
·reduce()和reduceRight():进行相关计算
·slice():将数组一部分复制出来
·splice():直接对数组进行切割
·indexOf():获取指定元素在数组中的索引值
·reverse():将数组倒序排序
·in操作符:在数组元素上进行迭代
2)Math:
·random():随机产生一个大于0小于1的数值
3)String:
·substr()与substring():获取子字符串
·length:获取字符串长度
·indexOf():获取指定值在字符串中的索引值
·split():将字符串切割成数组
另外,Node.js中还有setInterval()、setTimeout()、forEach()、console方法。

9.Node.js核心模块:
Node.js核心模块为轻量级的,其他模块可以通过npm来注册安装。主要核心模块包括http、util、querystring、url和fs等。核心模块不需要下载安装,只需要request载入即可使用:
var http=require('http');
1)http模块:
http是Node.js从HTTP服务器获取相应内容的主要模块,包含的主要方法有:
·http.createServer():返回一个新的Web服务器对象
·http.listen():在指定的主机名和端口上建立连接
·http.createClient):建立一个可以向其他服务器发送请求的客户端
·http.ServerRequest():将请求信息传递给request处理事件
·data:消息体数据,被接收是发出
·end:每次请求只会触发一次
·request.method():字符串格式的请求方法
·request.url():请求的URL字符串
·http.ServerResponse():该对象由服务器创建,用来作为请求处理事件输出内容
·response.writeHead():向请求的客户端发送响应头
·response.write():向请求的客户端发送响应内容
·response.end():告知客户端结束发送响应内容
2)util模块:
util模块提供了调试用的工具方法。
·util.inspect():返回一个由对象转换而成的字符串,调试时很有用
3)querystring模块:
querystring模块提供了一些处理查询字符串的工具。
·querystring.stringify():将一个对象序列化为一个查询字符串
·querystring.parse():反序列化一个字符串为对象
4)url模块:
url模块中包含了URL的相关处理和转化的工具。
·url.parse():接受一个URL字符串,返回转化后的对象
5)fs模块:
fs模块主要处理文件系统相关的操作,如读写文件等。库中所有的方法都有同步操作和异步操作两种方式。
·fs.readFile():异步读取文件内容
·fs.writeFile():异步写数据到文件中
非核心模块,可以在npmjs.org、nipster等查找注册过的模块。
6)便捷的Node.js工具:
·Crypto:包含随机生成器、MD5、HMAC-SHA1和一些其他算法
·Path:用来处理系统路径
·String decoder:将buffer或字符串类型数据解码

10.Node.js中读写文件:
读取文件的操作由fs模块完成,读取方式有同步和异步两种,大多数情况下应使用异步方法。
1)读取文件:
var fs=require('fs');
var path=require('path');
fs.readFile(path.join(__dirname,'/data/customers.csv'),{encoding:'utf-8'},
    function(err,data){if(err) throw err;console.log(data);}
);
2)将数据写入文件:
var fs=require('fs');
fs.writeFile('message.txt','Hello World!',function(err){
    if(err) throw err;
    console.log('Writing is done.');
});
3)数据流:
数据流是指应用在处理数据时还可以同时接收数据,这种特性适合处理视频、数据迁移等场合。示例:
var fs=require('fs');
fs.createTeadStream('/data/customers.csv').pipe(process.stdout);
默认情况下,Node.js使用buffer来处理流。
4)读取目录:
fs.readdir(source,function(err,files){
  if(err){
    console.log('Error finding files: '+err);
  }else{
    files.forEach(function(filename,fileIndex){
      console.log(filename);
      gm(source+filename).size(function(err.values){
        if(err){
          console.log('Error identifying file size: '+err);
        }else{
          console.log(filename+':'+values);
          aspect=(values.width/values.height);
          widths.forEach(function(width,widthIndex){
            height=Math.round(width/aspect);
            console.log('resizing '+filename+'to '+height+'x'+height);
            this.resize(width,height).write(destination+'w'+width+'_'+filename,function(err){
              if(err) console.log('Error writing file: '+err);
            });
          }.bind(this));
        }
      });
    });
  }
})

11.使用原生模块的一个示例:
var http=require('http');
var util=require('util');
var querystring=require('querystring');
var mongo=require('mongodb');
var host=process.env.MONGOHQ_URL||'mongodb://@127.0.0.1:27017';
//MONGOHQ_URL=mongodb://user:pass@server.mongohq.com/db_name
mongo.Db.connect(host,function(error,client){
  if(error) throw error;
  var collection=new mongo.Collection(client,'test_collection');
  var app=http.createServer(function(request,response{
    if(request.method==='GET'&&request.url==='/messages/list.json'){
      collection.find().toArray(function(error,results){
        response.writeHead(200,{'Content-Type':'text/plain'});
        console.dir(results);
        response.end(JSON.stringify(results));
      });
    };
    if(request.method==="POST"&&request.url==="/messages/create.json"){
      request.on('data',function(data){
        collection.insert(
          querystring.parse(data.toString('utf-8')),
          {safe:true},
          function(error,obj){
            if(error) throw error;
            response.end(JSON.stringify(obj));
          }
        );
      });
    };
  });
  var port=process.env.PORT||5000;
  app.listen(port);
})

三、模块加载及NPM安装:

模块分为两类,原生模块和文件模块,原生模块是Node.js API提供的模块,在启动是已经加载;文件模块需要通过调用Node.js的require方法来实现加载。
Node.js对加载模块进行缓存,再次加载时从缓存中读取相应模块数据,不会有重复开销。

1.原生模块的加载:
应用Node.js提供的require来加载相应的模块,加载成功后会返回一个Node.js对象,该对象拥有该模块的所有属性和方法。示例:
var httpModule=require('http');
可以通过查看Node.js的API文档,了解原生模块及相应的方法和属性。

2.文件模块加载:
文件模块加载需要指定文件路径:
var test=require('/path/test.js');
其中,/path/test.js代表test文件模块的绝对路径,也可以使用相对路径,“./”开头,可以省略test.js的后缀名。返回值也是一个对象。
得到返回的test对象后,就可以调用文件模块的属性和方法。文件模块中,只有exports和module.exports对象暴露给外部的属性和方法才能通过返回的require对象进行调用,其他方法和属性是无法获取的。示例:
exports.name='hello';
exports.game=function(){console.log('gamename')};
可以通过查看require方法返回的对象得到其拥有的属性和方法:
console.log('test');
返回信息为一个JSON对象,{name:'hello',game:[Function]}。只有exports和module.exports的方法和属性是可见的。

3.exports和module.exports:
所有的exports对象最终都是通过module.exports传递执行,其实就是给module.exports添加属性和方法,可以被module.exports替代。exports只能返回一个object对象。
module.exports可以返回一个数据类型,如数组、字符串、数字等。如:
module.exports=['hello','world','how',are','you'];
当使用了module.exports后,该模块中的所有exports对象执行的属性和方法都将被忽略。

4.npm模块:
NPM,是Node Packaged Modules的缩写,是Node.js的包管理器。开发者可以提交个人Node.js模块,也可以下载模块包,并将模块应用到项目中。npm安装需要使用package.json文件和node_module目录。
Windows下安装Node.js时默认会安装NPM模块。在命令行输入npm -v,显示版本信息,说明已经安装。可以使用npm -help查看NPM指令使用方法,最常用的是npm install和npm uninstall,分别用来安装和卸载模块。
使用npm安装模块,会在当前文件夹下产生node_modules目录,并在该目录中下载模块,运行require时会自动加载当前目录下的node_modules目录中的相应模块,也可以直接在github下载相应模块并将其放入项目的node_modules目录中。

四、Express.js模块:

express是一个Web开源框架,基于Node.js的http模块和Connect组件,主要集成了Web的http服务器的创建、静态文件管理、服务器url请求处理、GET和POST请求分发、Session处理等功能。express.js中引入了Mongoose、Sequelize库等为扩展。
express.js是单入口的主文件启动,通常会在Node命令在启动这个文件,有时也会以模块形式存在。

1.express.js的安装:
1)全局安装:
安装express使用命令:
npm install -g express
参数-g是添加全局执行环境,安装时可以设定安装的版本信息,在express后添加@version即可。如:
npm install -g express@3.0
安装成功后,创建一个应用app,执行命令:
express app
执行完成后,在本路径下创建了app目录,包括多个目录和文件。cd进入app目录,运行项目,使用命令:
node app.js
执行后,显示Express server listening on port 3000,表示安装成功。打开浏览器,地址栏输入http://127.0.0.1:3000,可以看到Express的页面,表明成功安装配置express,不过其中提示错误Cannot find module jade,表明项目中还需要安装jade模块。
可以使用express -h或express -help查看express可用的选项。命令的通用格式为:
express [options] [dir!appname]
其中的选项options包括:
·-e、--ejs:添加EJS引擎支持,默认使用jade
·-H、--hogan:添加Hogan.js引擎支持
·-c<engine>、--css<engine>:添加样式表<engine>支持模块,如less、stylus或compass
·-f、--force:强制应用在非空目录中生成
比如,生成一个依赖stylus的应用命令:
express -c styl express-styl
2)本地安装:
一般先创建一个项目目录,进入此目录,使用文本编辑器或通过npm init创建package.json文件:
{
  "name":"express-cli",
  "version":"0.0.1",
  "description":"",
  "main":"index.js",
  "scripts":{"test":"echo \"Error: no test specified\" && exit 1"},
  "author":"",
  "license":"BSD"
}
然后使用npm安装模块:
npm install express
如果没有package.json文件或node_module目录情况下执行上述命令,npm会先生成文件及目录。

2.express.js目录结构:
·node_modules:express.js和第三方模块的依赖都在这个目录下
·views:jade或者其他模板
·routes:包含请求处理程序的Node.js模块
·db:MongoDB的种子数据和脚本
·public:所有前端的静态文件,包括HTML、CSS、浏览器前端的JavaScript和Stylus等

3.npm初始化和package.json:
可以在项目目录下使用npm init命令,会生成没有内容的package.json文件,使用npm install package_name --save命令,安装模块时会增加到package.json中。如:
npm install express --save
也可以手动创建package.json文件,并创建并增删其中的内容。如:
{
    "name":"hello world",
    "version":"0.0.1",
    "private":true,
    "scripts":{"start":"node app.js"},
    "dependencies":{"express":"4.1.1","jade":"1.3.1",
         "mongoskin":"1.4.1","stylus":"0.44.0"}
}
其中,设置了入口app.js,这样可以使用以下方法运行:
node app.js
node app
node start
也可以将入口文件命名为index.js,这样就可以通过node . 命令来启动。

4.主文件app.js:
express的主文件由几个部分组成,引入依赖、相关配置、连接数据库、定义中间件、定义路由、开启服务、在多核系统上启动cluster多核处理模块等。
所有依赖都以require()方法引入:
var express=require('express');
var http=require('http');
var path=require('path');
然后实例化对象express:
var app=express();
配置express使用app.set()方法:
app.set('appName','hello-world');
app.set('port','process.env.PORT||3000);
app.set('views',path.join(__dirname,'views');
app.set('views engine','jade');
然后是使用中间件的部分。中间件是express框架的骨干部分,由外部第三方模块定义或自身的模块所定义。中间件是用来组织和复用代码的一种方式,函数中只有三个参数,request、response、next。
下一部分是路由,路由会在定义好的列表中进行处理。一般路由放在中间件后面,但也有中间件放在路由后面,如错误处理。
app.all('*',function(req,res){
    res.render('index',{msg:'Welcome to the Practical Node.js!'});
});
代码中使用了express的res.render(viewName,data,callback(error,html))函数,其中viewName为模板名或模板引擎,data是一个可传递的可选对象,jade在使用msg来定义对象,callback是可选函数,在错误或html绘制完成后调用。
最后是启动服务,由http机器createServer方法组成,其中通过前面的设置和路由传递express的对象。
http.createServer(app).listen(app.get('port'),function(){
    console.log('Express.js server listening on port '+app.get('port'));
});
全部代码为:
var express=require('express');
var http=require('http');
var path=require('path');
var app=express();
app.set('port','process.env.PORT||3000);
app.set('views',path.join(__dirname,'views');
app.set('views engine','jade');
app.all('*',function(req,res){
    res.render('index',{msg:'Welcome to the Practical Node.js!'});
});
http.createServer(app).listen(app.get('port'),function(){
    console.log('Express.js server listening on port '+app.get('port'));
});
开启服务前,需要创建一个index.jade文件。jade是一个模板引擎,文件中使用html标签。

五、Jade模板:

模板引擎是一个库,或者是一个使用一定规则来解释数据并渲染视图的框架。在Web应用中,视图就是HTML页面,也可以使用JSON或XML文件;在桌面程序中,也可以是图形用户界面GUI。在MVC框架中,模板属于视图层。
jade是简洁的Node.js模板引擎,是express.js的默认模板引擎。Node.js不支持网页内嵌代码,要使用模板。

1.安装Jade模板:
要在express.js中使用Jade,只需要安装Jade模板,然后设置view engine即可。
jade模板安装命令:
npm install jade
命令执行后,将安装的jade模板拷贝到express应用项目app目录的node_modules目录中,此项目已经包含了express和jade两个模块,执行命令node app.js后,express中设置:
app.set('view engine','jade');
使用浏览器打开http://127.0.0.1:3000后,就不再提示缺少jade的错误了。
jade模板文件app/views/index.jade中的代码为:
extends layout
block content
  h1=title
  p Welcome to #{title}
第一行代码extends layout相当于在index.jade页面前加载layout.jada页面,其中的extends是jade模板的关键字;h1和p分别表示html对应的标记名<h1></h1>和<p></p>,其后的值是标记内的DOM元素。代码中的#{title}是服务器Node.js传递的参数,可以查看Node.js的源代码app/routers/index.js:
exports.index=function(req,res){
  res.render('index',{title:'Express'});
};
其中,jade提供了render API,该API指定显示的jade模板名,以及向模板中传递的参数值,title变量就在其中。参数在jade模板中的调用方法是#{params},其中可以使用json或其他格式的数据类型。
jade模板中提供了设置html的标记,以及DOM元素的id和class值,还提供if条件语句、for循环语句等。

2.Jade语法:
Jade模板对应着一个HTML页面,使用一定语法来进行转换。
1)标签:
一行开头的任何文本都会默认解释为HTML标签。标签后的文本和空格会被解释成内联HTML,也就是元素的文本内容。示例:
Body
  div
    h1 Practical Node.js
    p The only book most people will ever need.
  div
    footer © Apress
2)数据和变量:
传给Jade模板的数据称为locals。要输出一个变量值,使用等号。示例:
h1=title
p=body
(locals):
{
title: "Express.js Guide",
body: "The Comprehensive Book on Express.js"
}
3)属性:
属性紧跟在标签名后,用括号括起来,格式为name=value,多个属性之间用逗号分隔。
div(id="content", class="main")
  a(href="http://expressjsguide.com", title="express.js Guide",
    target="_blank") Express.js Guide
  form(action="/login")
    button(type="submit, value="save")
  div(class="hero-unit") Lean Node.js!
有时,一个属性的值是动态变化的,这时只需要使用变量名。符号|允许在新的一行中写HTML节点的内容。示例:
a(hred=url, data-active=isActive)
label
  input(type="checkbox", checked=isChecked)
  |yes/no
为上述模板提供的数据为:
{
  url:"/logout",
  isActive:true,
  isChecked:false
}
注意,值是false的属性在输出HTML代码时会被忽略;而当没有传值时,会被赋值为true。
input(type='radio', checked)
input(type='radio', checked=true)
input(type='radio', checked=false)
4)字面量:
可以直接在标签名之后写类和id。示例:
div#content
  p.lead.center
    |webapplog:where code lives
    #side-bar.pull-right
    span.content.span4
      a(href="/contact") contact us
上述代码对应的HTML为:
<div id="content">
  <p class="lead center">
    webapplog:where code lives
    <div od="side-bar" class="pull-right"></div>
    <span class="contact span4">
      <a href="/contact">contact us</a>
    </span>
  </p>
</div>
注意,没有写标签名,默认就是div标签。
5)文本:
通过符号|可以输出原始文本。如:
div
  |Jade is a template engine.
  |It can be used in Node.js and in the browser JavaScript.
6)Script和Style块:
有时需要在HTML的script或style标签中写内容块,这时可以使用点号。如:
script.
  console.log("Hello Jade!")
  setTimeout(function(){
    window.location.href='http://rpjs.co'
  },200))
  console.log('Good bye!')
上述代码书写了一段JavaScript脚本。
7)JavaScript代码:
如果要在模板编译时使用JavaScript代码,书写的是Jade输出的可执行代码,可以使用符号-、=或!=,用于输出HTML元素和注入JavaScript。但要注意,避免跨站脚本XSS攻击。
- var arr=['<a>','<b>','<c>']
ul
  -for(var i=0;i<arr.length;i++)
    li
      span=i
      span!="unescaped: "+arr[i]+" vs."
      span="escaped: "+arr[i]
上述代码会生成HTML:
<ul>
  <li><span>0</span><span>unescapes: <a> vs. </span><span>escaped: <a></span></li>
  <li><span>1</span><span>unescapes: <b> vs. </span><span>escaped: <b></span></li>
  <li><span>2</span><span>unescapes: <c> vs. </span><span>escaped: <c></span></li>
</ul>
Jade允许在代码中写几乎所有的JavaScript。
8)注释:
若想在页面中输出注释,就使用JavaScript的注释形式//;若不想输出,则使用//-。
// content goes here
p Node.js is a non-blocking I/O for scalable apps.
//- @todo change this to a class
p(id="footer") Copyright 2014  Azat
输入HTML为:
<!-- content goes here-->
<p>Node.js is a non-blocking I/O for scalable apps.</p>
<p id="footer">Copyright 2014 Azet</p>
9)if语句:
给of语句加前缀-,就可以写标准的JavaScript代码了;也可以使用一种极简的不需要前缀、不需要括号的替代写法。如:
- var user={}
- user.admin=Math.random()>0.5
if user.admin
  button(class="launch") Launch Spacecraft
else
  button(class="login") Log in
除了if,还可以使用unless,表示不,或者!。
10)each语句:
Jade中的迭代可以只简单地写each。如:
- var languages=['php','node','ruby']
div
  each value, index in languages
    p=index+". "+value
输出的HTML代码为:
<div>
  <p>0. php</p>
  <p>1. node</p>
  <p>2. ruby</p>
</div>
这种方法也适用于对象:
- var languages=['php':-1,'node':2,'ruby:':1]
div
  each value, index in languages
    p=key+": "+value
Jade编译后输出的HTML为:
<div>
  <p>php: -1</p>
  <p>node: 2</p>
  <p>ruby: 1</p>
</div>
11)过滤器:
当有一个文本块需要用另外一种语言写时,就会用到过滤器。如Markdown过滤器写法为:
p
  :markdown
    # Parctical Node.js
  [This book](http://expressjsguide.com) really helps to grasp many compoments needed for moden-day web devolopment.
如果使用Markdown的过滤器,需要安装Markdown模块,以及marked和Markdown NPM包。
12)读取变量:
Jade中读取变量的值是通过#{name}来实现的。如果要在一个段落中输出title:
- var title="Express.js Guide"
p Read the #{title} in PDF, MOBI and EPUB
在模板编译时,变量的值就会被处理,因此不要在可执行JavaScript中使用。
13)case语句:
Jade中使用case语句的示例:
- var coins=Math.round(Math.random()*10)
case coins
  when 0
    p You have no money
  when 1
    p You have a coins
  default
    p You have #{coins} coins!
14)函数mixin:
mnxin是带参数并产生一些HTML的函数,声明的语法为:
mixin name(param, param2,  ...)
用法是+name(data)。示例:
mixin row(items)
  tr
    each item, index in items
      td=item
mixin table(tableData)
  table
    each row, index in tableData
      +row(row)
- var node=[{name:"express"}, {name:"hapi"}, {name:"derby"}]
+table(node)
- var js=[{name:"backbone"}, {name:"angilat"}, {name:"ember"}]
+table(js)
上述代码生成的HTML代码为:
<table>
  <tr>
    <td>express</td>
  </tr>
  <tr>
    <td>hapi</td>
  </tr>
  <tr>
    <td>derby</td>
  </tr>
</table>
<table>
  <tr>
    <td>backbone</td>
  </tr>
  <tr>
    <td>angular</td>
  </tr>
  <tr>
    <td>ember</td>
  </tr>
</table>
15)include:
include是把逻辑提取到单独文件中的一种方式,以便让多个文件重用。要包含一个Jade模板,用include /path/filename。如在文件A中:
include ./includes/header
其中模板名和路径不加引号。
include是一种自顶向下的方法,在include其他文件的主文件中,决定用什么。主文件首先被处理,可以在此定义数据locals,接着再处理被包含进来的子文件,此处可以使用刚才定义的数据locals。
但无法在文件名和文件路径中使用变量,因为inludes/partials是在编译时处理的,而不是在执行时。
16)extend:
extend是一种自底向上的方法,与include相反。包含的文件决定它要替换主文件的哪一部分,格式为extend filename和block blockname。
在文件file_a中:
block header
  p some default text
block content
  p Loading ...
block footer
  p copyright
在文件file_b中:
extend file_a
block header
  p very specific text
block content
  .main-content

3.单独使用Jade:
Jade并不一定必须与express一起使用,可以单独使用。在项目中添加Jade模板,用命令mkdir mode_modules创建一个空目录node_modules,然后使用命令npm install jade -save安装并添加jade到package.json。
如果有发送电子邮件的Node.js脚本,然后要用模板动态生成电子邮件的HTML。使用:
.header
  h1=title
  p
.body
  p=body
.footer
  div=By
    a(href="http://twitter.com/#{author.twitter}")=author.name
  ul
    each tag,index in tags
      li=tag
示例中,Node.js脚本需要填充数据,提供模板的数据为:
·title:字符串
·body:字符串
·author:字符串
·tags:数组
可以从多个源获取这些变量,如数据库、文件系统、用户输入等。在文件jade-example.js中用编码的方式给title、author、tags传值,用命令行参数的方式给body传值:
var jade=require('jade'),
   fs=require('fs'):
var data={
  title:"Practical Node.js",
  author:{twitter:"@azat_co",name:"Azat"},
  tags:['express','node','javascript']
}
data.body=process.argv[2];
fs.readFile('jade-example.jade','utf-8',function(error,source){
  var template=jade.compile(source);
  var html=template(data)
  console.log(html);
});
运行node jade-example.js 'email body'时就会输出界面。
Jade的API除了jade.compile(),还有jade.render()和jade.renderFile()。前面的代码还可以用jade.render()重写最后一部分为:
fs.readFile('jade-example.jade','utf-8',function(error,source){
  var html=jade.render(source,data);
  console.log(html);
});
而使用jade.renderFile()重写会更简单:
fs.readFile('jade-example.jade',data,function(error,source){
  console.log(html);
});
通过npm安装Jade时使用选项-g或--global,可以让Jade作为命令行工具来使用。可以使用jade -h查询相关命令参数。
如果要在浏览器中使用Jade,可以使用browserify及其中间件jadeify。还有另外一个模板Handlebars,这个模板不允许使用很多JavaScript,而且要求书写完整的HTML代码,所以不太关心空格和缩进。

六、MongoDB:

NoSQL称为非关系数据库,适合分布式系统。MongoDB是文档存储的NoSQL数据库,使用类似JavaScript的接口。
在www.mongodb.org下载MongoDB。解压缩后会有一个bin目录,输入命令./bin/mongod,或者将MongoDB安装路径添加到PATH环境变量,可以直接输入命令mongod。可以看到:
MongoDB starting: pid=7218 port=27017...
表示MongoDB数据库正在运行,默认监听27017端口。在浏览器打开localhost:28017,可以看到版本号、日志以及其他有用信息。MongoDB占用27017和28017两个端口,用于app通信和统计Web GUI。Node.js可以仅仅使用27017。

1.MongoDB shell命令:
·help:输出可用的命令列表
·show dbs:输出数据库服务器上数据库的名称到连接的控制台上
·use db_name:切换到fb_name
·show collections:输出选择出的数据库集合的列表
·db.collection_name.find(query):查找所有匹配条件的数据
·db.collection_name.findOne(query):查找一条匹配条件的数据
·db.collection_name.insert(document):在collection_name集合中插入一条数据
·db.collection_name.save(document):保留一条数据到collection_name集合中
·db.collection_name.update(query,{$set:data}):用data对象的值更新匹配条件的collection _name集合的数据
·db.collection_name.remove(query):删除collection_name集合中所有匹配条件的数据
·printjson(document):输出参数文档
可以使用JavaScript编写操作脚本:
var a=db.messages.findOne();
printjson(a);
a.text="h1";
printjson(a);
db.messages.save(a);

2.Node.js原生MongoDB驱动:
var mongo=require('mongodb');
   dbHost='127.0.0.1',
   dbPort=27017;
var Db=mongo.Db;
var Connection=mongo.Connection;
var Server=mongo.Server;
var db=new Db('local',new Server(dbHost,dbPort),{safe:true});
db.open(function(error,dbConnection){
  if(error){
    console.error(error);
    process.exit(1);
  }
  console.log('db state: ',db._state);
  item={name:'Azat'}
  dbConnection.collection('messages').insert(item,function(error,item){
    if(error){
      console.error(error);
      process.exit();
    }
    console.info('created/inserted: ',item);
    db.close();
    process.exit(0);
  });
});
从message集合中取出一条数据的代码:
dbConnection.collection('messages').findOne({},function(error,item){
    if(error){
      console.error(error);
      process.exit(1);
    }
    console.info('findOne: ',item);
    db.close();
    process.exit(0);
});
从message集合中取出多条数据并保存为数组:
dbConnection.collection('messages').find(
            {_id:new mongo.ObjectID(id)}).toArray(function(error,item){
    if(error){
      console.error(error);
      process.exit(1);
    }
    console.info('find: ',items);
    db.close();
    process.exit(0);
});

3.Mongoskin:
Mongoskin是一个操作MongoDB的模块,提供比原生驱动更好的API。安装方法:
npm install mongoskin
1)Mongoskin的主要方法:
·findItems(...,callback):查找元素并返回一个数组替代指针
·findEach(...,callback):遍历每个查找到的元素
·findById(id,...,callback):通过_id格式化字符串查找
·removeById(id,...,callback):删除匹配_id的元素
可供选择的MongoDB原生驱动和Mongoskin包括:
·mongoose:支持建模的可配置的异步JavaScript驱动
·mongolia:轻量级的MongoDB ORM驱动
·monk:一个提供方便的极小的层,可用来使用Node.js编码改善MongoDB
以下模块常用来进行数据验证:
·node-validator;验证数据
·express-validator;在express中验证数据
2)使用示例:
与数据库连接:
var mongoskin=require('mongoskin'),
   dbHost='127.0.0.1',
   dbPort=27017;
var db=mongoskin.db(dbHost+':'+dbPort+'/local',{safe:true});
创建数据集合:
db.bind('messages',{
  findOneAndAddText:function(text,fn){
    db.collection('messages').findOne({},function(error,item){
      if(error){
        console.error(error);
        process.exit(1);
      }
      console.info('findOne: ',item);
      item.text=text;
      var id=item._id.toString();
      console.info('before saving: ',item);
      db.collection('messages').save(item,function(error,count){
        console.info('save: ',count);
        return fn(count,id);
      });
    })
  }
});
调用自定义方法:
db.collection('messages').findOneAndAddText('hi',function(count,id){
  db.collection('messages').find({
    _id:db.collection('messages').id(id)
  }).toArray(function(error,items){
    console.info("find: ",items);
    db.close();
    process.exit(0);
  });
});

七、使用session和OAuth进行用户认证和授权:

1.使用express中间件权限管理:
在Web应用中,权限管理指面向不同的用户开放不同的页面权限。可以使用express中间件实现复杂的规则设置,比如限制全部URL、限制部分URL、限制单个URL等。
·全部URL:app.get('*',auth)
·部分URL:app.get('/api/*',auth)
·单个URL:app.get('/admin/users',auth)
限制整个/api/目录访问,使用语句:
app.all('/api/*',auth);
app.get('/api/users',users.list);
app.post('/api/users',users.create);
......
或者使用:
app.get('/api/users',auth,users.list);
app.post('/api/users',auth,users.create);
......
其中,auth()方法接收三个参数,req、res和next。示例:
var auth=function(req,res,next){
  //鉴定用户
  //如果鉴定失败,则调用next(new Error('Not authorized'));
  //或者res.send(401);
    return next();
}
不能忘记调用next()函数,否则express将无法进行后续的处理,包括调用其他回调、继续尝试匹配其他路由规则等。

2.基于token的用户认证:
应用中,会为不同的用户赋予不同的权限,所以需要在auto()函数在添加用户认证的流程。
最常见的方案是基于cookie或session权限管理,更有效的方案是在每次请求中都携带token,并在服务端通过token进行独立认证,既可以把token字段加载到请求参数中,也可以添加到HTTP请求头中。也可以是其他认证信息,如Emai密码、API密钥、API密码等。
示例中每个请求都会提交token字段,并在接收时把通过req.query.token获取的token和应用中存储的token进行对比。如果比对成功则调用next()方法继续后续处理,如果不通过则调用next(error)触发express的错误响应。代码为:
var auth=function(req,res,next){
  if(req.query.token&&token===SECRET_TOKEN){
    //校验通过,进行下一阶段处理
    return next()
  }else{
    return next(new Error('Not authorized'));
    //也可以res.send(401);
  }
};
实际使用中,一般使用API的key和secret生成的基于散列的信息加密算法HMAC-SHA1字符串,并把它和接收到的token进行比对。调用next()方法时传入一个error对象作为参数,表示放弃请求处理,会触发express的错误模式,并进入错误处理流程。

3.基于cookie和session的用户认证:
使用cookie进行用户认证常在用户界面中使用,使用cookie存储session ID,并在请求时提交,基于session的认证就是这种模式,在Web应用中很常见,因为浏览器可以自动处理带有session的请求头,而且大多数后端平台或框架也能原生支持session。
基于session的用户认证借助于请求体对象req中的session对象完成。session可以鉴别客户端,并对应地存储信息,供同一个客户端所有的后续请求读取。
express的一些版本中,需要使用require()引入操作session所依赖的模块,因为核心包中已经不包括这部分操作。引入并使用cookie-parser和express-session模块代码:
var cookieParser=require('cookie-parser');
var session=require('express-session');
......
app.use(cookieParser());
app.use(session());
当然,这些模块需要事先通过npm安装到项目的node_module目录中。
可以在req.session中存储任何数据,它们会自动出现在来自同一个客户端的所有后续请求中。认证信息用session存储的一个布尔值标记,在授权函数中检查这个标记,为真放行,为假则退出。示例:
app.post('/login',function(req,res,next){
  //检查凭证
  //在请求的有效负载中进行传递
  if(checkForCredentials(req)){
    req.session.auth=true;
    req.redirecr('/dashboard'); //非公开内容
  }else{
    res.send(401); //认证不通过
  }
});
应避免在cookie中存储任何敏感信息,因为不安全,而且存储长度有限。推荐不手动操作cookie,cookie中只保留session ID字段,这个字段由express中间件自动控制。
express默认使用内存来存储session数据,在每次应用崩溃或手动重启时session数据会丢失。可以使用Redis或MongoDB存储session数据,这样既可以保证session数据能够持久化存储,也可以实现session跨服务器读取。

4.Node.js的OAuth:
OAuth模块用于在Node.js中开发OAuth,模块可以计算签名、编码信息、生成HTTP头,最后发送请求。但是还需要发起OAuth握手、添加回调路由、在session或数据库中存储信息等,需要使用代码实现。
在处理复杂场景,或只有部分流程使用OAuth时,推荐使用node-auth模块。
Everyauth模块只需要几行代码就可以在基于express的用于中实现OAuth,其自带OAuth配置,包括接口地址、参数名称等,而且会默认将用户信息存储在session中,也可以通过修改findOrCreate的回调函数把用户信息存在数据库中。

八、WebSocket和Socket.IO:

HTML5的WebSocket开创了实时连接的新标准,在服务器端,Node.js有一个高效的非阻塞的I/O平台,非常适合处理后端到浏览器的WebSocket任务。
WebSocket是浏览器和服务器之间的一种特殊的通信通道,是一个HTML5协议。不同于传统的HTTP请求,WebSocket的连接是持久的,它通过在客户端和服务器端之间保持双工连接,服务器的更新可以及时推送给客户端,而不需要客户端以一定的时间间隔去轮询。

1.使用ws模块实现WebSocket通信:
WebSocket通信包括浏览器端和服务器端的代码。
1)浏览器WebSocket的实现:
var ws=new WebSocket('ws://localhost:3000');
一旦建立连接就向服务器发送一条消息:
ws.onopen=function(event){
  ws.send('front-end message: ABC');
};
通常情况下,消息会作为对用户动作的一个响应而被发送,比如单击鼠标。一旦从本地WebSocket中得到消息,处理就会被执行:
ws.onmessage=function(event){
  console.log('Server message: ',event.data);
};
好的代码应有处理错误的方法onerror:
ws.onerror=function(event){
  console.log('Server error message: ',event.data);
};
2)Node.js服务器端代码:
WebSocket.org提供了一个echo服务器用来测试浏览器的WebSocket,也可以用ws模块建立小型WebSocket服务器。
var WebSocketServer=require('ws').Server,
   wss=new WebSocketServer({port:3000});
使用事件监听的方式等待连接的建立,当连接建立完成,在connection事件的回调函数中发送字符串XYZ,并用message事件去监听来自页面的消息:
wss.on('connection',function(ws){
  ws.send('XYZ');
  ws.on('message',function(message){
    console.log('received%s',message);
  });
});
为了在服务器端使用ws模块,需要事先使用npm安装。使用命令node server启动Node.js服务器。在浏览器中打开客户端的页面,可以看到Server message: XYZ消息。而在服务器端,输出消息为received:front-end message: ABC。
WebSocket连接可能会丢失,需要重新建立。

2.Socket.IO:
Socket.IO库很流行,可以使用在express中,使用前需要用npm安装。Socket.IO可以认为是一个服务器,处理的是socket连接。重构的express代码为:
var http=require('http');
var express=require('express');
var path=require('path');
var logger=require('morgan');
var bodyParser=require('body-parser');
var routes=require('./routes/index');
var app=express();
app.set('views',path.join(__dirname,'views'));
app.set('view engine','jade');
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(express.static(path.join(__dirname,'public')));
app.use('/',routes);
Socket.IO部分的代码片段为:
var server=http.createServer(app);
var io=require('socket.io').listen(server);
当Socket服务器的连接建立后,添加事件监听器messageChange实现逆序输入字符串的逻辑:
io.sockets.on('connection',function(socket){
  socket.on('messageChange',function(data){
    console.log(data);
    socket.emit('receive',data.message.splite('').reverse().join(''));
  });
});
最后监听端口启动服务器:
app.net('port',process.env.PORT||3000);
server.listen(app.get('port'),function(){
  console.log('Express server listening on port '+app.get('port'));
});
默认情况下,WebSocket连接可以使用标准端口,HTTP使用80,HTTPS使用443。
客户端还需要在index.jade中写一个表单和前端脚本,与服务器端通信。代码:
extends layout
block content
  h1=title
  p Welcome to
    span.received-message #{title}
  input(type='text',class='message',placeholder='what is on your mind?',onkeyup='send(this)')
  script(src="/socket.io/socket.io.js")
  script.
    var socket=io.connect('http://localhost');
    socket.on('receive',function(message){
      console.log('received %s',message);
      document
        .querySelector('.received-message')
        .innerText=message;
    });
    var send=function(input){
      console.log(input.value);
      var value=input.value;
      console.log('sending %s to server',value);
      socket.emit('messageChange',{message: value});
    };
再次启动服务器,就可以在浏览器中查看到实时通信。当在浏览器的表单中输入文本,服务器也会实时输出日志数据。

Copyright@dwenzhao.cn All Rights Reserved   备案号:粤ICP备15026949号
联系邮箱:dwenzhao@163.com  QQ:1608288659