网站建设
网站备案流程
本机IIS服务器的创建
Win7下配置本机IIS服务器
建网站需要学习的内容
使用表格布局网页
定义网页头文件元素
制作弹出网页
制作网页宣传窗
Div+CSS布局网页
CSS的常见选择器
CSS设置文本样式
CSS设置背景颜色与图像
CSS设置表格样式
HTML中使用CSS的方法
CSS3新增的部分属性1
CSS3新增的部分属性2
CSS3动画与渐变
网页显示MySQL数据库中汉字的乱码问题
HTML5的新特性
HTML5的API
HTML5音视频API
HTML5表单
HTML5表单API
HTML5画布canvas
HTML5拖放API
HTML5地理位置API
HTML5离线应用API
HTML5 Workers多线程
HTML5跨源通信
HTML5 WebSocket通信
HTML5的Web存储API
HTML5本地数据库
HTML5其他一些API
Node.js功能和使用
常用Web前端工具
WebGL编程
GLSL ES语言
使用ThreeJs库3D编程
XML可扩展标记语言
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});
};
再次启动服务器,就可以在浏览器中查看到实时通信。当在浏览器的表单中输入文本,服务器也会实时输出日志数据。