1.Node.js介绍
1.1Node.js是什么
- node.js是一个JavaScript运行时环境,可以解析和执行JavaScript代码
- 也就是说现在的JavaScript可以万千脱离于浏览器运行,一切归功于,Node.js
- Node.js中的JavaScript
- 没有BOM、DOM
- 在Node这个JavaScript执行环境中为JavaScript提供了一些服务器级别的操作API
- 例如文件读写
- 网络服务的构建
- 网络通信
- http服务器
- 等处理。。。
1.2Node.js能做什么
- web服务器后台
- 命令行工具
- npm
- git
- hexo博客
- 。。。
- 对于前端来讲,接触node最多的是它的命令行工具
- 自己写的少,主要使用第三方的
- webpack
- gulp
- npm
1.3学习资源
http://javascript.ruanyifeng.com/
ECMAScript语法
在Node中采用采用EcmaScript进行编码
< https://www.w3cschool.cn/ecmascript/8j1d1q68.html>
2.Node.js环境安装及使用
2.1Hello World例子
1.编写JavaScript脚本文件
2.打开终端,定位到脚本文件所在目录
3.输入node 文件名 执行对应的文件
注意:脚本文件命名不能用node.js命名,最好不要使用中文
- 解析执行JavaScript
- 读写文件
- http
3.Node.js语法
3.1Node.js文件系统
3.1.1如何执行脚本文件
注意:浏览器不认识具有特定Node API(如:fs文件系统模块的代码)的代码,但是浏览器认识ECMAScript语法的的JavaScript语言部分,如 consle.log(),BOM,DOM的代码
浏览器中的JavaScrip没有操作文件的能力
//NOde中的JavaScript具有文件操作的能力
//fs 是file-system的简写,就是文件系统的意思
//在Node中要想进行问及那操作,必须引入fs这个核心模块
//在fs这个核心模块中,就提供了所有文件相关的API
//例如:fs.readFile就是用来读取文件
//1.使用require方法加载fs核心模块
var fs=require('fs')
//2.读取文件
// 第一个参数是要读取的文件路径
// 第二个参数的一个回调函数
// 成功
// data 数据
// error null
// 失败
// data null
// error 错误对象
var fs=require('fs')
fs.readFile('./data/hello.txt',function(error,data){
//<Buffer 73 74 75 64 79 20 6e 6f 64 65 6a 73>
//文件中存储的其实都是二进制数据
//为什么这里看到的不是0和1呢?原因是二进制转为十六进制了
//通过 toString转为我们能够认识的字符
// console.log(data)
console.log(data.toString())
})
3.1.2写文件
var fs=require('fs')
// 第一个参数:文件路径
// 第二个参数:文件内容
// 第三个参数:回调函数
// error形参
// 成功:
// 文件写入成功
// error是null
// 失败:
// 文件写入失败
// error就是错误对象
fs.writeFile('./data/你好>.md','大家好,我是Node.js',function(error){
//console.log('文件写入成功')
//console.log(error)
if(error){
console.log('写入失败')
console.log(error)
}else{
console.log('写入成功')
console.log(error)
}
})
let fs = require('fs');
fs.writeFile('1.txt','hello',(err)=>{
console.log('完成');
})
3.1.3 读文件
let fs = require('fs');
fs.readFile('./1.txt','utf8',(err,data) => {
if(err){
console.log(err);
}
else{
console.log(data);
// console.log(data.toString());
}
});
3.2最简单的http服务
//使用Node构建一个web服务器
//在Node中提供了一个核心模块:http
//http这个模块的作用:帮我们创建编写服务器
//1.加载http核心模块
var http=require('http')
//2.使用http.createServer()方法创建一个web服务器
//返回一个 Server实例
var server=http.createServer()
//3.服务器
// 提供服务:对 数据的服务
// 发请求
// 接收请求
// 处理请求
// 给个反馈(发送响应)
// 注册request请求事件
// 当客户端请求过来,就会自动触发服务器的request请求事件,然后执行第二个参数,回调处理函数
server.on('request',function(){
console.log('收到客户端请求')
})
//4.绑定端口号,启动服务器
server.listen(3000,function(){
console.log('服务器启动成功,可以通过http://127.0.0.1:3000/或者localhost来进行访问')
})
3.2.1抓取接口数据
// 抓取数据
// 导入http模块
let http = require('http');
// 获取别人的数据(自带模块,只能做get,不能做post)
// 给别人提供数据
http.get('http://81.68.77.84/wy_shop/category/searchTreeLv1CategoryName.php',(res)=>{
// console.log(res);
let html = '';
// on 等价于addEventListener
res.on('data',(content)=>{
// 当有数据变化时,触发该函数
html+= content;
})
res.on('end',()=>{ // 数据全部接收完毕
console.log(html)
})
})
3.2.2抓取页面
// 抓取页面
let http = require('http');
let fs = require('fs');
// 获取别人的数据(自带模块,只能做get,不能做post)
// 给别人提供数据
http.get('http://www.hscoder.top/',(res)=>{
// console.log(res);
let html = '';
// on 等价于addEventListener
res.on('data',(content)=>{
// 当有数据变化时,触发该函数
html+= content;
})
res.on('end',()=>{ // 数据全部接收完毕
// console.log(html)
fs.writeFile('hs.html',html,(err)=>{
console.log('完成');
})
})
})
3.3Node中的JavaScript
-
Ecmascript
- 没有DOM、DOM
-
核心模块
-
第三方模块
-
用户自定义模块
3.3.1核心模块
Node为JavaScript提供了很多服务器级别的API,这些API绝大多数都被包装到一个具名的核心模块中。
例如文件操作的fs核心模块,http构建的http模块,path路径操作模块,os系统信息操作模块
如果模块是核心模块,要是用,必须使用require引用加载模块
var fs=require('fs')//require加载模块
var http=req
- http
- 端口号定位具体的应用程序
3.3.2Content-Type
- Content-Type服务器最好把每次响应的数据类型,告诉客户端,正确告诉
- 不同资源对应的类型是不一样的,具体参考: http://tool.oschina.net/commons
- 对于文本类型的数据,最好加上乱码,防止中文解析乱码
- 通过网络发送文件
- 发送的其实不是文件,本质上是文件的内容
- 当浏览器接收到服务器响应内容之后,根据Content-Type类型进行解析
3.3.3模块作用域与通信
-
在Node中没有全局作用域的概念
-
在Node中,只能通过require方法加载多个JavaScript脚本文件
-
require加载只能是执行其中的代码,文件与文件之间是由于模块作用域,所以不会有污染
外部无法访问内部
内部也无法访问外部
模块作用域的好处:可以加载执行多个文件,可以完全避免变量命名冲突问题
但是在某些情况下,模块之间需要进行通信
在每一个模块中,都提供了一个对象:
exports
该对象默认是一个空对象
把需要被外部访问的成员,手动挂载到exports,这个借口对象上,然后谁来require这个模块,通过对象成员访问其他模块内部,得到其他模块内部的exports接口对象
3.4web服务器开发
3.4.1在Node中使用模板引擎
安装:
npm install art-template --save
使用:
var template=require('art-template')
var ret=template=template.render('hello' {{ name }},{
name:'honor'
})
console.log(ret) //=> hello honor
//art-template
//art-template不仅可以在浏览器使用,也可以在node使用
//安装
// npm install art-template --save
// 该命令在哪执行,把包下载到哪里,默认下载到node_modules
// 下载目录不要更改,也不支持改
// 在Node中使用art-template模板引擎
// 模板引擎最早诞生于服务器端,后发=发展到前端
// 1.安装npm install art-template --save
// 2.在需要使用的文件模块中加载art-tempplate
// require方法加载:require('art-template')
// 参数中的art-template的名字,就是你下载的包的名字
// 即:install的名子是什么,require的名子就是什么
// 3.查文档,使用模板引擎的API
var fs=require('fs')
//node把模板字符串存放到文件里面
// 这里不是浏览器,这样行不通 template('script标签id',{对象})
// var tplStr=`
// <!DOCTYPE html>
// <html lang='en'>
// <head>
// <meta chars et="UTF-8">
// <meta name="viewport" content="width=device-width, initial-scale=1.0">
// <meta http-equiv="X-UA-Compatible" content="ie=edge">
// <title>Document</title>
// </head>
// <body>
// <p>大家好,我的名字是:{{ name }}</p>
// <p> 我今年{{ age }}岁了</p>
// <h1> 我来自{{ province }}<h1>
// <p> 我喜欢{{ each hobbies }} {{ $value }} {{ /each }}</p>
// </body>
// </html>
// `
fs.readFile('./tpl.html',function(err,data){
if(err){
return console.log('读取失败')
}
//默认读取到的data,默认是二进制数据
//而模板引擎的render方法,需要接收的是字符串
//在这里需要把data二进制数据转为字符串,才可以给模板引擎使用
var ret=template.render(data.toString(),{
name: 'honor',
age: 18,
province: '湖北省',
hobbies: [
'写代码',
'喝水',
'上厕所'
],
title:'个人信息'
})
console.log(ret)
})
反引号之间的字符串,高亮显示:honor
3.4.2jQuery的each和JavaScript原生的forEach
each是art-tempalte的模板语法,只有art-tempalte专属模板语法
{{each 遍历项}}
<li>{{ $value }}</li>
{{/each}}这是art-tempalte模板引擎支持的语法,只能在模板字符串中使用
jquery中的each语法
$.each(数组,function)
$(‘div’).each(function),一般用于jQuery选择器选择到的伪数组实例对象
区别:
jQuery的each
-
jQuery的each由第三方库提供
-
jQuery 2以下的版本是兼容IE8的
-
它的each方法主要用来遍历jQuery 实例对象(伪数组)
-
同时,它可以作为低版本浏览器中forEach的替代品
-
jQuery的实例对象,即伪数组不能使用forEach的实例方法
-
可以通过;[].slice.call($(‘div’)).forEach(function(item){
//;[].slice.call(‘jQuery实例对象’),slice是截取返回数组,不传参,就从0截到最后一个
console.log(item)
})
slice() 方法可从已有的数组中返回选定的元素。 语法 arrayObject.slice(start,end) start 必需参数。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。 end 可选参数规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。 ['a','b','c'].slice()//不穿参数,全部截取 ["a", "b", "c"] ['a','b','c'].slice(1)//传一个参数,不设置end参数,从start一直截取到尾部 ["b", "c"] ['a','b','c'].slice(2) ["c"] //负值从数组的尾部选取元素 ['a','b','c'].slice(-1)//-1 指最后一个元素,-2 指倒数第二个元素,依次类推 ["c"] ['a','b','c'].slice(-2,-1) ["b"] ['a','b','c'].slice(-3,-1) ["a", "b"]
Array.prototype.mySlice=function(){ var start=0 var end=this.length //this就是下面的fakeArr if(arguments.length===1){//传一个参数 start=arguments[0] }else if(arguments.length===2){ start=arguments.[0] end=arguments.[1] } var temp=[] for(var i=start;i<end;i++){ // fakeArr[0] // fakeArr[1] // fakeArr[2] temp.push(this[i]) } return temp } var=fakeArr:{//伪数组 0:'abc', 1:'efg', 2:'haha', length:3 } //得到真正的数组 [].mySlice.call(fakeArr)//这样就将伪数组转成了数组
将伪数组转成数组,就可以使用forEach的实例方法了
JavaScript的forEach
- foreach是EcmaScript5中提供的
- 不兼容IE8,及其以下的IE版本
forEach是EcmaScript5中的数组遍历函数,是JavaScript原生支持的遍历语法,可以被遍历任何被遍历的成员
***注意:***jQuery的each方法几乎和ForEach一致
低版本浏览器不支持
3.4.3代码无分号风格
function say() {
console.log('hello world')
}
say()
// ;(function (){//匿名函数自执行
// console.log('hello')
// })()
// ;['苹果','香蕉'].forEach(function (item)){
// console.log(item)
// }
// `反引号是EcmaScript中新增的一种字符串的包裹方式,交:模块字符串
// 它支持换行和非常拼接变量
// 反引号里面写什么格式就是什么格式 类似于html里面的pre标签
// var foo=`
// 大家好
// 我是渣渣辉
// `
// console.log(foo)
;`hello`.toString()
// 当使用无分号风格,注意以下情况:
// 当一行代码是以:
// (
// [
// `
// 开头的时候 ,在前面补一个分号 ,避免语法解析错误
// 在第三方的代码中以分号开头
推荐书籍:<<编写可维护的 JavaScript>>
《JavaScript 高级编程》第3版
《JavaScript 语言精粹》
3.4.4服务端与客户端渲染
区别:
电商网站,商品评论列表大多数是客户端渲染,为了用户体验,采用客户端渲染,爬虫抓不到,不利于SEO搜索引擎优化。
商品列表采用服务端渲染,目的是为了SEO搜索引擎优化
客户端最少两次请求,发起ajax在客户端使用模板引擎渲染
客户端拿到的就是服务端已经渲染好的
3.4.5初步实现Apache功能
//1.加载http核心模块
var http=require('http')
var fs=require('fs')
//2.创建Server
var server=http.createServer()
//3.监听Server的request请求事件,设置请求处理函数
var wwwDir='E:\www'//动态转移www目录,其下面的文件,有与之对应的相对路径
//即:www文件夹路径改变,只需要改变wwwDir的值即可
server.on('request',function(req,res){
var url=req.url
// 请求/,都index.html
// 请求/a.txt,都a.txt
// 请求哪个,读哪个 即:wwwDir+请求的url
var filePath='/index.html'
if(url!=='/'){
filePath=url//请求的url
}
fs.readFile(wwwDir+filePath,function(err,data){
if(err){
return res.end('404 Not Found')
}
res.end(data)
})
})
//4.绑定端口号,启动服务
server.listen(3000,function(){
console.log('running......')
})
Apache文件目录列表功能
var http = require('http')
var fs = require('fs')
var server = http.createServer()
var wwwDir = 'E:/www'
server.on('request', function (req, res) {
var url = req.url
fs.readFile('./template.html', function (err, data) {
if (err) {
return res.end('404 Not Found.')
}
// 1. 如何得到 wwwDir 目录列表中的文件名和目录名
// fs.readdir
// 2. 如何将得到的文件名和目录名替换到 template.html 中
// 2.1 在 template.html 中需要替换的位置预留一个特殊的标记(就像以前使用模板引擎的标记一样)
// 2.2 根据 files 生成需要的 HTML 内容
// 只要你做了这两件事儿,那这个问题就解决了
fs.readdir(wwwDir, function (err, files) {
if (err) {
return res.end('Can not find www dir.')
}
// 2.1 生成需要替换的内容
var content = ''
files.forEach(function (item) {
// 在 EcmaScript 6 的 ` 字符串中,可以使用 ${} 来引用变量
content += `
<tr>
<td data-value="apple/"><a class="icon dir" href="/D:/Movie/www/apple/">${item}/</a></td>
<td class="detailsColumn" data-value="0"></td>
<td class="detailsColumn" data-value="1509589967">2017/11/2 上午10:32:47</td>
</tr>
`
})
// 2.3 替换
data = data.toString()
data = data.replace('^_^', content)
// 3. 发送解析替换过后的响应数据
res.end(data)
})
})
})
server.listen(3000, function () {
console.log('running...')
})
3.4.6Node中的Console控制台
REPL
- read
- eval
- loop
作用:辅助性API测试
进入:cmd 输入node
退出:按两次ctrl+c
3.4.7设计请求路径
在PHP中,使用$GET直接查询字符串数据
在Node中,通过url.parse()解析
如果没有url.parse(),这个模块,自己解析,例如:
- /pinglun?name=jack&message=hello
- split(‘?’)按照不同的方式来分割字符串
- name=jack&message=hello
- split(‘&’)
- name=jack message=hello
- forEach()
- name=jack.split(‘=’)
- 0 key
- 1 value
3.4.8在Node中服务器重定向
- header(‘location’)
- 301 永久重定向 浏览器记住
- 访问a.com,跳到b.com
- 下次,浏览器不请求a,直接跳到b.com(缓存)
- 302 临时重定向 浏览器不记忆
- 访问a.com,跳到b.com
- 下次请求a.com,先请求服务器,先到服务器
- 还会请求a
- a告诉你往b
- 301 永久重定向 浏览器记住
3.5Node中的模块系统
使用Node编写应用程序,主要是在使用
- EcmaScript语言
- 和浏览器不一样,在Node中没有BOM、DOM,即:没有window,没有document
- 核心模块(内置)
- 文件操作的fs
- http服务的http
- url路径处理模块
- path路径处理模块
- os操作系统信息
- 第三方模块(npm下载使用)
- art-tempalte
- 自己写的模块(加载使用)
- 自己创建的文件
3.5.1 什么是模块化
- 具有文件作用域
- 具有通信规则
- 加载 require
- 导出
3.5.2CommomJS模块规范
在Node中的JavaScript有一个很重要的概念:模块系统
- 模块作用域
- 使用require方法加载模块
- 使用exports接口对象来导出模块中的成员
3.5.2.1加载require
语法:
var 自定义变量名称=require('模块')
两个作用:
- 执行被加载模块中的代码
- 得到被加载模块中
exports
导出的接口对象
- 得到被加载模块中
3.5.2.2导出exports
-
Node中是模块作用域,默认文件中所有成员只在当前文件模块有效
-
对于希望被其他模块访问的成员,就需要把这些公开的成员都挂载到
exports
接口对象中就可以了导出多个成员(必须在对象中)
exports.a=123//挨个导出 exports.b='hello' exports.c=function(){ console.log('ccc') } exports.d={ foo:'bar' }
导出单个成员(拿到的就是,函数、字符串)
module.exports='hello'
以下情况会覆盖:
module.exports='hello'
module.exports=function(){
return x+y
}
//后面覆盖前面,最终导出function,以后者伪准
也可以通过这样来导出多个成员:
module.exports={//一次性导出
add:function(){
return x-y
},
str:'hello'
}
3.5.2.3原理解析
exports是modele exports
的一个引用
console.log(exports===module.exports)//结果为:true
exports.foo='bar'
//等价于
module.exports.foo='bar'
3.5.2.4exports和module.exports的区别
- 每个模块中都有一个module对象
- module对象中有一个exports对象
- 可以把需要导出的成员挂载到module.exports接口对象中
- 即:
modelue.exports.xxx=xxx
的方式,过于麻烦 - 为了方便,Node在每一个模块中提供了一个成员叫:
exports
exports===module.exports
结果为true
- 所以对于:
module.exports.xxx=xxx
的方式完全可以:exports.xxx
- 当一个模块需要导出单个成员的时候(非对象,例如:当导出字符串、数组、函数的时候),这个时候必须使
module.exports.xxx
的方式 - 不要使用
exports=xxx
不管用,但是exports.xxx=xxx可以正常使用 - 因为每个模块最终向外·
return
的是module.exports
- 而
exports
只是module.exports
的一个引用 - 即便,你为
exports=xx
重新赋值,不会影响module.exports
- 但是有一种赋值方式比较特殊:
exports=module.exports
,用来重新建立引用关系
module.exports={ //已经指向新的对象
a:123
}
exports=module.exports//重新建立exports和module.exports引用关系,即可生效
exports.foo='bar'//还是原来的对象
3.5.2.5require方法加载规则
注意:更多底层细节 参考:朴灵《深入浅出Node.js》中的模块系统章节
深入浅出 Node.js(三):深入 Node.js 的模块机制
https://www.infoq.cn/article/nodejs-module-mechanism/
https://www.bilibili.com/video/av27670326/?p=44
-
核心模块
- 模块名
-
第三方模块
- 模块名
-
用户自己写的
- 路径
-
优先从缓存加载
a.js
console.log('a.js被加载')
var fn=require('./b')
console.log(fn)//输出b.js里面的function
b.js
console.log('b.js被加载')
module.exports=function(){
console.log('hello b')
}
main.js
require('./a')
var fn=require('./b')//这句话不会重复加载b,只是为了得到里面的接口对象
console.log(fn)//由于已经在a中加载过b了,
//可以将缓存中b.js的 接口对象function拿过来用,但是不会重复执行里面的代码,,避免重复加载,提高模块加载效率
最终输出:a.js被加载
b.js被加载
[Function]
[Function]
blog
a
node-module
art-template
foo.js
b
//一旦b中找不到,就会返回上一级目录查找,就不会去同级的兄弟目录a去找
../a.foo.js //但是可以通过路径加载兄弟目录的的文件
a中的第三方包不能通过require加载
除非require('../a/node_modules/art-template/index.js')
- 判断模块标识
- 核心模块
- 第三方模块
- 自己写的模块
//如果是非路径形式的模块标识,即第三方模块和用户自定义模块
// 路径形式的模块
// ./ 当前目录
// ../ 上一级目录
// /xxx 首位的/ 表示当前文件模块所属磁盘跟路径
// F:\Node.js\node.js\03\code\04-require标识符分析 这样的绝对路径也不用
// /xxx的路径方式几乎不用
// .js后缀名可以省略
// require('./foo.js')
//核心模块的本质也是文件
//核心模块的文件已经被内置编译到二进制文件中了,只需要根据名字加载即可
//require('fs')
//require('http')
//第三方模块
//凡是第三方模块都必须通过npm下载
//使用的时候 通过require('包名')的方式进行加载使用
//不可能有第一个第三方包和核心模块的名字一样
//既不是核心模块,也不是路径形式的模块,即:第三方模块
// 先找到当前文件所处目录中的node_modules目录
// node_modules/art-template
// node_modules/art-template/package.json文件
// node_modules/art-template/package.json文件中的main属性
// main属性就记录了template的入口模块
// 然后加载使用第三方包
// 实际上加载的还是文件
//如果package.json文件不存在或者main指定的入口模块也是没有的
//Cannot find module 'a'
///则node会自动找该目录下的index.js
//也就是说当main.js没有或指定是错的、或者没有package.json的时候,index.js会作为一个默认备选项,如果备选项index.js都没有,报错:// Cannot find module 'xxx'
//如果以上任何一个条件都不成立(即:当前同级目录下,没有node_modules文件夹),则会进入上一级目录中的node_modules目录查找,规则同上面一样
//如果上一级还没有,继续向上上级查找
//如果直到磁盘根目录还找不到,最后报错:
// Cannot find module 'xxx'
// var template=require('art-template')
/*注意:在一个项目中,有且仅有一个node_modules,放在项目根目录
这样子目录的代码,都可以加载第三方包
模块查找机制
优先从缓存加载
路径形式的文件模块(用户自己写的)
第三方模块
node_modules/art-template
node_modules/art-template/package.json
node_modules/art-template/package.json里面的main
如果这两个不成立,找index.js备选项。
如果index.js备选项也不成立,进入上一级目录找node_modules
如果上一级目录,没有node_modules,再去上一级,按这个规则,依次往上,直到磁盘根目录,
还找不到,报错: Cannot find module 'xxx'
在一个项目中,有且仅有一个node_modules,放在项目根目录
*/
require('a')//这句 就是当做第三方模块
/*
在node_module里面的这些包,是通过包名加载的,而不是文件目录
*/
//当前文件所处目录中的node_modules目录
// a目录
// a目录/package.json
// a目录/package.json main属性
// 真正加载的是main属性里面的foo.js
3.5.3 npm
-
node package manager
–save 会保存dependencies依赖项
3.5.3.1 npm网站
nmpjs.com
3.5.3.2 npm 命令行工具
查看npm 版本
npm --version
升级npm(自己升级自己)
npm install --global npm
3.5.3.3 常用命令
- npm init 生成package.json文件
- npm init -y 跳过向导,快速生成
- npm install
- 一次性把dependencies选项中的依赖项全部安装
- npm i
- npm install 包名
- 只下载
- npm i 包名
- npm install --save 包名
- 下载并且保存依赖项到package.json文件的dependencies
- npm i -S 包名
- npm uninstall 包名
- 只删除,依赖项依然存在
- npm un 包名
- npm uninstall
- 删除的同时,去除依赖信息
- npm un -S 包名
- npm help
- 查看使用帮助
- npm 命令 --help
- 查看指定命令的使用帮助
- 例如忘记uninstall命令简写,使用
npm uninstall --help
,查看使用帮助
3.5.3.4 npm被墙问题
npm存储包的服务器在国外,有时候速度很慢
淘宝npm镜像:https://npm.taobao.org/
安装淘宝的cnpm
#在任意目录执行
# --global 表示安装到全局
# --gloabl不能省略
npm install --global cnpm
#走国外的服务器
npm install jquery
#使用cnpm通过淘宝的服务器下载
cnpm install jquery
如果不想安装cnpm
,又想使用淘宝服务器下载
npm install jquery --registry=https://registry.npm.taobao.org
每次收到加http参数很麻烦,把这个选项加到配置文件中
npm config set registry=https://registry.npm.taobao.org
查看npm配置信息
npm config list
经过上面的命令,以后所有的npm install
都会通过淘宝的服务器下载
3.5.4 package.json
每个项目都与要有package.json
文件(包描述文件)
可以通过npm init
创建package.json初始化
PS F:\Node.js\node.js\03\code\npm-demo> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (npm-demo)
version: (1.0.0) 0.0.1
description: 这是一个npm测试
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: honor
license: (ISC)
About to write to F:\Node.js\node.js\03\code\npm-demo\package.json:
{
"name": "npm-demo",
"version": "0.0.1",
"description": "这是一个npm测试",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "honor",
"license": "ISC"
}
Is this OK? (yes) yes
PS F:\Node.js\node.js\03\code\npm-demo> npm install --save jquery
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN npm-demo@0.0.1 No repository field.
+ jquery@3.4.1
added 1 package from 1 contributor in 8.139s
PS F:\Node.js\node.js\03\code\npm-demo>
dependencies选项,可以帮我们保存
第三方包的依赖信息
如果node_modules
删除了,只需要:npm install
就会自动把package.json
里面的dependencies
依赖项重新下下来
- 每个项目都有一个
package.json文件
- 执行
npm install
包名的时候,最好加上–save,用来保存依赖项信息
3.5.4.1 package.json和package-lock.json
npm 5以前不会有package-lock.json
这个文件的
npm 5以后加入了这个文件
当安装包的时候,npm都会生成或更新package-lock.json
这个文件的
- npm 5以后的版本不需要加
--save
,自动保存依赖信息 - 当安装包的时候,会自动创建或者更新
package-lock.json
文件 - package-lock.json保存
node_modules
所有包的下载地址,包名,版本号- 这样的话,当重新
npm install
的时候会加快下载速度
- 这样的话,当重新
lock
称之为锁- 这个
lock
用来锁定包的版本(即使在package.json
中手动修改包的版本号,一旦npm install
会根据package-lock.json
中锁定的版本号安装包,而不是自动更新到最新版本,同时指定安装到x.x.x版本是无效的,只根据上次安装后,package-lock.json
中锁定的包的版本进行安装) - 锁定版本号,避免包自动升级到新版,出现问题
- 这个
3.6 Express
原生的http在某些方面不足以应对开发需求,框架的目的就是提高效率,代码高度统一
在Node中,有很多web框架,以express
为主
- http://expressjs.com/
3.6.1 安装
npm install --save express
3.6.2 hello world
const express=require('express')
const app=express()
app.get('/',(req,res)=>res.send('Hello World!'))
app.listen(3000,()=>console.log('Example app listening on port 3000'))
3.6.3基本路由
路由器
- 请求方法
- 请求路径
- 请求处理函数
get:
//当以GET方法请求 / 的时候,执行对应的请求处理函数
app.get('/',function(req,res){
res.send('get')
})
post:
//当以POST方法请求 /login 的时候,执行对应的请求处理函数
app.post('/login',function(req,res){
res.send('post')
})
3.6.4 静态服务
//当以 /public开头的时候,去 ./public目录中查找对应的资源
//这种方式,便于辨识
app.use('/public',express.static('./public/'))
//访问输入:120.0.0.1:3000/public/login.html
//必须是 /a/public木中的资源具体路径
//即:/a/ 是 /public/的别名,/a/ 表示/public/
//访问即输入:http://127.0.0.1:3000/a/login.html
// app.use('/a/',express.static('./public/'))
//当省略第一个参数的时候,可以通过省略/public的方式访问,即:直接:120.0.0.1:3000/login.html
// app.use(express.static('./public/'))
//1.安装
//2.引入第三方包
var express =require('express')
//3.创建服务器应用程序
// 也就是原来的http.createServer
var app =express()
//在Express开放资源,就一个API的事
//公开指定目录
//这样就可以直接通过 /public/xx 的方式,访问目录中所有的资源了
app.use('/public/',express.static('./public/'))
app.use('/static/',express.static('./static/'))
app.use('/node_modules/',express.static('./node_modules/'))
//模板引擎,在Express也是一个API的事
//当服务器收到get请求 / 的时候,执行回调处理函数
app.get('/',function(req,res){
res.send('hello express!')
})
app.get('/about',function(req,res){
//在Express中,可以使用req.query来获取查询字符串
console.log(req.query)
res.send('about')
})
app.get('/pinglun',function(req,res){
//req.query
//在Express中使用模板引擎,有更好的方式,res.render('文件名',{模板对象})
//art-template结合Express使用
})
//相当于server.listen
app.listen(3000,function(){
console.log('app is running at port 3000')
})
3.6.5 中间件
官网文档:http://www.expressjs.com.cn/guide/using-middleware.html
http://www.expressjs.com.cn/resources/middleware.html
中间件的本质就是一个请求处理方法,把用户从请求到响应的整个过程分发到多个中间件中去处理,这样做的目的是提高代码的灵活性,动态可扩展
- 同一个请求,所经过的中间件都是同一个请求对象和响应对象
3.6.5.1 应用程序级别的中间件
万能匹配(不关心任何请求和路径方法)
app.use(function (req,res,next) {// 万能匹配
console.log('Time',Date.now())
next()
})
只关心以/xxx/
开头的:
app.use('/a',function (req,res,next) {// 万能匹配
console.log('Time',Date.now())
next()
})
3.6.5.2 路由级别中间件
get:
app.get('/',function (req,res){
res.send('get!')
})
post:
app.post('/',function (req,res){
res.send('post!')
})
put:
app.put('/user',function (req,res){
res.send('put!')
})
delete:
app.delete('/user',function (req,res){
res.send('delete!')
})
3.6.5.3 错误处理中间件
app.use(function (err,req,res,next){
console.error(err,stack)
res.status(500).send('Something broke!')
})
3.6.5.4 内置中间件
-
express.static serves static assets such as HTML files, images, and so on.
开放静态资源
-
express.json parses incoming requests with JSON payloads. NOTE: Available with Express 4.16.0+
使用JSON有效负载解析传入的请求
-
express.urlencoded parses incoming requests with URL-encoded payloads. NOTE: Available with Express 4.16.0+
使用URL编码的有效负载解析传入的请求
3.6.5.5 第三方中间件
官放文档:http://www.expressjs.com.cn/guide/using-middleware.html
-
body-parser
-
compression
-
cookie-parser
-
morgan
-
response-time
-
serve-static
-
session
3.7 在Express中配置使用art-template模板引擎
安装:
npm install --save art-template
npm install --save express-art-template
配置:
app.engine('art', require('express-art-template'));
//第一个参数表示,当渲染以.art结尾的文件的时候,使用art-template模板引擎
//可以将art改成html:
app.engine('html', require('express-art-template'));
//这样就不必将views目录下的文件,改成以.art结尾
使用:
app.get('/',function(req,res){
// express默认会去项目的views目录下找index.html
res.render('404.html',{
title: 'hello world'
})
})
如果要修改默认的views
视图渲染目录,可以
//注意第一个参数views不能写错
app.set('views',目录路径)
//修改后,再次render的时候,就基于这个新的目录路径来找
3.7.1 art-template模板引擎高级语法
-
include:导入header,footer等,形成模板的header,footer
-
{{include 'xxx/xxx/header.html'}} {{include 'xxx/xxx/footer.html'}}
-
-
block:在模板中留坑,方便在其他页面,这个坑里,写不同的内容
-
<!--给标题title留坑--> {{block 'title'}}{{/block}} <!--给自己的body留坑--> {{block 'body'}}{{/blok}}
-
-
extend:在其他页面,使用模板页
// 在其他页面,使用模板页 {{extend 'xxx/xxx/模板页.html'}} // 填坑,在这填,这个页面想要的title部分 {{block 'title'}}{{'标题'}}{{/block}} //填坑,在这,填这个页面的body主体 {{block 'body'}}{{'标题'}}{{/block}}
3.8 在Express中获取表单GET请求参数
Express内置了一个API,可以通过req.query
来获取
req.query
3.9 在Express中获取表单POST请求体数据
在Express中没有内置获取表单post请求的API,需要使用第三方包body-parser
官网:http://www.expressjs.com.cn/en/resources/middleware/body-parser.html
使用说明:
var express = require('express')
// 0.引包
var bodyParser = require('body-parser')
var app = express()
// 配置body-parser
//加入这个配置,在req请求对象上会多出来一个属性:body
//即:可以直接通过req.body来获取表单post请求体数据
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
//通过req.body来获取表单post请求体数据
res.end(JSON.stringify(req.body, null, 2))
})
安装:
npm install --save body-parser
配置body-parser中间件(插件,专门解析表单post请求体),一定要在挂载路由之前配置:
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
引包:
var bodyParser = require('body-parser')
示例:
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
//通过req.body来获取表单post请求体数据
res.end(JSON.stringify(req.body, null, 2))
})
提示:默认Session数据内存存储的,服务器一旦重启,就会丢失,真正的生产环境,会把session进行持久化存储。
3.10在Express配置使用express-session插件
参考文档:https://www.npmjs.com/package/express-session
安装:
npm install express-session
引包:
var session = require('express-session')
配置(一定要在挂载路由之前):
// 该插件会为req请求对象添加一个成员:req.session默认是一个对象
app.use(session({
// 配置加密字符串,在原有基础上拼接起来,一起加密
// 增强安全性,防止客户端恶意伪造
secret: 'keyboard cat',
resave: false,
// 无论是否使用session,都默认分配一把钥匙
saveUninitialized: true
}))
使用:
// 添加session数据
req.session.foo = 'bar'
// 获取session数据
req.session.foo
3.11文件操作路径和模块路径
文件操作路径:
//在文件操作的相对路径中
// ./data/a.txt 相对于当前目录
// data/a.txt 相对于当前目录
// /data/a.txt 绝对立即,当前文件模块所处的磁盘根目录
// F:xx/xx/... 绝对路径
// fs.readFile('./data/a.txt',function(err,data){
// if(err){
// console.log(err)
// return console.log('读取失败')
// }
// console.log(data.toString())
// })
模块操作路径:
//这里忽略了 . ,也是磁盘根目录,会报错
require('/data/foo.js')//省略.,则会去找data文件夹的磁盘根目录即:F:data\foo.js
//相对路径
require('./data/foo.js')
//模块加载的路径中的相对路径不能省略.
3.12 path路径操作模块
参考文档:http://nodejs.cn/api/path.html
- path.basename(path[, ext])
- 获取一个给定路径中的文件名部分
- path是路径
- ext是可选参数,用来指定要去除的后缀名
path.extname('index.html');
// 返回: '.html'
// 从最后一次出现 .(句点)字符到 path 最后一部分的字符串结束
//如果在 path 的最后一部分中没有 . ,或者如果 path 的基本名称,除了第一个字符以外没有 .,则返回空字符串。
path.extname('.index');
// 返回: ''
- path.dirname(path)
- 获取一个路径中的目录部分
path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'
- path.extname
- 获取一个路径中的扩展名部分
- path.parse
- 把一个路径,转为对象
- 对象中包含root根路径
- dir目录
- base包含后缀名的文件名
- ext后缀名
- name不包含后缀名的文件名
- 把一个路径,转为对象
- path.join
- 当出现路径拼接的时候用这个方法
- path.isAbsolute判断一个路径是否是绝对路径
3.13 Node中的其他成员
在每个模块中,除了require
、exports
等模块相关API之后,还有两个特殊成员:
_dirname
用来动态获取当前文件模块所属目录的绝对路径_filename
用来动态获取当前文件的绝对路径_dirname
和_filename
是不受执行ndoe命令所属路径影响的
在文件操作中,使用相对路径是不可靠的,因为在Node中,文件操作的路径被设计为,相对于执行node命令所处的路径,受执行node命令所处的路径影响。(折样设计,是有使用场景的)
所以,为了解决这个问题,只需要把相对路径变为绝对路径
可以使用_dirname
和_filename
动态获取绝对路径
在拼接路径的过程中,推荐多使用path.json(_dirname,'文件名')
来辅助拼接
**注意 :**模块中的路径标识和这里的路径没关系,不受影响(就是相对于文件模块)
3.14 nodemon工具重启服务
第三方命令行工具,nodemon
帮助我们解决频繁修改代码,重启服务器问题
nodemon
是一个基于Node.js开发的一个第三方命令行工具,独立安装
#在任意目录执行都可以
# --global安装的包,可以在任目录执行
npm install --global nodemon
安装完毕,使用:
node app.js
#现在使用 nodemon
nodemon app.js
只要是通过nodemon
启动的服务,它会监视文件的变化,自动帮你重启服务
4.CRUD 案例
4.1 模块化思想
模块如何划分:
- 模块职责要单
- Vue
- angular
- React
4.2 起步
-
初始化
npm init -y
-
安装依赖
-
模板处理
4.3 路由设计
请求方法 | 请求路径 | get参数 | post参数 | 备注 |
---|---|---|---|---|
GET | /students | 渲染首页 | ||
GET | /students/new | 渲染添加学生页面 | ||
POST | /students/new | name、age、gender、hobbies | 处理添加学生请求 | |
GET | /students/edit | id | 渲染编辑页面 | |
POST | /students/edit | id、name、age、gender、hobies | 处理编辑页面 | |
GET | /students/delete | id | 处理删除请求 |
4.4 提取路由模块
router.js
/*
router.js路由模块
职责:
处理路由
根据不同的方法+请求路径,设置具体的请求函数
模块职责要单一
*/
var fs=require('fs')
// Express提供了一种更好的方式
//专门用来包装路由
var express=require('express')
// 1.创建一个路由容器
var router=express.Router()
// 2.把路由都挂载到router路由容器中
router.get('/students',function(req,res){
// readFile的第二个参数是可选的,传入utf8就是告诉它,把读取的文件直接按照uf8编码转成我们认识的字符
// 除了这样来转换,也可以通过data.toString()的方式
fs.readFile('./db.json','utf8',function(err,data){
if(err){
return res.status(500).send('Server error')
}
// console.log(typeof data)// String类型
// console.log(data)
//从文件读取到的是字符串
//这里手动转成对象
var students=JSON.parse(data).students
res.render('index.html',{
fruits:[
'苹果',
'香蕉',
'橘子'
],
// students:JSON.parse(data).students
students:students
})
})
})
router.get('/students/new',function(req,res){
res.render('new.html')
})
router.post('/students/new',function(req,res){
// 1.获取表单数据
// 2.处理
// 由于文件中内容是字符串,只能 先读出来->转成对象->往对象中push追加添加的数据->转成字符串->存进去
// 将数据保存到db.json中,用以持久化
// 3.发送响应
console.log(req.body)
})
router.get('/students/edit',function(req,res){
res.send('get students edit')
})
router.post('/students/new',function(req,res){
res.send('post students edit')
})
router.get('/students/delete',function(req,res){
res.send('get students delete')
})
// 3.把router导出(用于在下面app.js中的挂载)
module.exports=router
app.js(入口)
var router=require('./router')
//挂载路由
app.use(router)
4.5 设计操作数据的API文件模块
/*
student.js
数操作文件模块
职责:操作文件中的数据,只处理数据,不关心业务(怎么获取数据,发送响应)
*/
/*
获取所有学生列表
return []
*/
exports.find=function(){
}
/*
添加保存学生
*/
exports.save=function(){
}
/*
更新学生
*/
exports.update=function(){
}
/*
删除学生
*/
exports.delete=function(){
}
4.6 编写步骤
- 处理bootstrap模板(样式资源)
- 配置开放静态资源
- 配置模板引擎
- 简单路由:/students 渲染静态页出来
- 路由设计
- 提取路由模块
- 一系列业务操作需要处理文件数据,封装students.js
- 写好students.js方法
- 查询所有学生列表的API find
- findById
- save
- updateById
- deleteById
- 实现具体功能
- 通过路由收到请求
- 接收请求中的数据(get,post)
- req.query(get)
- req.body(post 需要安装配置body-parser第三方包)
- 调用数据操作API处理数据
- 根据操作结果给客户端发送响应
- 业务功能顺序
- 列表
- 添加
- 编辑
- 删除
- ES6新特性
- find
- findindex5
5.异步编程
5.1 回调函数
不成立的情况:
function add(x,y){
console.log(1)
setTimeout(function () {
console.log(2)//1秒之后打印2
var result =x+y
return result
},1000)
console.log(3)//执行结束,不会等待前面的定时器,result直接返回默认值undefined
}
console.log(add(10,20)) //->undefined
// 输出顺序:1 3 undefined 2
不成立的情况:
function add(x,y){
var result
console.log(1)
setTimeout(function () {
console.log(2)//1秒之后打印2
result =x+y
},1000)
console.log(3)//执行结束,不会等待前面的定时器,result直接返回默认值undefined
return result
}
console.log(add(10,20))
// 输出顺序:1 3 undefined 2
回调函数:
function add(x,y,callback){
// callback就是回调函数
// var x = 10
// var y = 20
// var calllback = function (a) { consolelog(result) }
console.log(1)
setTimeout(function () {// 异步操作
var result =x + y
callback(result)// 实参
},1000)
}
// 回调函数
add(10,20,function (a) {// a是形参
// 现在拿到这个结果,可以做任何操作
// a才是我们得到的结果
console.log(result)
})
//注意:凡是需要得到一个函数内部异步操作的结果
// setTimeout
// readFile
// writeFile
// ajax
// 这种情况,必须通过:回调函数
基于原生 XMLHTTPRequest封装get方法:
注意:chrome浏览器实现全浏览器跨域ajax请求
在较高版本的chrome浏览器,有同源访问策略控制,运行代码会报错:
Access to XMLHttpRequest at ‘file:///F:/Node.js/node.js/05/code/data.json’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
解决办法:
在chrome快捷方式上打开属性栏,在‘目标’栏加上后缀--disable-web-security --user-data-dir。即可实现在此浏览器上所有网页的跨域请求
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>封装ajax方法</title>
</head>
<body>
<script>
// 这些是异步的:
// setTimeout
// readFile
// writeFile
// readdir
// ajax 往往异步API 都伴随着有一个回调函数
// var result=fn()
// $.get('abc',function () {})
// 在较新版本的chrome浏览器,跨域ajax请求报错
/*
Access to XMLHttpRequest at 'file:///F:/Node.js/node.js/05/code/data.json'
from origin 'null' has been blocked by CORS policy:
Cross origin requests are only supported for protocol schemes:
http, data, chrome, chrome-extension, https.
*/
// 解决办法:
// 在chrome快捷方式上打开属性,在‘目标’栏加上后缀--disable-web-security --user-data-dir。
// 即可实现在此浏览器上所有网页的跨域请求
function get(url,callback){
var oReq = new XMLHttpRequest();
//oReq.addEventListener("load", reqListener);
// 当请求加载成功之后,要调用指定的函数
oReq.onload=function () {
// 现在需要得到这里的 oReq.responseText
// console.log(oReq.responseText)
callback(oReq.responseText)
}
oReq.open("GET", url);
oReq.send();
}
get('data.json',function (data) {
console.log(data)
})
</script>
</body>
</html>
5.2 Promise
参考文档:http://es6.ruanyifeng.com/
callback hell 回调地狱
无法保证读取文件的顺序的代码:
// 异步编程,读取文件的顺序,取决于操作系统调度机制
var fs = require('fs')
fs.readFile('./data/a.txt','utf8',function(err,data){
if(err) {
//return console.log('读取失败')
throw err
// 抛出异常
// 1.阻止程序执行
// 2.把错误消息打印到控制台
}
console.log(data)
})
fs.readFile('./data/b.txt','utf8',function(err,data){
if(err) {
//return console.log('读取失败')
throw err
// 抛出异常
// 1.阻止程序执行
// 2.把错误消息打印到控制台
}
console.log(data)
})
fs.readFile('./data/c.txt','utf8',function(err,data){
if(err) {
//return console.log('读取失败')
throw err
// 抛出异常
// 1.阻止程序执行
// 2.把错误消息打印到控制台
}
console.log(data)
})
通过回调嵌套的方式保证顺序:
// 异步编程,读取文件的顺序,取决于操作系统调度机制
var fs = require('fs')
fs.readFile('./data/a.txt','utf8',function(err,data){
if(err) {
//return console.log('读取失败')
throw err
// 抛出异常
// 1.阻止程序执行
// 2.把错误消息打印到控制台
}
console.log(data)
fs.readFile('./data/b.txt','utf8',function(err,data){
if(err) {
//return console.log('读取失败')
throw err
// 抛出异常
// 1.阻止程序执行
// 2.把错误消息打印到控制台
}
console.log(data)
fs.readFile('./data/c.txt','utf8',function(err,data){
if(err) {
//return console.log('读取失败')
throw err
// 抛出异常
// 1.阻止程序执行
// 2.把错误消息打印到控制台
}
console.log(data)
})
})
})
为了解决以上方式带来的问题(回调地狱嵌套),所以在EcmaScript 6中新增了一个API,Promise
Promise 基本语法
var fs = require('fs')
// 在ES6中新增了一个API Promise
// Promise是一个构造函数
// console.log(1)
// 创建Promise容器
// 当Promise一旦创建,就开始执行里面的代码
// Promise只是一个容器,本身不是异步,里面的任务不是异步
var p1 = new Promise(function (resolve,reject) {
// console.log(2)
fs.readFile('./data/a.txt','utf8',function (err,data) {// 异步任务
if (err) {
// 失败了,承诺容器中的任务失败
// 把容器的Pending状态变为Rejected
// console.log(err)
// 调用reject就相当于调用then方法的第二个参数函数
reject(err)
} else {
// console.log(3)
// 承诺容器中的任务成功了
// console.log(data)
// 把容器的Pending状态改为成功Resolved
// 这里调用的resolve方法就是then方法传递的那个function
resolve(data)
}
})
})
// console.log(4)
// 输出结果 1 2 4 3 hello aaa
// p1就是那个承诺
// 当p1成功了,然后(then)做指定的操作
// then方法接收的function就是容器中的resolve函数,resolve传什么,下面就得到什么
p1
.then(function (data) {
console.log(data)
},function (err) {
console.log('读取文件失败',err)
})
封装 Promise API(Promise版本的readFile
)
var fs = require('fs')
// 封装
function pReadFile (filePath) {
return new Promise(function (resolve,reject) {
fs.readFile(filePath,'utf8',function (err,data) {// 异步任务
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then(function (data) {
console.log(data)
return pReadFile('./data/a.txt')
})
.then(function (data) {
console.log(data)
return pReadFile('./data/b.txt')
})
.then(function (data) {
console.log(data)
return pReadFile('./data/c.txt')
})
5.3 json-server
JSON-Server 是一个 Node 模块,运行 Express 服务器,可以指定一个 json 文件作为 api 的数据源。
安装
npm install -g json-server
查看是否安=安装成功,显示版本号
json-server --version
启动
json-server
可以直接把一个json
文件托管成一个具备全RESTful
风格的API
,并支持跨域、jsonp
、路由订制、数据快照保存等功能的 web 服务器。
在code目录打开命令行工具,输入:
json-server --watch data.json
5.4 http-server
http-server是基于node.js的一个简单、零配置的命令行web服务器,可以方便实现跨域资源请求。
安装:
npm install http-server -g
使用:
在code目录打开命令行工具,输入:
http-server
简写:hs
6. MongoDB
资料:https://www.runoob.com/mongodb/mongodb-tutorial.html
官方文档: https://docs.mongodb.com/manual/
6.1 MongoDB数据库的基本概念
- 可以有多个数据库
- 一个数据库中可以有多个集合(表)
- 一个集合中可以有多个文档(表记录)
- 文档结构很灵活,没有任何限制
- MongoDB非常灵活,不需要像MySQL一样,先创建数据库、表、设计表结构
- 在里只需要,当你需要插入数据的时候,只需要指定往哪个数据库的哪个集合插入哪条数据
- MongoDB自动完成建库建表
{
qq:{
users:[//集合
//每一条记录都是对象
{name:"张三",age:18},
{name:"李四",age:19},
{name:"王五",age:20},
{name:"赵六",age:21},
{name:"阮七",age:22}
],
products:[
],
...
},
taobao:{
},
baiud:{
}
}
6.1 安装与使用
下载
https://www.mongodb.com/download-center/community
安装:
https://jingyan.baidu.com/article/09ea3ede5aff37c0aede3919.html
https://www.jb51.net/article/145489.htm
配置环境变量:
将MongDB目录下的bin目录,配置为用户变量,在用户变量的Path中,将bin目录的路径,复制到Path中,新建环境变量
检验是否安装成功
mongod --version
6.2 关系型数据库和非关系型数据库
表就是关系
或者说,表和表之间存在关系
- 所有的关系型数据库都需要通过
sql
语言来操作 - 所有的关系型数据库在操作之前,都需要设计表结构
- 而且数据表还支持约束
- 唯一的
- 主键
- 默认值
- 非空
- 非关系型数据库非常灵活
- 有的非关系型数据库就是key-value键值对
- MongoDB是长得最像关系型数据库的非关系型数据库
- 数据库(关系型数据库中)—》数据库(非关系型数据库中)
- 数据表(关系型数据库中)—》集合,简单理解就是数组(非关系型数据库中)
- 表记录(关系型数据库中)—》文档对象(非关系型数据库中)
- MongoDB不需要设计表结构
- 也就是说,可以任意的往里面存数据,没有结构性这么一说
6.3 启动和关闭数据库
先启动服务
net start MongoDB
运行:
# mongodb默认使用执行mongod命令所处盘符根目录下的/data/db作为自己的数据存储目录
# 所以在第一次执行命令之前,手动在磁盘根目录/下,新建一个data文件件/db文件夹
mongod
运行成功如下
如果要修改默认的数据存储目录
mongod --dbpath=数据存储目录路径
停止:
在开启服务的控制台,ctrl+c停止
或者直接关闭命令行窗口
查看服务等是否启动成功
// 在浏览器输入
http://127.0.0.1:27017/
6.4 连接和退出连接数据库
注意:启动服务,窗口最小化,才能连接
连接:
#该命令默认连接本机服务
mongo
退出连接:
#在连接状态下输入 exit退出连接
exit
6.5 基本命令
https://www.cnblogs.com/liyonghui/p/mongodb.html
-
show dbs
-
查看显示所有数据库
-
-
db
- 查看当前操作的数据库(默认连接到test),当show dbs之后,没有显示test,是因为test;里面没有数据,一旦往里面插入数据,test就被自动创建出来了
-
use 数据库名称
- 切换到指定的数据库(如果没有,会新建)
-
show collections
显示当前数据库中的集合(类似关系数据库中的表) -
db.students.find()
对于当前数据库中的students集合进行数据查找(由于没有条件,会列出所有数据)
- 插入数据
向集合插入数据
创建集合
db.createCollection("userinfo")
删除表
db.userinfo.drop
6.5.1 查询
-
查询所有
db.userinfo.find() db.userinfo.find().pretty() //格式化输出
-
按条件查询
db.userinfo.find({username:"lili"})
-
范围查询
- gt大于 gte大于等于
- lt小于 lte 小于等于
- : 等于 ne 不等于
-
查询 (and ) 查询密码(价格 数字)在100到325之间的数据
db.userinfo.find({userpwd:{$gt:100,$lt:325}).pretty()
-
查询(or) 查询用户为leson或者为lili的数据
db.userinfo.find({$or:[{username:"leson"},{username:"lili"}]})
-
查询(and 和or的使用)
db.userinfo.find({userpwd:{$gt:300},$or:[{username:"lili"},{username:"lulu"}]}).pretty()
-
//查询年龄在30到50之间的lii2或者lili3
//变成价格在3000到5000之间的品牌 手机 华为或者小米
db.userinfo.find({userage:{$gte:30,$lt:50},$or:[{username:'lili2'},{username:"lili3"}]}).pretty()
-
模糊查询
// 匹配用户名中含有l这个字符的 db.userinfo.find({username:/l/})
-
条件模糊查询
// 查询用户名里面含有l 并且年龄大于30 db.userinfo.find({username:/l/,age:{$gt:30}})
-
类型匹配
db.userinfo.find({userpwd:{$type:"string"}})
-
分页
// skip(5).limit(5) //跳过五条数据 显示五条数据 db.userinfo.find().skip(1).limit(2)
-
排序
// 排序 sort 1为为升序 -1为降序 // 根据编号升序 db.userinfo.find().sort({"_id":1})
-
统计总数
db.userinfo.count() //所有的总数 db.userinfo.count({username:"leson"})//满足条件的总数
-
查看所有索引
db.userinfo.getIndexes() [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "1905.userinfo" } ]
-
删除所有索引(基本不用)
db.col.dropIndexes()
-
删除指定索引
db.col.dropIndex("索引名称")
注意:查询判断result.data.length即可
6.5.2 新增
-
新增一条
db.userinfo.insert({username:"lili",usertel:134,userpwd:123456}) //返回受影响的行数 WriteResult({ "nInserted" : 1 })
-
新增多条
db.userinfo.insertMany([{username:"leson",usertel:134,userpwd:123},{username:"lulu",usertel:135,userpwd:135}])
-
excel批量新增入库
注意:新增判断result.data.length即可
6.5.3 更新
-
更新一条
db.userinfo.update({username:"leson"},{$set:{userpwd:"abc"}})
-
更新多条
db.userinfo.update({username:"leson"},{$set:{userpwd:"bbb"}},false,true) //多条更新
更新接口
用_id更新_
判断更新是否修改成功
注意:更新 用 result.nModified判断是否生效
6.5.4 删除
-
删除一条
// 删除用户名为leson的一条数据 db.userinfo.remove({"username":"leson"},{justOne:true})
-
删除多条
// 删除用户名为leson的所有数据 db.userinfo.remove({"username":"leson"});
-
删除一条数据
db.userinfo.deleteOne({usertel:"123456"})
-
删除多条数据
db.userinfo.deleteMany({username:{$in:["lili","beibei"]}})
-
手动释放 删除后的资源
db.repairDatabase()
-
判断是否删除成功
https://blog.csdn.net/wolf_soul/article/details/50059191?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
6.6 在Node中如何操作MongoDB数据库
6.6.1 使用官方的mongodb
包来操作
https://github.com/mongodb/node-mongodb-native
6.6.2 使用第三方mongoose来操作MongoDB数据库
第三方包:mongoose
基于MongoDB官方的mongodb
包再一次做了封装
网址:https://mongoosejs.com/
安装:
npm i mongoose
mongoose使用:
// 引包
const mongoose = require('mongoose');
// 连接MongoDB数据库
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});
// 创建一个模型
// 就是在设计数据库
// MongDB是动态的,非常灵活,只需要在代码中设计数据库就可以了
// mongoose这个包,可以让设计编写过程变得非常简单
const Cat = mongoose.model('Cat', { name: String });
// 实例化一个Cat(最终是cats)
const kitty = new Cat({ name: 'Zildjian' });
// 持久化保存Kitty实例
kitty.save().then(() => console.log('meow'));
7.mongoose
-
官网:https://mongoosejs.com/
中文:http://www.mongoosejs.net/
-
文档:https://mongoosejs.com/docs/guide.html
中文:http://www.mongoosejs.net/docs/guide.html
-
API文档:https://mongoosejs.com/docs/api.html
7.1 起步
安装:
npm i mongoose
官方示例:
// 引包
const mongoose = require('mongoose');
// 连接MongoDB数据库
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});
// 创建一个模型
// 就是在设计数据库
// MongDB是动态的,非常灵活,只需要在代码中设计数据库就可以了
// mongoose这个包,可以让设计编写过程变得非常简单
const Cat = mongoose.model('Cat', { name: String });
// 实例化一个Cat(最终是cats)
const kitty = new Cat({ name: 'Zildjian' });
// 持久化保存Kitty实例
kitty.save().then(() => console.log('meow'));
// 或者
// kitty.save(function (err){
// if(err){
// console.log(err);
// }else{
// console.log('meow')
// }
// })
7.2 官方指南
7.2.1 设计Scheme发布Model
// 引包
var mongoose=require('mongoose')
var Schema = mongoose.Schema;
// 连接MongoDB数据库
// 指定连接的数据库不需要存在,当你插入第一条数据库之后,自动被创建出来
mongoose.connect('mongodb://localhost:27017/itcast', {useNewUrlParser: true});
// 设计文档结构(表结构)
// 字段名称就是表结构中的属性名称
// 值
// 约束的目的是为了保证数据完整性
var userSchema = new Schema({
username:{
type:String,
required:true//不能为空
},
password:{
type:String,
required:true//不能为空
},
email:{
type:String,
}
});
//3. 将文档结构发布为模型
// mongoose.model方法激素用来将一个架构发布为model
// 第一个参数:传入一个 大写单数名词字符串用来表示数据库名词
// mongoose会自动将 大写单数名词字符串 转为 小写复数 的集合名称
// User 最终转为 users集合名称
// 第二个参数:接收架构Schema
// 返回值:模型构造函数
var User = mongoose.model('User', userSchema);
// 4.有了模型构造函数之后,就可以使用这个构造函数对users中的数据进行增删改查
7.2.2 增加数据
mongoose 新增数据,先new ,再save
var admin = new User({
username:'admin',
password:'123456',
email:'admin@admin.com'
})
// 持久化存储
admin.save(function (err,result){
if(err){
console.log('保存失败')
}else{
console.log('保存成功')
console.log(result)
}
})
7.2.3 查询数据
查询所有
//查询所有数据
User.find(function (err,result){
if(err){
console.log('查询失败')
}else{
console.log(result)
}
})
按条件查询所有
// 按条件查询所有
User.find({ //返回的是一个数组
username:'admin',// 条件,没有条件,默认查询第一条插入的记录
password:'123456'
},
function (err,result){
if(err){
console.log('查询失败')
}else{
console.log(result)
}
})
按条件查询单个
// 按条件查询
User.findOne({// 只找匹配的第一个对象,返回一个对象
username:'admin',// 条件,没有条件,默认查询第一条插入的记录
password:'123456'
},
function (err,result){
if(err){
console.log('查询失败')
}else{
console.log(result)
}
})
7.2.4 删除数据
// 删除数据
User.remove({
username:'Jack',// 条件,没有条件,默认查询第一条插入的记录
},
function (err,result){
if(err){
console.log('删除失败')
}else{
console.log('删除成功')
console.log(result)
}
})
7.2.5 更新数据
根据条件更新所有:
Model.update(conditions,doc, options, callback);
根据指定条件更新一个:
A.findOneAndUpdate(conditions, update, options, callback)
根据Id更新一个
// 更新数据
// 根据Id更新数据
User.findByIdAndUpdate('5d8083c813491d38f8b04127',{
password:'123'
},function (err,result) {
if (err){
console.log('更新失败')
}else{
console.log('更新成功')
}
})
8. 使用Node操作MYSQL数据库
https://www.npmjs.com/package/mysql
安装:
npm install mysql
使用Node操作MYSQL之前,开启mysql服务
- CMD进入mysq目录下的bin目录,输入命令:
net start MySQL
- 或者
win+r
输入services.msc
进入服务,开启mysql服务
操作数据库
// 加载mysql包
var mysql = require('mysql');
// 创建连接
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'db_users'
});
// 连接数据库
connection.connect();
// 执行数据操作
// 查询
connection.query('SELECT * FROM `users`', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results);
});
// 插入
// connection.query('INSERT INTO users VALUES(1,"admin","123456")', function (error, results, fields) {
// if (error) throw error;
// console.log('The solution is: ', results);
// });
// 关闭连接
connection.end();
9. JavaScript中和遍历相关的数组方法
https://www.runoob.com/jsref/jsref-obj-array.html
9.1 every
定义和用法
every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
every() 方法使用指定函数检测数组中的所有元素:
- 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
- 如果所有元素都满足条件,则返回 true。
注意: every() 不会对空数组进行检测。
注意: every() 不会改变原始数组。
语法
array.every(function(currentValue,index,arr), thisValue)
9.2 some
定义和用法
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
- 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。
注意: some() 不会改变原始数组。
语法
array.some(function(currentValue,index,arr),thisValue)
9.3 include
定义和用法
includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
语法
arr.includes(searchElement)
arr.includes(searchElement, fromIndex)
9.4 map
定义和用法
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。
注意: map() 不会改变原始数组。
语法
array.map(function(currentValue,index,arr), thisValue)
9.5 reduce
定义和用法
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce() 可以作为一个高阶函数,用于函数的 compose。
注意: reduce() 对于空数组是不会执行回调函数的。
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
10 综合案例
10.1 目录结构
10.2 模板页
10.3 路由设计
路径 | 方法 | get参数 | post参数 | 是否需要登录权限 | 备注 |
---|---|---|---|---|---|
/ | GET | 渲染首页 | |||
/register | GET | 渲染注册页面 | |||
/register | POST | email、nickname、psssword | 处理注册请求 | ||
/login | GET | 渲染登录页面 | |||
/login | POST | email、password | 处理登录请求 | ||
/loginout | GET | 处理退出请求 |
10.4 模型设计
10.5 功能实现
10.6 编写步骤
-
创建目录结构
-
整合静态页-模板页
-
include:导入header,footer等,形成模板的header,footer
-
{{include 'xxx/xxx/header.html'}} {{include 'xxx/xxx/footer.html'}}
-
-
block:在模板中留坑,方便在其他页面,这个坑里,写不同的内容
-
<!--给标题title留坑--> {{block 'title'}}{{/block}} <!--给自己的body留坑--> {{block 'body'}}{{/blok}}
-
-
extend:在其他页面,使用模板页
// 在其他页面,使用模板页 {{extend 'xxx/xxx/模板页.html'}} // 填坑,在这填,这个页面想要的title部分 {{block 'title'}}{{'标题'}}{{/block}} //填坑,在这,填这个页面的body主体 {{block 'body'}}{{'标题'}}{{/block}}
-
-
设计用户登录、退出、注册的路由
-
用户注册
- 先处理好客户端页面内容(表单控件的name,发送请求,能不能收集表单数据、表单验证等)
- 服务端
- 先获取客户端表单请求的数据
- 操作数据库
- 如果有错,发送500 告诉客户端错了
- 其他的根据业务发送不同的响应数据
-
用户登录
-
用户退出