nodejs笔记
目录
简介
node.js不是语言,也不是框架,而是一个基于chrome的JavaScript运行平台,是运行在服务端的JavaScript,所以nodejs更浏览器一样,也是单线程的。
安装:
- 官网下载安装包安装
- 修改安装目录下
./node_modules/npm/npmrc
的文件内容为(修改npm全局安装路径):
prefix = D:/soft/nodejs
cache = D:/soft/nodejs/npm_global_cache
- 查看当前安装版本:
node -v
demo
编写文件:hello.js
console.log("hello world!");
解析执行:node hello.js
文件操作
浏览器中的JavaScript是没有文件操作能力的,但是在node中可以,且文件操作都是异步的(一般参数中有回调函数的函数都是异步函数)。
读取文件
// 加载fs核心模块
// 这里的./是相对于执行node命令的相对路径而不是相对当前文件的相对路径
var fs = require('fs');
fs.readFile("./1.txt", function(error, data){
console.log(error);
console.log(data);
});
写文件
var fs = require('fs');
fs.writeFile('./1.txt', 'hello Node.js', function(error) {
if (error) {
console.log('写入失败')
} else {
console.log('写入成功了')
}
});
HTTP
demo
该模块可以创建和编辑服务器,示例代码:
// 加载http模块
var http = require('http');
var url = require('url');
// 创建一个Web服务器
var server = http.createServer();
// 当客户端发送请求,会执行回调函数
server.on('request', function(req, res) {
console.log('请求地址:', req.socket.remoteAddress, req.socket.remotePort);
console.log('请求路径:' + req.url);
console.log('请求参数:', url.parse(req.url, true));
// 设置编码和响应类型,避免乱码
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.write('hello ');
res.write('nodejs');
// 结束响应,也可以直接使用:res.end("hello nodejs");
// 另外响应只能是字符串,其他对象需要使用JSON.stringify(obj)转化为字符串
// 返回重定向:res.statusCode=302;res.setHeader('Location', '/');
res.end();
});
// 绑定端口号,启动服务器
server.listen(8080, function() {
console.log('启动成功,访问地址:http://127.0.0.1:8080/');
});
据此,对于静态文件,直接读取文件然后响应给客户端就完成了静态web服务器的功能。
根据路径返回文件内容
var http = require('http')
var fs = require('fs')
var server = http.createServer()
var filePath = 'E:/img'
server.on('request', function(req, res) {
fs.readdir(filePath + req.url, function(error, filesArr){
if(!error){
filesArr.forEach(function(data, index){
res.write(`<a href='${req.url}${data}'>${data}</a><br>`)
})
return res.end()
}
})
fs.readFile(filePath + req.url, function(err, data){
if(err){
return res.end('404: ' + err)
}
res.end(data)
})
}).listen(8080, function() {
console.log('启动成功,访问地址:http://127.0.0.1:8080/')
})
Path
path是nodejs中路径操作的核心模块;
常用api示例
path.basename('D:/data/index.js', '.js')
:返回:indexpath.basename('D:/data/index.js')
:返回:index.jspath.dirname('D:/data/index.js')
:返回:D:/datapath.extname('D:/data/index.js')
:放回:.jspath.parse('D:/data/index.js')
:返回:{root: 'D:/', dir: 'D:/data', base: 'index.js', ext: '.js', name: 'index'}
path.join('D:/data/', '/a', 'index.js')
:返回:D:/data/a/index.js
加载与导出
require与export
加载:
- nodejs中通过require加载执行模块(即加载执行文件,nodejs中文件的加载个人认为可以将模块文件当作一个无参函数,加载时相当于执行这个函数),如:
console.log('a start');
// 可以省略后缀名.js
var b = require('./b');
console.log('a end');
// 执行顺序是a start -> b start -> a end
- node会优先从缓存加载,如:a加载b,a加载c,b加载c,此时c中代码只在b中执行一次,a中只是拿到c的返回对象,但不会执行c中的代码;
- 加载第三方包会从上一级目录下的node_modules目录中找,若没有则继续上一级目录找,到根目录都没有则报错。
导出:
- 在node中没有全局作用域,即内部不能访问外部,外部也不能访问内部;
- nodejs在完成模块加载时,会返回module.exports对象;
- node内部还定义了exports=module.exports,所以也可以在内部设置exports的属性给外部使用;
- 外部模块可以通过require获取内部模块返回的module.exports对象。
由上面规则知道,直接给exports或者module.exports赋值,容易混淆返回值,但最终外部获取的对象应该以module.exports的值为准。
__dirname与__filename
在nodejs中,./所代表的相对路径是相对于执行node命令的而不是相对于当前文件的,对此在nodejs的每个模块中有了以下两个特殊成员:
__dirname
:可以用来获取当前文件模块所属目录的绝对路径__filename
:可以用来获取当前文件的绝对路径
art-template
art-template是模板引擎,可以在node和浏览器中执行。
安装:npm install art-template --save(会安装到执行该命令的目录)
文档:http://aui.github.io/art-template/zh-cn/docs/index.html
在浏览器中使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在浏览器中使用art-template</title>
</head>
<body>
·
<!-- 注意:在浏览器中需要引用 lib/template-web.js 文件 -->
<script src="node_modules/art-template/lib/template-web.js"></script>
<script type="text/template" id="tpl">
引入公有页面:{{include './header.html'}}
<p>大家好,我叫:{{ name }}</p>
<p>我今年 {{ age }} 岁了</p>
<h1>我来自 {{ province }}</h1>
<p>我喜欢:{{each hobbies}} {{ $value }} {{/each}}</p>
</script>
<script>
var ret = template('tpl', {
name: 'Jack',
age: 18,
province: '北京市',
hobbies: ['写代码', '唱歌', '打游戏']
})
console.log(ret)
</script>
</body>
</html>
在nodejs中使用
// 在node中使用需要配置环境变量或者在art-template的安装目录下执行
var template = require('art-template')
res = template.render(`
<p>大家好,我叫:{{ name }}</p>
<p>我今年 {{ age }} 岁了</p>
<h1>我来自 {{ province }}</h1>
<p>我喜欢:{{each hobbies}} {{ $value }} {{/each}}</p>
`, {
name: 'Jack',
age: 18,
province: '北京市',
hobbies: ['写代码', '唱歌', '打游戏']
})
console.log(res);
npm
npm网站:https://www.npmjs.com
,所有通过npm命令下载的包都是从这个网站下载的,另外也可以在该网站查看部分npm包的使用文档。
npm常用命令:
npm init
:生成package.json文件,可以加选项:-y,快速生成;npm install
:根据package.json安装第三方包(install可以简写为i);npm install 包名
:安装第三方包;npm install 包名 --save
:安装第三方包,并添加到package.json的dependencies中;npm install 包名 --save-dev
:安装第三方包,并添加到package.json的devDependencies中;npm uninstall 包名
:删除包但保留依赖(uninstall可以简写为un);npm uninstall 包名 --save
:删除包,同时删除依赖;npm install --global 包名
:全局安装;npm uninstall --global 包名
:全局卸载;npm install --global npm
:升级npm;
说明:
- npm5版本以后,不需要–save参数,也会添加依赖,同时还会生成package-lock.json文件;
- package.json文件只会列出当前项目的依赖,且每次重新安装时会自动升级新版本,而不是文件中写的版本;
- package-lock.json会列出当前项目依赖项的依赖,即所有依赖,同时还会列出依赖项的下载地址、版本号,且每次重新安装会使用文件中的版本,不会自动升级。
使用淘宝npm镜像(http://npm.taobao.org
)解决npm很慢的问题:
- 方式一:
- 安装淘宝npm:
npm install --global cnpm
; - 然后将原来的
npm
命令改为cnpm
来执行;
- 安装淘宝npm:
- 方式二:
- 配置全局参数:
npm config set registry https://registry.npm.taobao.org/
; - 查看配置信息:
npm config list
(默认的官方配置为:https://registry.npmjs.org/
); - 然后执行
npm
命令时就会使用淘宝镜像了。
- 配置全局参数:
nrm:nrm提供了一些常用的npm包镜像地址,能够让我们快速的切换包镜像服务器地址;使用方式:
npm install nrm -g
:安装;nrm ls
:查看镜像地址列表,列表中带*
的就是正在使用的;nrm use 镜像名
:使用指定镜像地址。
express
express是基于nodejs的开源的轻量级的web框架;
安装:npm install express --save
;
demo
该demo具有路由模块原则,还整合了art-template以及get、post请求参数的获取:
main.js
包含以下模块的配置示例:
- art-template模板引擎配置
- body-parser中间件配置
- 静态资源路径配置
- session模块配置
- 两种路由使用方式的配置
- express中的中间件的使用
- 404处理
- 全局错误处理
var express = require('express')
var app = express()
// 使用模板引擎art-template,模板默认路径在views/下
app.engine('html', require('express-art-template'))
// 修改模板默认路径views/修改为html/
// app.set('views', 'html/')
// 配置 body-parser 中间件(插件,专门用来解析表单 POST 请求体,使用方法:req.body)
var bodyParser = require('body-parser')
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
// 指定静态资源路径
app.use('/public', express.static('./public/'))
// 引入并配置session,使用方法:req.session.attr1=value1
session = require('express-session')
app.use(session({secret: 'xxx', resave: false, saveUninitialized: true}))
// 指定处理具体请求的router,这个需要放在app的配置后面
var router = require('./router.js')
// 方式一(推荐)
// router中配置的请求,本质上也是中间件
app.use(router)
// 方式二
// router(app)
// 中间件:满足规则的所有url都会进入中间件方法
// 第一个参数可以限定中间件匹配的url前缀,没有则不限定url
// 多个中间件按代码顺序匹配
// 多个中间件使用的req和res是同一个对象
// app.get()与app.post()方法本质上也是中间件
app.use(function(req, res, next){
console.log("中间件1")
//next() //调用next方法才会执行后面的中间件
})
// 一般最后一个不限定url的中间件用于处理404
app.use(function(req, res, next){
console.log("中间件2")
res.send("404")
})
// 统一异常处理中间件,4个参数一个不能少
// 在上面的中间件中调用next(err)方法,会跳过上面匹配的中间件而直接进入该方法
app.use(function(err, req, res, next){
res.send('出错了')
})
app.listen(8080, function(){
console.log('启动成功,访问地址:http://127.0.0.1:8080/')
})
router.js
两种路由实现方式,与上面的main.js中的两种方式相对应,还包含:
- get方式参数的获取
- post方式参数的获取
- art-template模板引擎的使用
- 以下代码,本质上也是中间件
// 方式一(推荐)
var express = require('express')
var router = express.Router()
router.get('/', function(req, res, next){
res.render('index.html', {name: '首页'})
}).get('/get', function(req, res){
res.send(req.query)
}).post('/post', function(req, res){
res.send(req.body)
})
module.exports = router
// 方式二
/*module.exports = function(app){
// 方法名对应请求方式的get、post,第一个参数是访问路径
app.get('/', function(req, res){
res.render('index.html', {name: '首页'})
}).get('/get', function(req, res){
res.send(req.query)
}).post('/post', function(req, res){
res.send(req.body)
})
}*/
index.html
art-template要渲染的模板内容,该文件路径为:./views/index.html
:
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>index</title>
</head>
<body>
<h1>这是{{name}}</h1>
<a href="/get?name=张三&age=22">get请求</a><br>
<form action="/post" method="post">
<input type="text" name="name" value="李四"/><br>
<input type="number" name="age" value="3"/><br>
<input type="submit" value="post提交"/><br>
</form>
</body>
</html>
热部署插件
nodemon会监视文件变化并自动重启服务器,使用方法如下:
npm install --global nodemon
:安装第三方命令行工具;- 之后将原来的执行命令
node xxx.js
改为nodemon xxx.js
;
连接MongoDB
原生操作
文档:https://github.com/mongodb/node-mongodb-native/
安装:npm install mongodb --save
;
mongoose
英文文档:https://mongoosejs.com/ 中文文档:http://www.mongoosejs.net
安装:npm install mongoose --save
;
示例代码:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test01', {useNewUrlParser: true, useUnifiedTopology: true});
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// 这两行必须在调用model方法前面
var StuSchema = new mongoose.Schema({name: {type: String, required: true}, age: Number});
StuSchema.methods.say = function(){console.log(this.name + ": " + this.age);};
var Stu = mongoose.model('stus', StuSchema);
// var s1 = new Stu({name: "张三", age: 28});
// s1.save(function(err, s1){
// if(err){
// return console.error(err);
// }
// console.log("保存成功: " + s1);
// });
Stu.find(function(err, stus){
if(err){
return console.error(err);
}
console.log(stus);
stus.forEach(function(v, i){v.say();});
});
});
express-mongoose
示例代码:
main.js
var express = require('express')
var app = express()
app.engine('html', require('express-art-template'))
var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())
app.use('/public', express.static('./public/'))
var router = require('./router.js')
app.use(router)
app.listen(8080, function(){
console.log('启动成功,访问地址:http://127.0.0.1:8080/')
})
router.js
var express = require('express')
var router = express.Router()
var Stu = require('./mongoose_stu.js')
/**
* 首页,所有学生列表
*/
router.get('/', function(req, res){
Stu.find(function(err, stus){
if(err){
res.render('index.html', {datas: [], msg: err})
}else{
res.render('index.html', {datas: stus, msg: ''})
}
});
})
/**
* 添加
*/
router.post('/save', function(req, res){
new Stu(req.body).save(function(err, stu){
if(err){
res.render("index.html", {datas: [], msg: err})
}else{
res.redirect("/")
}
})
})
/**
* 删除
*/
router.get('/delete', function(req, res){
Stu.findByIdAndRemove(req.query.id, function(err){
if(err){
res.render('index.html', {datas: [], msg: err})
}else{
res.redirect("/")
}
})
})
/**
* 更新
*/
router.post('/update', function(req, res){
Stu.findByIdAndUpdate(req.body.id, req.body, function(err){
if(err){
res.render('index.html', {datas: [], msg: err})
}else{
res.redirect("/")
}
})
})
module.exports = router
mongoose_stu.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test01', {useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false});
// 这两行必须在调用model方法前面
var StuSchema = new mongoose.Schema({name: {type: String, required: true}, age: Number});
StuSchema.methods.say = function(){
console.log(this.name + ": " + this.age);
};
var Stu = mongoose.model('stus', StuSchema);
module.exports = Stu;
index.html
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>index</title>
</head>
<body>
<a href="/">首页</a>
<p>{{msg}}</p>
{{each datas}}
<li>{{$value.id}}===>{{$value.name}}===>{{$value.age}}<a href="/delete?id={{$value.id}}">删除</a></li>
{{/each}}
<br><br>
<form action="/save" method="post">
name: <input type="text" name="name"/><br>
age: <input type="text" name="age"/><br>
<input type="submit" value="添加"/>
</form>
<form action="/update" method="post">
id: <input type="text" name="id"/><br>
name: <input type="text" name="name"/><br>
age: <input type="text" name="age"/><br>
<input type="submit" value="修改"/>
</form>
</body>
</html>
package.json
{
"name": "express_mongoose_demo",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"art-template": "^4.13.2",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"express-art-template": "^1.0.1",
"mongoose": "^5.8.0"
}
}
Promise
回调地狱:JavaScript编程中,异步代码不能保证执行顺序,要保证执行顺序需要在异步代码的回调函数编写后序执行的代码,即回调嵌套,也叫回调地狱。这种代码难以维护,所以在EcmaScript6中添加了Promise API。
Promise API示例代码(mongoose的所有数据库操作API都支持promise):
var fs = require('fs')
// 异步任务1
var p1 = new Promise(function (resolve, reject) {
fs.readFile('./data/a.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
// 异步任务2
var p2 = new Promise(function (resolve, reject) {
fs.readFile('./data/b.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
// 顺序执行1、2,这里then方法第一个参数是上面的resolve,第二个参数是上面的reject
p1.then(function (data) {
console.log(data)
//return一个Promise 对象的时候,以便后续异步任务执行
return p2
}, function (err) {
console.log('读取文件失败了', err)
}).then(function (data) {
console.log(data)
})