企业需求
具有服务器端开发经验更好
前端
后端
全栈开发工程师(全干)
基本的网站开发能力
服务端
前端
运维部署
Node.js构建于chrome的v8引擎之上
浏览器中的javascript
Ecmascript
基本的语法
if
var
function
Object
Array
BOM
DOM
node.js中的javascript
没有DOM、BOM
Ecmascript
在node.js这个JavaScript执行环境中为JavaScript提供了一些服务器级别的操作API
例如文件读写
网络服务的构建
网络通信
http服务器
等处理。。。
使用事件驱动、非阻塞和异步I/O模型等技术来提高性能,可优化应用程序的传输量和规模
npm
web 服务器后台
命令行工具
对于前端工程师来讲,接触最多的是它的命令行工具
node 文件名
执行对应的文件node.js
来命名,也就是说除了node
这个名字随便起,最好不要使用中文。文件读取:
//浏览器中的JavaScript是没有文件操作能力的
//但是Node中的JavaScript具有文件操作能力
//fs是file-system的简写,就是文件系统的意思
//在Node中如果想要进行文件的操作就必须引用fs这个核心模块
//在fs这个和兴模块中,就提供了人所有文件操作相关的API
//例如 fs.readFile就是用来读取文件的
// 1.使用fs核心模块
var fs = require('fs');
// 2.读取文件
第一个参数就是要读取的文件路径
第二个参数是一个回调函数
读取成功
data 数据
error null
读取失败
data undefined没有数据
error 错误对象
fs.readFile('./data/a.txt',function(error,data){
if(error){
console.log('文件读取失败');
}
else{
console.log(data.toString());
}
})
//
// 文件中存储的其实都是二进制数据 0 1
// 这里为什么看到的不是 0 和 1 呢?原因是二进制转为 16 进制了
// 但是无论是二进制01还是16进制,人类都不认识
// 所以我们可以通过 toString 方法把其转为我们能认识的字符
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
文件写入:
// 1.使用fs核心模块
var fs = require('fs');
// 2.将数据写入文件
// 第一个参数:文件路径
// 第二个参数:写入内容
// 第三个参数:回调函数
fs.writeFile('./data/a.txt','我是文件写入的信息',function(error){
if(error){
console.log('文件写入失败');
}
else{
console.log('文件写入成功');
}
})
读取文件目录
var fs = require('fs')
// 读取文件目录,目录会保存在files数组中
fs.readdir('C:/Users/meibo/Desktop/web学习/03-后台服务器/node/node/02/code/www', function (err, files) {
if (err) {
return console.log('目录不存在')
}
console.log(files)
})
第一个参数:文件夹路径
第二个参数:回调函数
err 错误信息
file 该文件夹下的文件目录信息(数组形式)
// 1.加载http核心模块
var http = require('http');
// 2.使用http.createServer()创建一个web服务器
var server = http.createServer();
// 3.服务器要做的事儿
// 提供服务:对数据服务
// 发请求
// 接收请求
// 处理请求
// 反馈(发送响应)
// 当客户端请求过来,就会自动触发服务器的request请求事件,然后执行第二个参数:回调处理函数
server.on('request',function(req,res){
//req--request res--response
console.log('收到客户端的请求了,请求路径是:' + req.url);
//response.write('服务器响应客户端的请求');
//write可以使用多次
//response.end();//结束响应,一般直接使用end
let url = req.url;//获取到的是端口号之后的那一部分
if (url === '/') {
res.end('index page')
} else if (url === '/login') {
res.end('login page')
} else if (url === '/products') {
let products = [{
name: '苹果 X',
price: 8888
},
{
name: '菠萝 X',
price: 5000
},
{
name: '小辣椒 X',
price: 1999
}
]
// 响应内容只能是二进制数据或者字符串
// 数字
// 对象
// 数组
// 布尔值
// JSON.parse(),将字符串转数组
// JSON.stringify(),将数组转字符串
res.end(JSON.stringify(products))
} else {
res.end('404 Not Found.')
}
})
// 4.绑定端口号,启动服务
server.listen(3000,function(){
console.log('服务器启动成功了,可以通过http://127.0.0.1:3000/ 来进行访问')
})
不同的资源对应的 Content-Type 是不一样的
text/plain—普通文本
text/html—html格式
http://tool.oschina.net/commons(常用对照表)
var http = require('http')
var server = http.createServer()
server.on('request', function (req, res) {
// 在服务端默认发送的数据,其实是 utf-8 编码的内容
// 但是浏览器不知道你是 utf-8 编码的内容
// 浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码去解析
// 中文操作系统默认是 gbk
// 解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的
// 在 http 协议中,Content-Type 就是用来告知对方我给你发送的数据内容是什么类型
// res.setHeader('Content-Type', 'text/plain; charset=utf-8')
// res.end('hello 世界')
var url = req.url
if (url === '/plain') {
// text/plain 就是普通文本
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('hello 世界')
} else if (url === '/html') {
// 如果你发送的是 html 格式的字符串,则也要告诉浏览器我给你发送是 text/html 格式的内容
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('hello html 点我
')
}
})
server.listen(3000, function () {
console.log('Server is running...')
})
// 结合 fs 发送文件中的数据
// 不同的资源对应的 Content-Type 是不一样的
// 图片不需要指定编码
// 一般只为字符数据才指定编码
var http = require('http')
var fs = require('fs')
var server = http.createServer()
server.on('request', function (req, res) {
// / index.html
var url = req.url
if (url === '/') {
// 我们要发送的还是在文件中的内容
fs.readFile('./resource/index.html', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data)
}
})
} else if (url === '/xiaoming') {
// url:统一资源定位符
// 一个 url 最终其实是要对应到一个资源的
fs.readFile('./resource/ab2.jpg', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
// 图片就不需要指定编码了,因为我们常说的编码一般指的是:字符编码
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
})
server.listen(3000, function () {
console.log('Server is running...')
})
如何通过服务器让客户端重定向?
状态码设置为 302 临时重定向
statusCode
在响应头中通过 Location 告诉客户端往哪儿重定向
setHeader
如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location ,然后对该地址发起新的请求
所以你就能看到客户端自动跳转了
res.statusCode = 302
res.setHeader('Location', '/')
art-template
文档https://aui.github.io/art-template/zh-cn/index.html
下载 npm install art-template
在node中使用模板引擎
const template = require('art-template')//加载模板引擎
const fs = require('fs')
fs.readFile('./tpl.html', function (err, data) {
if (err) {
return console.log('读取文件失败了')
}
// 默认读取到的 data 是二进制数据
// 而模板引擎的 render 方法需要接收的是字符串
// 所以我们在这里需要把 data 二进制数据转为 字符串 才可以给模板引擎使用
// template.render('模板字符串',替换对象)
var ret = template.render(data.toString(), {
name: 'Jack',
age: 18,
province: '北京市',
hobbies: [
'写代码',
'唱歌',
'打游戏'
],
title: '个人信息'
})
console.log(ret)
})
tpl.html文件
{{ title }}
大家好,我叫:{{ name }}
我今年 {{ age }} 岁了
我来自 {{ province }}
我喜欢:{{each hobbies}} {{ $value }} {{/each}}
在浏览器中使用的话需要在html文件中引入
为了让目录结构保持统一清晰,所以我们约定,把所有的 HTML 文件都放到 views(视图) 目录中
我们为了方便的统一处理这些静态资源,所以我们约定把所有的静态资源都存放在 public 目录中
哪些资源能被用户访问,哪些资源不能被用户访问,我现在可以通过代码来进行非常灵活的控制
/public 整个 public 目录中的资源都允许被访问
浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析,
当在解析的过程中,如果发现:
link
script
img
iframe
video
audio
等带有 src 或者 href(link) 属性标签(具有外链的资源)的时候,浏览器会自动对这些资源发起新的请求。
注意:在服务端中,文件中的路径就不要去写相对路径了。因为这个时候所有的资源都是通过 url 标识来获取的,我的服务器开放了 /public/ 目录,所以这里的请求路径都写成:/public/xxx
else if (pathname.indexOf('/public/') === 0) {
// /public/css/main.css
// /public/js/main.js
// /public/lib/jquery.js
// 统一处理:
// 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
// 所以我们就直接可以把请求路径当作文件路径来直接进行读取
fs.readFile('.' + pathname, function (err, data) {
if (err) {
return res.end('404 Not Found.')
}
res.end(data)
})
url模块
var url = require('url')
var obj = url.parse('http://127.1.1.0:80/pinglun?name=的撒的撒&message=的撒的撒的撒', true)
//使用 url.parse 方法将路径解析为一个方便操作的对象,第二个参数为 true 表示直接将查询字符串转为一个对象(通过 query 属性来访问)
console.log(obj)
console.log(obj.pathname) // 单独获取不包含查询字符串的路径部分(该路径不包含 ? 之后和80之前的内容 /pinglun)
console.log(obj.query)//获取表单提交的数据(对象的形式)
EcmaScript语言规范
核心模块(用require加载)
例如:
const fs = require ('fs');
文件操作的fs
http服务的http
url路径操作模块
path路径处理
os操作系统信息
用户自定义模块
第三方模块
在Node中的javascript还有一个很重要的概念:模块系统
加载require
语法:
var 自定义变量名称=require('模块')
两个作用
导出exports
Node中是模块作用域,默认文件中所有的成员只在当前文件模块有效
对于希望可以被其他模块访问的成员,我们就要把这些公开的成员都挂载到exports接口对象中就可以了
导出多个成员(必须在对象中)
exports.a=123
exports.b='hello'
exports.c=function(){
console.log('ccc')
}
exports.d={
foo:'ddd'
}
导出单个成员(拿到的就是:函数、字符串)
module.exports = 'hello'
以下情况会覆盖,后者覆盖前者
module.exports = 'hello'
module.exports = function (x, y) {
return x + y
}
也可这样导出多个成员
module.exports = {
add: function () {
return x + y
},
str: 'hello'
}
exports和module.exports
的一个引用:
console.log(exports === module.exports); //true
exports.foo = 'bar';
//等价于
module.exports.foo = 'bar';
当给exports重新赋值后,exports!= module.exports.
最终return的是module.exports,无论exports中的成员是什么都没用。
真正去使用的时候:
导出单个成员:exports.xxx = xxx;
导出多个成员:module.exports 或者 modeule.exports = {};
// 引用服务
var http = require('http');
var fs = require('fs');
// 引用模板
var template = require('art-template');
// 创建服务
var server = http.createServer();
// 公共路径
var wwwDir = 'D:/app/www';
server.on('request', function (req, res) {
var url = req.url;
// 读取文件
fs.readFile('./template-apche.html', function (err, data) {
if (err) {
return res.end('404 Not Found');
}
fs.readdir(wwwDir, function (err, files) {
if (err) {
return res.end('Can not find www Dir.')
}
// 使用模板引擎解析替换data中的模板字符串
// 去xmpTempleteList.html中编写模板语法
var htmlStr = template.render(data.toString(), {
title: 'D:/app/www/ 的索引',
files:files
});
// 发送响应数据
res.end(htmlStr);
})
})
});
server.listen(3000, function () {
console.log('running....');
})
1.jQuery中的each 和 原生JavaScript方法forEach的区别:
提供源头:
原生js是es5提供的(不兼容IE8),
jQuery的each是jQuery第三方库提供的(如果要使用需要用2以下的版本也就是1.版本),它的each方法主要用来遍历jQuery实例对象(伪数组),同时也可以做低版本forEach的替代品,jQuery的实例对象不能使用forEach方法,如果想要使用必须转为数组([].slice.call(jQuery实例对象))才能使用
2.模块中导出多个成员和导出单个成员
3.301和302的区别:
301永久重定向,浏览器会记住
302临时重定向
4.exports和module.exports的区别:
每个模块中都有一个module对象
module对象中有一个exports对象
我们可以把需要导出的成员都挂载到module.exports接口对象中
也就是`module.exports.xxx = xxx`的方式
但是每次写太多了就很麻烦,所以Node为了简化代码,就在每一个模块中都提供了一个成员叫`exports`
`exports === module.exports`结果为true,所以完全可以`exports.xxx = xxx`
当一个模块需要导出单个成员的时候必须使用`module.exports = xxx`的方式,=,使用`exports = xxx`不管用,因为每个模块最终return的是module.exports,而exports只是module.exports的一个引用,所以`exports`即使重新赋值,也不会影响`module.exports`。
有一种赋值方式比较特殊:`exports = module.exports`这个用来新建立引用关系的。
优先从缓存加载
判断模块标识符
核心模块
自己写的模块(路径形式的模块)
第三方模块(node_modules)
第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
npm
使用方式:
var 名称 = require('npm install【下载包】 的包名')
// 如果非路径形式的标识
// 路径形式的标识:
// ./ 当前目录 不可省略
// ../ 上一级目录 不可省略
// /xxx也就是D:/xxx 几乎不用
// 带有绝对路径几乎不用(D:/a/foo.js)
// 首位表示的是当前文件模块所属磁盘根目录
// require('./a');
// 核心模块
// 核心模块本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
require('fs');
// 第三方模块
// 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过require('包名')来加载才可以使用
// 第三方包的名字不可能和核心模块的名字是一样的
// 既不是核心模块,也不是路径形式的模块
// 先找到当前文所述目录的node_modules
// 然后找node_modules/art-template目录
// node_modules/art-template/package.json
// node_modules/art-template/package.json中的main属性
// main属性记录了art-template的入口模块
// 然后加载使用这个第三方包
// 实际上最终加载的还是文件
// 如果package.json不存在或者mian指定的入口模块不存在
// 则node会自动找该目录下的index.js
// 也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
//
// 如果条件都不满足则会进入上一级目录进行查找
// 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
var template = require('art-template');
/
和文件操作路径中的/
文件操作路径:
// 咱们所使用的所有文件操作的API都是异步的
// 就像ajax请求一样
// 读取文件
// 文件操作中的相对路径可以省略./
fs.readFile('index.txt',function(err,data){
if(err){
return console.log('读取失败');
}
console.log(data.toString());
})
模块操作路径:
// 在模块加载中,相对路径中的./不能省略
// 这里省略了.也是磁盘根目录
require('./index')('hello')
npmjs.com 网站 是用来搜索npm包的
npm是一个命令行工具,只要安装了node就已经安装了npm。
npm也有版本概念,可以通过npm --version
来查看npm的版本
升级npm(自己升级自己):
npm install --global npm
npm存储包文件的服务器在国外,有时候会被墙,速度很慢,所以需要解决这个问题。
https://developer.aliyun.com/mirror/NPM?from=tnpm淘宝的开发团队把npm在国内做了一个镜像(也就是一个备份)。
安装淘宝的cnpm:
npm install -g cnpm --registry=https://registry.npm.taobao.org;
#在任意目录执行都可以
#--global表示安装到全局,而非当前目录
#--global不能省略,否则不管用
npm install --global cnpm
安装包的时候把以前的npm
替换成cnpm
。
#走国外的npm服务器下载jQuery包,速度比较慢
npm install jQuery;
#使用cnpm就会通过淘宝的服务器来下载jQuery
cnpm install jQuery;
如果不想安装cnpm
又想使用淘宝的服务器来下载:
npm install jquery --registry=https://npm.taobao.org;
但是每次手动加参数就很麻烦,所以我们可以把这个选项加入到配置文件中:
npm config set registry https://npm.taobao.org;
#查看npm配置信息
npm config list;
只要经过上面的配置命令,则以后所有的npm install
都会通过淘宝的服务器来下载
每一个项目都要有一个package.json
文件(包描述文件,就像产品的说明书一样)
这个文件可以通过npm init
自动初始化出来
D:\code\node中的模块系统>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 ` 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: (node中的模块系统)
Sorry, name can only contain URL-friendly characters.
package name: (node中的模块系统) cls
version: (1.0.0)
description: 这是一个测试项目
entry point: (main.js)
test command:
git repository:
keywords:
author: xiaochen
license: (ISC)
About to write to D:\code\node中的模块系统\package.json:
{
"name": "cls",
"version": "1.0.0",
"description": "这是一个测试项目",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "xiaochen",
"license": "ISC"
}
Is this OK? (yes) yes
对于目前来讲,最有用的是dependencies
选项,可以用来帮助我们保存第三方包的依赖信息。
如果node_modules
删除了也不用担心,只需要在控制面板中npm install
就会自动把package.json
中的dependencies
中所有的依赖项全部都下载回来。
package.json
文件npm install 包名
的时候都加上--save
选项,目的是用来保存依赖信息npm 5以前是不会有package-lock.json
这个文件
npm5以后才加入这个文件
当你安装包的时候,npm都会生成或者更新package-lock.json
这个文件
npm5以后的版本安装都不要加--save
参数,它会自动保存依赖信息
当你安装包的时候,会自动创建或者更新package-lock.json
文件
package-lock.json
这个文件会包含
node_modules
中所有包的信息(版本,下载地址。。。)
npm install
的时候速度就可以提升从文件来看,有一个
lock
称之为锁
lock
使用来锁版本的1.1.1
版本1.1.1
package-lock.json
的另外一个作用就是锁定版本号,防止自动升级参考文档:https://nodejs.org/docs/latest-v13.x/api/path.html
path.basename:获取路径的文件名,默认包含扩展名
path.basename(c:/a/b/c/index.js)//结果为index.js
path.basename(c:/a/b/c/index.js,'.js')//结果为index
path.dirname:获取路径中的目录部分
path.dirname(c:/a/b/c/index.js)//结果为c:/a/b/c
path.extname:获取一个路径中的扩展名部分
path.parse:把路径转换为对象
path.join:拼接路径
path.join('c:/a','b'),结果c:\\a\\b,\\其实是代表一个\(转义)
path.isAbsolute:判断一个路径是否为绝对路径,返回布尔值
在每个模块中,除了require
,exports
等模块相关的API之外,还有两个特殊的成员:
__dirname
,是一个成员,可以用来动态获取当前文件模块所属目录
的绝对路径
__filename
,可以用来动态获取当前文件
的绝对路径(包含文件名)
__dirname
和__filename
是不受执行node命令所属路径影响的
在文件操作中,使用相对路径是不可靠的,因为node中文件操作的路径被设计为相对于执行node命令所处的路径。
所以为了解决这个问题,只需要把相对路径变为绝对路径(绝对路径不受任何影响)就可以了。
就可以使用__dirname
或者__filename
来帮助我们解决这个问题
在拼接路径的过程中,为了避免手动拼接带来的一些低级错误,推荐使用path.join()
来辅助拼接
var fs = require('fs');
var path = require('path');
// console.log(__dirname + 'a.txt');
// path.join方法会将文件操作中的相对路径都统一的转为动态的绝对路径
fs.readFile(path.join(__dirname + '/a.txt'),'utf8',function(err,data){
if(err){
throw err
}
console.log(data);
});
补充:模块中的路径标识和这里的路径没关系,不受影响(就是相对于文件模块)
注意:
模块中的路径标识和文件操作中的相对路径标识不一致
模块中的路径标识就是相对于当前文件模块,不受node命令所处路径影响
原生的http在某些方面表现不足以应对我们的开发需求,所以就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码高度统一。
在node中有很多web开发框架。主要学习express,koa
http://expressjs.com/
,其中主要封装的是http。
// 1 安装
// 2 引包
var express = require('express');
// 3 创建服务器应用程序
// 也就是原来的http.createServer();
var app = express();
// 公开指定目录
// 只要通过这样做了,就可以通过/public/xx的方式来访问public目录中的所有资源
// 在Express中开放资源就是一个API的事
app.use('/public/',express.static('/public/'));
//模板引擎在Express中开放模板也是一个API的事
// 当服务器收到get请求 / 的时候,执行回调处理函数
app.get('/',function(req,res){
res.send('hello express');
})
// 相当于server.listen
app.listen(3000,function(){
console.log('app is runing at port 3000');
})
npm install express
// 引入express
var express = require('express');
// 1. 创建app
var app = express();
// 2.
app.get('/',function(req,res){
// 1
// res.write('Hello');
// res.write('World');
// res.end()
// 2
// res.end('hello world');
// 3
res.send('hello world');
})
app.listen(3000,function(){
console.log('express app is runing...');
})
路由:
get:
//当你以get方法请求/的时候,执行对应的处理函数
app.get('/',function(req,res){
res.send('hello world');
})
post:
//当你以post方法请求/的时候,执行对应的处理函数
app.post('/',function(req,res){
res.send('hello world');
})
const express=require('express');
const app=express();
//当以/public/开头的时候,去./public/目录中找对应的资源
//这种方式更清晰直观一点,用的最多
app.use('/public/',express.static('./public/'))
//当省略第一个参数时,则可以通过 省略 /public 的方式来访问
// 这种方式的好处就是可以省略 /public/
// app.use(express.static('./public/'))
// 必须是 /a/puiblic目录中的资源具体路径,/abc/d/代替了public相当于别名
// app.use('/abc/d/', express.static('./public/'))
app.get('/',function(req,res){
res.send('hello express');
})
app.listen(3000,function(){
console.log('express run in port 3000');
})
// /public资源
app.use(express.static('public'))
// /files资源
app.use(express.static('files'))
// /public/xxx
app.use('/public',express.static('public'))
// /static/xxx
app.use('/static',express.static('public'))
app.use('/static',express.static(path.join(__dirname,'public')))
art-template官方文档
在node中,有很多第三方模板引擎都可以使用,不是只有
art-template
安装:
npm install art-template
npm install express-art-template
//两个一起安装
npm i art-template express-art-template
配置:
app.engine('html', require('express-art-template'));
使用:
app.get('/',function(req,res){
// express默认会去views目录找index.html
res.render('index.html',{
title:'hello world'
});
})
如果希望修改默认的views
视图渲染存储目录,可以:
// 第一个参数views千万不要写错
app.set('views',目录路径);
重定向:
res.redirect('/')
// res.statusCode = 302
// res.setHeader('Location', '/')
Express内置了一个api,可以直接通过req.query
来获取数据
// 通过requery方法获取用户输入的数据
// req.query只能拿到get请求的数据
var comment = req.query;
在Express中没有内置获取表单post请求体的api,这里我们需要使用一个第三方包body-parser
来获取数据。
安装:
npm install body-parser;
配置:
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
var express = require('express')
// 引包
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来获取表单请求数据
res.end(JSON.stringify(req.body, null, 2))
})
// Express 对于没有设定的请求路径,默认会返回 Cat not get xxx
// 如果你想要定制这个 404
// 需要通过中间件来配置
// 咱们讲中间件的时候说一下如何处理
// 只需要在自己的路由之后增加一个
app.use(function (req, res) {
// 所有未处理的请求路径都会跑到这里
// 404
})
参考文档:https://github.com/expressjs/session
安装:
npm install express-session
配置:
//该插件会为req请求对象添加一个成员:req.session默认是一个对象
//这是最简单的配置方式
//Session是基于Cookie实现的
app.use(session({
//配置加密字符串,他会在原有的基础上和字符串拼接起来去加密
//目的是为了增加安全性,防止客户端恶意伪造
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,//无论是否适用Session,都默认直接分配一把钥匙
cookie: { secure: true }
}))
使用:
// 读
//添加Session数据
//session就是一个对象
req.session.foo = 'bar';
//写
//获取session数据
req.session.foo
//删
req.session.foo = null;
delete req.session.foo
提示:
默认Session数据时内存储数据,服务器一旦重启,真正的生产环境会把Session进行持久化存储。
模块如何划分:
javascript模块化:
请求方法 | 请求路径 | 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,hobbies | 处理编辑请求 | |
GET | /students/delete | id | 处理删除请求 |
router.js:
/**
* router.js路由模块
* 职责:
* 处理路由
* 根据不同的请求方法+请求路径设置具体的请求函数
* 模块职责要单一,我们划分模块的目的就是增强代码的可维护性,提升开发效率
*/
var fs = require('fs');
// Express专门提供了一种更好的方式
// 专门用来提供路由的
var express = require('express');
// 1 创建一个路由容器
var router = express.Router();
// 2 把路由都挂载到路由容器中
router.get('/students', function(req, res) {
// res.send('hello world');
// readFile的第二个参数是可选的,传入utf8就是告诉他把读取到的文件直接按照utf8编码,直接转成我们认识的字符
// 除了这样来转换,也可以通过data.toString()来转换
fs.readFile('./db.json', 'utf8', function(err, data) {
if (err) {
return res.status(500).send('Server error.')
}
// 读取到的文件数据是string类型的数据
// console.log(data);
// 从文件中读取到的数据一定是字符串,所以一定要手动转换成对象
var students = JSON.parse(data).students;
res.render('index.html', {
// 读取文件数据
students:students
})
})
});
router.get('/students/new',function(req,res){
res.render('new.html')
});
router.get('/students/edit',function(req,res){
});
router.post('/students/edit',function(req,res){
});
router.get('/students/delete',function(req,res){
});
// 3 把router导出
module.exports = router;
app.js:
var router = require('./router');
// router(app);
// 把路由容器挂载到app服务中
// 挂载路由
app.use(router);
es6中的find和findIndex:
find接受一个方法作为参数,方法内部返回一个条件
find会便利所有的元素,执行你给定的带有条件返回值的函数
符合该条件的元素会作为find方法的返回值
如果遍历结束还没有符合该条件的元素,则返回undefined
/**
* student.js
* 数据操作文件模块
* 职责:操作文件中的数据,只处理数据,不关心业务
*/
var fs = require('fs');
/**
* 获取所有学生列表
* return []
*/
exports.find = function(){
}
/**
* 获取添加保存学生
*/
exports.save = function(){
}
/**
* 更新学生
*/
exports.update = function(){
}
/**
* 删除学生
*/
exports.delete = function(){
}
注意:
模板页:
模板页
{{ block 'head' }}{{ /block }}
{{include './header.html'}}
{{ block 'content' }}
默认内容
{{ /block }}
{{include './footer.html'}}
{{ block 'script' }}{{ /block }}
模板的继承:
header页面:
公共的头部
footer页面:
公共的底部
模板页的使用:
{{extend './layout.html'}}
{{ block 'head' }}
{{ /block }}
{{ block 'content' }}
Index页面的内容
{{ /block }}
{{ block 'script' }}
{{ /block }}
最终的显示效果:
关系型数据库(表就是关系,或者说表与表之间存在关系)。
sql
语言来操作非关系型数据库
非关系型数据库非常的灵活
有的关系型数据库就是key-value对儿
但MongoDB是长得最像关系型数据库的非关系型数据库
mongoDB不需要设计表结构
一个数据库中可以有多个数据库,一个数据库中可以有多个集合(数组),一个集合中可以有多个文档(表记录)
{
qq:{
user:[
{name:'张三',age:20},
{name:'张三',age:20},
{name:'张三',age:20},
{name:'张三',age:20},
...
]
},
taobao:{
user:[
{name:'张三',age:20},
{name:'张三',age:20},
{name:'张三',age:20},
{name:'张三',age:20},
...
]
},
baidu:{
user:[
{name:'张三',age:20},
{name:'张三',age:20},
{name:'张三',age:20},
{name:'张三',age:20},
...
]
}
}
下载
安装
npm i mongoose
配置环境变量
最后输入mongod --version
测试是否安装成功
启动:
# mongodb 默认使用执行mongod 命令所处盘符根目录下的/data/db作为自己的数据存储目录
# 所以在第一次执行该命令之前先自己手动新建一个 /data/db
mongod
如果想要修改默认的数据存储目录,可以:
mongod --dbpath = 数据存储目录路径
停止:
在开启服务的控制台,直接Ctrl+C;
或者直接关闭开启服务的控制台。
连接:
# 该命令默认连接本机的 MongoDB 服务
mongo
退出:
# 在连接状态输入 exit 退出连接
exit
show dbs
db
use 数据库名称
show collections
db.表名.find()
插入数据
db.数据库名称.insertOne({"name":"jack"})
MongoDB
包来操作https://github.com/mongodb/node-mongodb-native
mongoose
来操作MongoDB数据库第三方包:mongoose
基于MongoDB官方的mongodb
包再一次做了封装,名字叫mongoose
,是WordPress项目团队开发的。
https://mongoosejs.com/
官方学习文档:https://mongoosejs.com/docs/index.html
// 1.引包
// 注意:按照后才能require使用
var mongoose = require('mongoose');
// 拿到schema图表
var Schema = mongoose.Schema;
// 2.连接数据库
// 指定连接数据库后不需要存在,当你插入第一条数据库后会自动创建数据库
mongoose.connect('mongodb://localhost/test');
// 3.设计集合结构(表结构)
// 用户表
var userSchema = new Schema({
username: { //姓名
type: String,
require: true //添加约束,保证数据的完整性,让数据按规矩统一
},
password: {
type: String,
require: true
},
email: {
type: String
}
});
// 4.将文档结构发布为模型
// mongoose.model方法就是用来将一个架构发布为 model
// 第一个参数:传入一个大写名词单数字符串用来表示你的数据库的名称
// mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称
// 例如 这里会变成users集合名称
// 第二个参数:架构
// 返回值:模型构造函数
var User = mongoose.model('User', userSchema);
// 5.通过模型构造函数对User中的数据进行操作
var user = new User({
username: 'admin',
password: '123456',
email: 'xiaochen@qq.com'
});
user.save(function(err, ret) {
if (err) {
console.log('保存失败');
} else {
console.log('保存成功');
console.log(ret);
}
});
根据条件删除所有:
User.deleteMany({
username: 'xiaoxiao'
}, function(err, ret) {
if (err) {
console.log('删除失败');
} else {
console.log('删除成功');
console.log(ret);
}
});
根据条件删除一个:
User.deleteOne({
username: 'xiaoxiao'
}, function(err, ret) {
if (err) {
console.log('删除失败');
} else {
console.log('删除成功');
console.log(ret);
}
});
更新所有:
User.update(conditions,doc,[options],[callback]);
根据指定条件更新一个:
User.FindOneAndUpdate([conditions],[update],[options],[callback]);
根据id更新一个:
// 更新 根据id来修改表数据
User.findByIdAndUpdate('5e6c5264fada77438c45dfcd', {
username: 'junjun'
}, function(err, ret) {
if (err) {
console.log('更新失败');
} else {
console.log('更新成功');
}
});
查询所有:
// 查询所有
User.find(function(err,ret){
if(err){
console.log('查询失败');
}else{
console.log(ret);
}
});
条件查询所有:
// 根据条件查询
User.find({ username:'xiaoxiao' },function(err,ret){
if(err){
console.log('查询失败');
}else{
console.log(ret);
}
});
条件查询单个:
// 按照条件查询单个,查询出来的数据是一个对象({})
// 没有条件查询使用findOne方法,查询的是表中的第一条数据
User.findOne({
username: 'xiaoxiao'
}, function(err, ret) {
if (err) {
console.log('查询失败');
} else {
console.log(ret);
}
});
文档:https://www.npmjs.com/package/mysql
安装:
npm install mysql
// 引入mysql包
var mysql = require('mysql');
// 创建连接
var connection = mysql.createConnection({
host : 'localhost', //本机
user : 'me', //账号root
password : 'secret', //密码12345
database : 'my_db' //数据库名
});
// 连接数据库 (打开冰箱门)
connection.connect();
//执行数据操作 (把大象放到冰箱)
connection.query('SELECT * FROM `users` ', function (error, results, fields) {
if (error) throw error;//抛出异常阻止代码往下执行
// 没有异常打印输出结果
console.log('The solution is: ',results);
});
//关闭连接 (关闭冰箱门)
connection.end();
不成立的情况下:
function add(x,y){
console.log(1);
setTimeout(function(){
console.log(2);
var ret = x + y;
return ret;
},1000);
console.log(3);
//到这里执行就结束了,不会i等到前面的定时器,所以直接返回了默认值 undefined
}
console.log(add(2,2));
// 结果是 1 3 undefined 4
使用回调函数解决:
回调函数:通过一个函数,获取函数内部的操作。(根据输入得到输出结果)
这是javascript编程的一大特色,异步编程
var ret;
function add(x,y,callback){
// callback就是回调函数
// var x = 10;
// var y = 20;
// var callback = function(ret){console.log(ret);}
console.log(1);
setTimeout(function(){
var ret = x + y;
callback(ret);
},1000);
console.log(3);
}
add(10,20,function(ret){
console.log(ret);
});
注意:
凡是需要得到一个函数内部异步操作的结果(setTimeout,readFile,writeFile,ajax,readdir)
这种情况必须通过 回调函数 (异步API都会伴随着一个回调函数)
ajax:
基于原生XMLHttpRequest封装get方法:
var oReq = new XMLHttpRequest();
// 当请求加载成功要调用指定的函数
oReq.onload = function(){
console.log(oReq.responseText);
}
oReq.open("GET", "请求路径",true);
oReq.send();
function get(url,callback){
var oReq = new XMLHttpRequest();
// 当请求加载成功要调用指定的函数
oReq.onload = function(){
//console.log(oReq.responseText);
callback(oReq.responseText);
}
oReq.open("GET", url,true);
oReq.send();
}
get('data.json',function(data){
console.log(data);
});
文件的读取无法判断执行顺序(文件的执行顺序是依据文件的大小来决定的)(异步api无法保证文件的执行顺序)
var fs = require('fs');
fs.readFile('./data/a.text','utf8',function(err,data){
if(err){
// 1 读取失败直接打印输出读取失败
return console.log('读取失败');
// 2 抛出异常
// 阻止程序的执行
// 把错误信息打印到控制台
throw err;
}
console.log(data);
});
fs.readFile('./data/b.text','utf8',function(err,data){
if(err){
// 1 读取失败直接打印输出读取失败
return console.log('读取失败');
// 2 抛出异常
// 阻止程序的执行
// 把错误信息打印到控制台
throw err;
}
console.log(data);
});
var fs = require('fs');
fs.readFile('./data/a.text','utf8',function(err,data){
if(err){
// 1 读取失败直接打印输出读取失败
return console.log('读取失败');
// 2 抛出异常
// 阻止程序的执行
// 把错误信息打印到控制台
throw err;
}
console.log(data);
fs.readFile('./data/b.text','utf8',function(err,data){
if(err){
// 1 读取失败直接打印输出读取失败
return console.log('读取失败');
// 2 抛出异常
// 阻止程序的执行
// 把错误信息打印到控制台
throw err;
}
console.log(data);
fs.readFile('./data/a.text','utf8',function(err,data){
if(err){
// 1 读取失败直接打印输出读取失败
return console.log('读取失败');
// 2 抛出异常
// 阻止程序的执行
// 把错误信息打印到控制台
throw err;
}
console.log(data);
});
});
});
为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6新增了一个API:Promise
。
Promise:承诺,保证
Promise本身不是异步的,但往往都是内部封装一个异步任务
基本语法:
// 在EcmaScript 6中新增了一个API Promise
// Promise 是一个构造函数
var fs = require('fs');
// 1 创建Promise容器 resolve:解决 reject:失败
var p1 = new Promise(function(resolve, reject) {
fs.readFile('./a.text', 'utf8', function(err, data) {
if (err) {
// console.log(err);
// 把容器的Pending状态变为rejected
reject(err);
} else {
// console.log(data);
// 把容器的Pending状态变为resolve
resolve(1234);
}
});
});
// 当p1成功了,然后就(then)做指定的操作
// then方法接收的function就是容器中的resolve函数
p1
.then(function(data) {
console.log(data);
}, function(err) {
console.log('读取文件失败了', err);
});
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('./a.txt')
.then(function(data) {
console.log(data);
return pReadFile('./b.txt');
})
.then(function(data) {
console.log(data);
return pReadFile('./a.txt');
})
.then(function(data) {
console.log(data);
})
// 查询所有
User.find()
.then(function(data){
console.log(data)
})
注册:
User.findOne({username:'admin'},function(user){
if(user){
console.log('用户已存在')
} else {
new User({
username:'aaa',
password:'123',
email:'fffff'
}).save(function(){
console.log('注册成功');
})
}
})
User.findOne({
username:'admin'
})
.then(function(user){
if(user){
// 用户已经存在不能注册
console.log('用户已存在');
}
else{
// 用户不存在可以注册
return new User({
username:'aaa',
password:'123',
email:'fffff'
}).save();
}
})
.then(funciton(ret){
console.log('注册成功');
})
模板
数据接口(data.json数据)
封装ajax方法
function get(url, callback) {
const oReq = new XMLHttpRequest()
// 当请求加载成功之后要调用指定的函数
oReq.onload = function () {
// 我现在需要得到这里的 oReq.responseText
callback(oReq.responseText)
}
oReq.open("get", url, true)
oReq.send()
}
使用ajax(代码不美观)
get('http://127.0.0.1:3000/users/4', function (userData) {
get('http://127.0.0.1:3000/jobs', function (jobsData) {
let htmlStr = template('tpl', {
user: JSON.parse(userData),
jobs: JSON.parse(jobsData)
})
console.log(htmlStr)
document.querySelector('#user_form').innerHTML = htmlStr
})
})
使用jquery(需先引入jquery)
let data = {}
$.get('http://127.0.0.1:3000/users/4')
.then(function (user) {
data.user = user
return $.get('http://127.0.0.1:3000/jobs')
})
.then(function (jobs) {
data.jobs = jobs
let htmlStr = template('tpl', data)
document.querySelector('#user_form').innerHTML = htmlStr
})
封装promise版本的ajax
function pGet(url, callback) {
return new Promise(function (resolve, reject) {
const oReq = new XMLHttpRequest()
// 当请求加载成功之后要调用指定的函数
oReq.onload = function () {
// 我现在需要得到这里的 oReq.responseText
callback && callback(JSON.parse(oReq.responseText))
resolve(JSON.parse(oReq.responseText))
}
oReq.onerror = function (err) {
reject(err)
}
oReq.open("get", url, true)
oReq.send()
})
}
两种写法
pGet('http://127.0.0.1:3000/users/4', function (data) {
console.log(data)
})
pGet('http://127.0.0.1:3000/users/4')
.then(function (data) {
console.log(data)
})
使用
let data = {}
pGet('http://127.0.0.1:3000/users/4')
.then(function (user) {
data.user = user
return pGet('http://127.0.0.1:3000/jobs')
})
.then(function (jobs) {
data.jobs = jobs
var htmlStr = template('tpl', data)
document.querySelector('#user_form').innerHTML = htmlStr
})
async函数
安装
npm install -g json-server
使用
创建一个data.json
文件
json-server --watch data.json
安装
npm install --global http-server
使用
参考文档https://github.com/http-party/http-server
hs -c-l -o
C:\\a\\demo
,因为反斜杠\是转义字符标志http://taobao.com
我们在这里可以使用一个第三方命名行工具:nodemon
来帮助我们解决频繁修改代码重启服务器的问题。
nodemon
是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装:
#在任意目录执行该命令都可以
#也就是说,所有需要 --global安装的包都可以在任意目录执行
npm install --global nodemon
npm install -g nodemon
#如果安装不成功的话,可以使用cnpm安装
cnpm install -g nodemon
安装完毕之后使用:
node app.js
#使用nodemon
nodemon app.js
只要是通过nodemon
启动的服务,则他会监视你的文件变化,当文件发生变化的时候,会自动帮你重启服务器。
回调函数:获取异步操作的结果
function fn(callback){
// var callback = funtion(data){ console.log(data); }
setTimeout(function(){
var data = 'hello';
callback(data);
},1000);
}
// 如果需要获取一个函数中异步操作的结果,则必须通过回调函数的方式来获取
fn(function(data){
console.log(data);
})
参考文档:https://es6.ruanyifeng.com/
.
app.js 项目的入口文件
controllers
models 存储使用mongoose设计的数据模型
node_modules 第三方包
package.json 包描述文件
package-lock.json 第三方包版本锁定文件(npm5之后才有)
public 公共静态资源
README.md 项目说明文档
routes 路由
views 存储视图目录
路由 | 方法 | get参数 | post参数 | 是否需要登录 | 备注 |
---|---|---|---|---|---|
/ | get | 渲染首页 | |||
/register(登录) | get | 渲染注册页面 | |||
/register | post | email,nickname,password | 处理注册请求 | ||
/login | get | 渲染登陆界面 | |||
/login | post | email,password | 处理登录请求 | ||
/loginout | get | 处理退出请求 | |||
参考文档:http://expressjs.com/en/guide/using-middleware.html
中间件:把很复杂的事情分割成单个,然后依次有条理的执行。就是一个中间处理环节,有输入,有输出。
说的通俗易懂点儿,中间件就是一个(从请求到响应调用的方法)方法。
把数据从请求到响应分步骤来处理,每一个步骤都是一个中间处理环节。
var http = require('http');
var url = require('url');
var cookie = require('./expressPtoject/cookie');
var query = require('./expressPtoject/query');
var postBody = require('./expressPtoject/post-body');
var session = require('./middlewares/session')
var server = http.createServer(function(){
// 解析请求地址中的get参数
// var obj = url.parse(req.url,true);
// req.query = obj.query;
query(req,res); //中间件
// 解析请求地址中的post参数
//req.body = {
// foo:'bar'
//}
postBody(req, res)
// 解析 Cookie
// req.cookies = {
// isLogin: true
// }
cookie(req, res)
// 配置 Session
// req.session = {}
session(req, res)
// 配置模板引擎
res.render = function () {
}
});
if (req.url === 'xxx') {
// 处理
// query、body、cookies、session、render API 成员
} else if (url === 'xx') {
// 处理
}
// 上面的过程都是了为了在后面做具体业务操作处理的时候更方便
server.listen(3000,function(){
console.log('3000 runing...');
});
同一个请求对象所经过的中间件都是同一个请求对象和响应对象。
var express = require('express');
var app = express();
app.get('/abc',function(req,res,next){
// 同一个请求的req和res是一样的,
// 可以前面存储下面调用
console.log('/abc');
// req.foo = 'bar';
req.body = {
name:'xiaoxiao',
age:18
}
next();
});
app.get('/abc',function(req,res,next){
// console.log(req.foo);
console.log(req.body);
console.log('/abc');
});
app.listen(3000, function() {
console.log('app is running at port 3000.');
});
万能匹配(不关心任何请求路径和请求方法的中间件):
app.use(function(req,res,next){
console.log('Time',Date.now());
next();
});
关心请求路径和请求方法的中间件:
app.use('/a',function(req,res,next){
console.log('Time',Date.now());
next();
});
严格匹配请求路径和请求方法的中间件
get:
app.get('/',function(req,res){
res.send('get');
});
post:
app.post('/a',function(req,res){
res.send('post');
});
put:
app.put('/user',function(req,res){
res.send('put');
});
delete:
app.delete('/delete',function(req,res){
res.send('delete');
});
var express = require('express');
var app = express();
// 中间件:处理请求,本质就是个函数
// 在express中,对中间件有几种分类
// 1 不关心任何请求路径和请求方法的中间件
// 也就是说任何请求都会进入这个中间件
// 中间件本身是一个方法,该方法接收三个参数
// Request 请求对象
// Response 响应对象
// next 下一个中间件
// // 全局匹配中间件
// app.use(function(req, res, next) {
// console.log('1');
// // 当一个请求进入中间件后
// // 如果需要请求另外一个方法则需要使用next()方法
// next();
// // next是一个方法,用来调用下一个中间件
// // 注意:next()方法调用下一个方法的时候,也会匹配(不是调用紧挨着的哪一个)
// });
// app.use(function(req, res, next) {
// console.log('2');
// });
// // 2 关心请求路径的中间件
// // 以/xxx开头的中间件
// app.use('/a',function(req, res, next) {
// console.log(req.url);
// });
// 3 严格匹配请求方法和请求路径的中间件
app.get('/',function(){
console.log('/');
});
app.post('/a',function(){
console.log('/a');
});
app.listen(3000, function() {
console.log('app is running at port 3000.');
});
app.use(function(err,req,res,next){
console.error(err,stack);
res.status(500).send('Something broke');
});
配置使用404中间件:
app.use(function(req,res){
res.render('404.html');
});
配置全局错误处理中间件:
app.get('/a', function(req, res, next) {
fs.readFile('.a/bc', funtion() {
if (err) {
// 当调用next()传参后,则直接进入到全局错误处理中间件方法中
// 当发生全局错误的时候,我们可以调用next传递错误对象
// 然后被全局错误处理中间件匹配到并进行处理
next(err);
}
})
});
//全局错误处理中间件
app.use(function(err,req,res,next){
res.status(500).json({
err_code:500,
message:err.message
});
});
参考文档:http://expressjs.com/en/resources/middleware.html