day01
node简介
Node.js是一个让JavaScript运行在服务器端的开发平台。
node就是一个js的执行环境-
node 与其它后台语言的不同:
- Node.js不是一种独立的语言,与PHP、JSP、Python、Perl、Ruby的“既是语言,也是平台”不同,Node.js的1. 使用JavaScript进行编程,运行在JavaScript引擎上(V8)。
- 与PHP、JSP等相比(PHP、JSP、.net都需要运行在服务器程序上,Apache、Naginx、Tomcat、IIS。),Node.js跳过了Apache、Naginx、IIS等HTTP服务器,它自己不用建设在任何服务器软件之上。
- Node.js没有web容器。
URL是通过了Node的顶层路由设计,呈递某一个静态文件的。
-
node特点:
--所谓的特点,就是Node.js是如何解决服务器高性能瓶颈问题的
- 单线程
在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存。也就是说,理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右。
Node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4万用户的连接。
另外,单线程的带来的好处,还有操作系统完全不再有线程创建、销毁的时间开销。
坏处,就是一个用户造成了线程的崩溃,整个服务都崩溃了,其他人也崩溃了。 - 非阻塞I/O ( non-blocking I/O )
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞模式下,一个线程永远在执行计算操作,这个线程的CPU核心利用率永远是100%。 - 事件驱动( event-driven )
在Node中,客户端请求建立连接,提交数据等行为,会触发相应的事件。
Node.js中所有的I/O都是异步的,回调函数,套回调函数。 - 适合开发什么?
- 善于I/O,不善于计算。
因为Node.js最擅长的就是任务调度,如果你的业务有很多的CPU计算,实际上也相当于这个计算阻塞了这个单线程,就不适合Node开发。 - 当应用程序需要处理大量并发的I/O,而在向客户端发出响应之前,应用程序内部并不需要进行非常复杂的处理的时候,Node.js非常适合。
- Node.js也非常适合与web socket配合,开发长连接的实时交互应用程序。
- 比如:
● 用户表单收集
● 考试系统
● 聊天室
● 图文直播
- 善于I/O,不善于计算。
- 单线程
http模块
//require表示引包,引包就是引用自己的一个特殊功能
//require的时候会执行一遍此文件,而且不管require几次只会执行一次。
//后面有用户再访问不会再次执行、引用
var http = require("http"); //创建服务器,参数是一个回调函数,表示如果有请求进来,要做什么
var server = http.createServer(function(req,res){
//req表示请求,request; res表示响应,response //设置HTTP头部,状态码是200,文件类型是html,字符集是utf8
res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
res.end("哈哈哈哈,我买了一个iPhone" + (1+2+3) + "s");
}); //运行服务器,监听3000端口(端口号可以任改)
server.listen(3000,"127.0.0.1");
url模块
- req
req.url 属性,表示用户的请求URL地址。所有的路由设计,都是通过req.url来实现的。 - 识别URL
用到两个新模块,第一个就是querystring模块,第二个就是url模块querystring.parse('foo=bar&baz=qux&baz=quux&corge')
// returns
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' } // it can decode `gbk` encoding string
querystring.parse('w=%D6%D0%CE%C4&foo=bar', null, null,
{ decodeURIComponent: gbkDecodeURIComponent })
// returns
{ w: '中文', foo: 'bar' }//url.parse()可以将一个完整的URL地址,分为很多部分:
//host、port、pathname、path、query var pathname = url.parse(req.url).pathname;
//url.parse()如果第二个参数是true,则 query 属性会通过 querystring 模块的 parse() 方法生成一个对象
//就可以直接打点得到这个参数
var query = url.parse(req.url,true).query;
var age = query.age; //直接打点得到这个参数
fs模块
- 事件循环机制
var server = http.createServer(function(req,res){
//不处理小图标
if(req.url == "/favicon.ico"){return;} //给用户加一个五位数的id
var userid = parseInt(Math.random() * 89999) + 10000; console.log("欢迎" + userid); res.writeHead(200,{"Content-Type":"text/html;charset=UTF8"});
//两个参数,第一个是完整路径,当前目录写./
//第二个参数,就是回调函数,表示文件读取成功之后,做的事情
fs.readFile("./test/1.txt",function(err,data){ //异步执行,不会阻塞前后的代码
if(err){ throw err; } console.log(userid + "文件读取完毕");
res.end(data);
});
});
// 并不一定是打印每一次 “欢迎..”间隔打印一次 “..文件读取完毕” - API
1. //创建文件夹
fs.mkdir("./album/aaa"); 2. //stat检测状态,*异步执行*
fs.stat("./album/bbb",function(err,stats){
//检测这个路径,是不是一个文件夹
console.log(stats.isDirectory());
}); - 异步导致的错误
失败案例,列出album文件夹中的所有子文件夹 3. //读取文件夹
fs.readdir("./album",function(err,files){
//files是个文件名的数组,表示./album这个文件夹里的所有东西(文件、文件夹) for(var i = 0 ; i < files.length ;i++){
var thefilename = files[i];
//进行一次检测
fs.stat("./album/" + thefilename,function(err,stats){
//由于异步,本次使用的 thefilename,并不一定是本次循环所赋的值 if(stats.isDirectory()){ //如果他是一个文件夹,那么输出它
wenjianjia.push(thefilename);
}
console.log(wenjianjia); //由于异步不会真的打印所出有文件夹
});
}
}); <!--循环里面放异步就容易出现这种错误-->
异步变成同步(迭代器)
-
遍历album里面的所有文件、文件夹
fs.readdir("./album/",function(err,files){
//files是一个存放文件(夹)名的数组
var wenjianjia = []; //存放文件夹的数组 //迭代器就是强行把异步的函数,变成同步的函数
// 1做完了,再做2;2做完了,再做3
(function iterator(i){
//结束遍历
if(i == files.length){
console.log(wenjianjia);
return; //一定不能丢,否则结束不了会报错
} var thefilename = files[i];
fs.stat("./album/" + thefilename,function(err,stats){
if(stats.isDirectory()){
//如果是文件夹,那么放入数组。不是,什么也不做
wenjianjia.push(thefilename);
}
iterator(i+1);
});
})(0);
});
path
1. path.extname('index.html');
// 返回: '.html'
path.extname('index.coffee.md');
// 返回: '.md' 2. path.normalize() 方法会规范化给定的 path,并解析 '..' 和 '.' 片段
path.normalize('/foo/bar//baz/asdf/quux/..');
// 返回: '/foo/bar/baz/asdf'
静态资源文件管理
var http = require("http");
var fs = require("fs");
var url = require("url");
var path = require("path"); var server = http.createServer(function(req,res){ //得到地址
var pathname = url.parse(req.url).pathname;
//判断此时用户输入的地址是文件夹地址还是文件地址
//如果是文件夹地址,那么自动请求这个文件夹中的index.html
if(pathname.indexOf(".") == -1){
pathname += "/index.html";
}
//输入的网址是127.0.0.1/images/logo.png
//实际请求的是./static/images/logo.png
var fileURL = "./" + path.normalize("./static/" + pathname);
//得到拓展名
var extname = path.extname(pathname); //把文件读出来
fs.readFile(fileURL,function(err,data){
//读完之后做的事情
if(err){
//文件不存在
res.writeHead(404,{"Content-Type":"text/html;charset=UTF8"})
res.end("404,页面没有找到");
}
//读完之后做的事情
getMime(extname,function(mime){
res.writeHead(200,{"Content-Type":mime})
res.end(data);
});
});
}); server.listen(80,"127.0.0.1"); function getMime(extname,callback){
//读取mime.json文件,得到JSON,根据extname key ,返回对应的value
//读取文件,异步,异步,异步
fs.readFile("./mime.json",function(err,data){
if(err){
throw Error("找不到mime.json文件!");
return;
}
//转成JSON
var mimeJSON = JSON.parse(data);
var mime = mimeJSON[extname] || "text/plain";
//执行回调函数,mime类型字符串,就是它的参数
callback(mime);
});
}
day02
模块化
-- 狭义的说,每一个JavaScript文件都是一个模块;而多个JavaScript文件之间可以相互require,他们共同实现了一个功能,他们整体对外,又称为一个广义上的模块。
Node.js中,一个JavaScript文件中定义的变量、函数,都只在这个文件内部有效。
当需要从此JS文件外部引用这些变量、函数时,必须使用exports
对象进行暴露。使用者要用require()
命令引用这个JS文件。一个JavaScript文件,可以向外exports无数个变量、函数。但是require的时候,仅仅需要require这个JS文件一次。使用的它的变量、函数的时候,用点语法即可。所以,无形之中,增加了一个顶层命名空间。
Node中,js文件和js文件,就是被一个个
exports
和require
构建成为网状的。而不是靠html文件统一在一起的。可以将一个JavaScript文件中,描述一个类。用
module.export = 构造函数名
的方式向外暴露一个类。-
也就是说,js文件和js文件之间有两种合作的模式:
- 某一个js文件中,提供了函数,供别人使用。 只需要暴露函数就行了:
exports.msg=msg;
- 某一个js文件,描述了一个类:
module.exports = People;
, 此时,People就被视为构造函数,可以用new来实例化了。
- 某一个js文件中,提供了函数,供别人使用。 只需要暴露函数就行了:
文件夹模块
var foo = require("foo.js");
没有写./
, 所以不是一个相对路径。是一个特殊的路径那么Node.js将该文件视为node_modules目录下的一个文件。-
node_modules文件夹并不一定在同级目录里面,在任何直接祖先级目录中,都可以。甚至可以放到NODE_PATH环境变量的文件夹中。
var bar = require("bar");
//那么Node.js将会去寻找node_modules目录下的bar文件夹中的index.js去执行。 -
每一个模块文件夹中,推荐都写一个package.json文件,这个文件的名字不能改,必须放在模块的根目录里。node将自动读取里面的配置。有一个main项,就是入口文件:
{
"name": "kaoladebar",
"version": "1.0.1",
"main" : "app.js"
} -
package.json
"dependencies": {
"silly-datetime": "^0.^1.0" //^ 表示固定
},
路径
1. require()别的js文件的时候,将执行那个js文件。
require()
中的路径,是从当前这个js文件出发,找到别人。而fs是从命令提示符找到别人。
所以,桌面上有一个a.js,test文件夹中有b.js、c.js、1.txt
// a要引用b:
var b = require(“./test/b.js”);
// b要引用c:
var b = require(“./c.js”);
但是,fs等其他的模块用到路径的时候,都是相对于cmd命令光标所在位置。
所以,在b.js中想读1.txt文件,推荐用绝对路径:
```
fs.readFile(__dirname + "/1.txt",function(err,data){
if(err) { throw err; }
console.log(data.toString());
});
```
```
var b = require(“./test/b.js”); // './' 指当前文件路径
fs.readFile( "./uploads",function(err,data){ // './' 指根目录文件路径
...
});
```
body-parser、formidable
```
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
app.post("/",function(req,res){
console.log(req.body);
});
```
```
var formidable = require('formidable');
var server = http.createServer(function(req,res){
if(req.url == "/dopost" && req.method.toLowerCase() == "post"){
//Creates a new incoming form.
var form = new formidable.IncomingForm();
//设置文件上传存放地址
form.uploadDir = "./uploads";
//执行里面的回调函数的时候,表单已经全部接收完毕了。
form.parse(req, function(err, fields, files) {
if(err){throw err;}
console.log(fields); //所有的文本域、单选框,都在fields存放;
console.log(files); //所有的文件域,files
res.writeHead(200, {'content-type': 'text/plain'});
res.end("成功");
});
}
});
```
day03
express路由能力
var express = require("express");
var app = express();
//当用get请求访问一个网址的时候,做什么事情:
//所有的GET参数,?后面的都已经被忽略。锚点#也被忽略;你路由到/a,实际/a?id=2&sex=nan 也能被处理
app.get("/haha",function(req,res){
res.send("这是haha页面,哈哈哈哈哈哈");
});
。
//当用post访问一个网址的时候,做什么事情:
app.post("网址",function(req,res){ //互联网的网址,不分大小写
});
//如果想处理这个网址的任何method的请求,那么写all
app.all("/",function(){
});
//正则表达式可以被使用。正则表达式中,未知部分用圆括号分组,然后可以用req.params[0]、[1]得到。req.params类数组对象。
app.get(/^\/student\/([\d]{10})$/,function(req,res){
res.send("学生信息,学号" + req.params[0]); //req.params[0]指第一个小括号匹配到的内容
});
app.get("/student/:id/:name",function(req,res){
var id = req.params["id"]; // :后的内容自动加入到req.params中(类数组对象)
var name = req.params["name"];
var reg= /^[\d]{6}$/; //正则验证
if(reg.test(id)){
res.send(id);
}else{
res.send("请检查格式");
}
});
app.listen(3000);
express静态文件
var express = require("express");
var app = express();
app.use(express.static("./public"));
app.get("/haha",function(req,res){
res.send("haha ");
});
app.listen(3000);
express与模版引擎配合
var express = require("express");
var app = express();
app.set("view engine","ejs"); //不需要再require('ejs')
app.set('views', './views'); //设置存放模版文件路径,默认就是views
app.get("/",function(req,res){
res.render("haha",{
"news" :["我是小新闻啊","我也是啊","哈哈哈哈"]
});
});
app.listen(3000);
express中间件
- 如果我的get、post回调函数中,没有next参数,那么匹配上第一个路由,就不会往下匹配了。
如果想往下匹配的话,那么需要写next();app.get("/",function(req,res,next){
console.log("1");
next();
}); app.get("/",function(req,res){
console.log("2");
});
//不会打印 2 - 下面两个路由,感觉没有关系,但是实际上冲突了,因为admin可以当做用户名 login可以当做id。
app.get("/:username/:id",function(req,res){
console.log("1");
res.send("用户信息" + req.params.username);
}); app.get("/admin/login",function(req,res){
console.log("2");
res.send("管理员登录");
});解决1: 交换位置。也就是说,express中所有的路由(中间件)的顺序至关重要。匹配上第一个,就不会往下匹配了。所以具体的往上写,抽象的往下写。
app.get("/admin/login",function(req,res){
console.log("2");
res.send("管理员登录");
}); app.get("/:username/:id",function(req,res){
console.log("1");
res.send("用户信息" + req.params.username);
});解决2: 路由get、post、use这些东西,就是中间件,中间件讲究顺序,匹配上第一个之后,就不会往后匹配了。next函数才能够继续往后匹配。
app.get("/:username/:id",function(req,res,next){
var username = req.params.username;
//检索数据库,如果username不存在,那么next()
if(检索数据库){
console.log("1");
res.send("用户信息");
}else{
next();
}
}); app.get("/admin/login",function(req,res){
console.log("2");
res.send("管理员登录");
});
express - use
-
app.use()
也是一个中间件。与get、post不同的是,他的网址不是精确匹配的,而是能够有小文件夹拓展的(子路径)。
比如网址: http://127.0.0.1:3000/admin/aa/bb/cc/ddapp.use("/admin",function(req,res){ res.write(req.originalUrl + "\n"); // /admin/aa/bb/cc/dd
res.write(req.baseUrl + "\n"); // /admin
res.write(req.path + "\n"); // /aa/bb/cc/dd res.end("你好");
});如果写一个
/
//当你不写路径的时候,实际上就相当于"/",就是所有网址
app.use(function(req,res,next){
console.log(new Date());
next();
});app.use()就给了我们增加一些特定功能的便利场所。
实际上app.use()的东西,基本上都从第三方能得到。app.use(haha); function haha(req,res,next){
var filePath = req.originalUrl;
//根据当前的网址,读取public文件夹中的文件
fs.readFile("./public/" + filePath,function(err,data){
if(err){ //文件不存在
next();
return;
}
res.send(data.toString());
});
} //静态服务
app.use(express.static("./public"));
// jingtai路由 下才会访问静态文件
app.use("/jingtai",express.static("./public"));
ejs - render()、express - send()、原生 - end()
大多数情况下,渲染内容用
res.render()
,将会根据views中的模板文件进行渲染。
如果不使用views文件夹,那么app.set("views","aaaa");
如果想写一个快速测试页,可以使用
res.send()
。
这个函数将根据内容,自动帮我们设置了Content-Type头部和200状态码。send()
和end()
一样,只能用一次。
和end不一样send()
能够自动设置MIME类型。如果想使用不同的状态码,可以:
res.status(404).send('Sorry, we cannot find that!');
如果想使用不同的Content-Type,可以:
res.set('Content-Type', 'text/html');
GET请求和POST请求的参数
- GET请求的参数在URL中,在原生Node中,需要使用
url
模块来识别参数字符串。在Express中,不需要使用url模块了。可以直接使用req.query
对象。 - POST请求在express中不能直接获得,必须使用
body-parser
模块。使用后,将可以用req.body
得到参数。但是如果表单中含有文件上传,那么还是需要使用formidable
模块。