简介
- 特点
- 单线程
- 非阻塞I/O
- 事件驱动
当客户端请求建立连接/提交数据等行为,都会触发相应的事件,而且同一时间只能执行一个事件处理函数,不过可以在执行一个事件处理函数之时,转而执行其他事件处理函数,类似于python的携程工作机制,这里称之为 事件环机制
善于I/O处理,不善于逻辑计算处理,Node不是一种独立的语言,也没有自己的web容器,它是基于Chrome V8引擎的javascript运行环境,即相当于是将Chrome浏览器的js解析器移植到自身身上
- 和浏览器的异同
都是javascript的运行环境,能够解析js程序,二者都支持ECMAScript语法,不过Node无法使用WebAPI,即无法进行BOM和DOM的操作,同样的,浏览器也无法实现Node中的文件操作功能
- 执行js程序的方式
node path/name.js
这是执行js程序的命令,另外Node可以和浏览器中console中一样,进行js的交互性操作
模块化
- 简介
一个js文件就是一个模块,每个模块都是一个独立的作用域,在这个作用域中的变量、函数、对象都是私有的,其他文件不可见,如某模块中引入了a模块
require('./a')
,程序会执行一遍a.js,但是a.js中的变量和函数等仍旧不可用
- 自定义模块
如上的a模块就是自定义模块,有两点需要注意下,一是js后缀可以省略,而是自定义模块必须加上路径,如上的
./
,否则会先被当做核心模块,核心模块中无此文件,继续被当做第三方模块,依旧查无此件,最终引发报错
- node中模块的分类
- 核心模块
node自带的模块,如fs、path、http、url、queryString等
- 第三方模块
需要先进行下载安装的模块,如mime、art-template等
- 自定义模块
- 模块查找规则
首先,检查模块名称中是否含有路径,有则判定为自定义模块,并到该路径中加载文件 如果不含有路径,首先当做核心模块,并到node安装路径的模块文件夹中查找 如果文件夹中找不到该模块文件,则当做第三方模块,此时按以下顺序 1. 到当前js文件的同级目录中查找模块文件夹[node_modules] 2. 在node_modules中查找和模块同名的文件夹 3. 在同名文件夹中查找package.json文件 4. 在package.json中查找main属性,属性值中的路径即为模块路径 5. 如果以上过程中发现不存在package.json,或者package.json中无main属性时,则取默认值,即当前目录中的index.js / index.json / index.node 6. 如果该同级目录中没有上述的这些index文件,则继续向上一级进行[node_modules]查找步骤,直至磁盘根路径 7. 如果磁盘根路径依旧查找无果,则抛出错误
- 模块导入:require
/* * require('模块名称'); * 模块名称即为package.json中的name值 * 模块名称的.js后缀可以省略,且如果引入的模块名称为index.js,则整个名称都可以省略 */
- 模块导出:exports
- 说明
正常情况下,一个自定义模块无法使用另一个自定义模块的变量和函数等,通过 module的exports属性 或者 exports全局属性 则可以实现导出模块数据供其他模块使用
- 使用module的exports属性
// a.js let num = 1000; module.exports.num = num; // b.js const a = require('./a'); console.log(a.num); //1000
- 使用全局属性exports
exports.num = 1000;
- module的exports属性 和 全局属性exports 的区别
//a.js exports = {name: 'zhangsan'} module.exports = {name: 'xiaoming'} //b.js const a = require('./a') console.log(a); // {name = 'xiaoming'}
在b模块中接收的是a模块暴露出来的一个对象,而module.exports直接就是指向的这个模块,exports全局属性的出现是为了简写,它指向的是module.exports,从而间接地指向了暴露出去的对象,另外一旦exports指向一个新对象时,将不再指向module.exports,故而上面代码中生效的是module.exports
global:全局变量模块
- 说明
类似浏览器中的window对象,是个全局对象,可以在任何地方直接使用,且此模块无需进行引入
- 常用属性/方法
- console
global.console.log("hello"); //可以省略global,功能同window的console
- 定时器、延时器
- __dirname
console.log(__dirname); //输出当前文件所处目录,注意不要写成global.__dirname这样会被解析成一个自定义的全局变量,结果为undefined
- __filename
输出当前文件路径 + 文件名称
- module
对当前模块的一个引用
- require
用于加载模块,它只能在模块中使用
- 定义全局变量
// foo.js global.foo = "foo"; //在foo模块中定义的全局变量
// bar.js require("./foo"); //在bar模块中加载foo模块 console.log(foo); //即在任意模块中都可以使用全局变量
fs:文件操作模块
- 引入
const fs = require('fs');
- 常用方法
const fs = require("fs"); /** * 异步读取文件:readFile(path[, options], callback) * 同步读取文件:readFileSync(path[, options]),返回文件内容,没有回调函数,且会等待文件读取完成后才执行后续代码 * err:发生错误时返回的错误信息 * data:文件内容,默认返回的是二进制形式的Buffer对象,可以使用toString方法转为字符串 * */ fs.readFile('./static/file/a.txt', 'utf-8', (err, data) => { if (err) return console.log(err); console.log(data.toString()); }); /** * 写入文件:fs.writeFile(file, data[, options], callback) * 1.默认为覆盖模式 * 2.文件不存在会自动进行创建 */ let content = "bbb"; fs.writeFile('./static/file/b.txt', content, 'utf-8', err => { console.log(err); }); /** * 追加形式写入文件:fs.appendFile(file, data[, options], callback) * 文件不存在也会自动创建 */ fs.appendFile('./static/file/b.txt', content, 'utf-8', err => { console.log(err); }); /** * 文件重命名:fs.rename(oldName, newName, err => {}) */ fs.rename('./static/file/b.txt', './static/file/bb.txt', err => { console.log(err) }); /** * 删除文件:fs.unlink(path, (err) => {}) */ fs.unlink('./static/file/bb.txt', err => { console.log(err) });
path:路径操作模块
- 相对路径
js文件中的相对路径是相对执行node命令时所在目录的路径,而不是相对js文件所在的路径,如有
data.txt
和data.js
,二者在同一目录下,如果data.js
中需要引入data.txt
,不能直接写./data.txt
,而是要看node *.js
文件在哪个目录下,以该路径为相对路径的标准
- 引入
const path = require('path'); /** * 路径拼接:path.join(path1, path2, ...) * 可以自动识别系统路径符号,并自动进行转换 */ let filePath = path.join("/a", "/b/", "c/", "./d/e", "f.txt"); console.log(filePath); // \a\b\c\d\e\f.txt /** * 获取文件名:path.basename(path[, ext]) * ext:文件名后缀,如果存在该参数,则输出不带后缀的文件名 */ console.log(path.basename(filePath)); // f.txt console.log(path.basename(filePath, 'txt')); // f. console.log(path.basename(filePath, '.txt')); // f /** * 获取文件目录:path.dirname(path) * 获取文件后缀:path.extname(path) */ console.log(path.dirname(filePath)); // \a\b\c\d\e console.log(path.extname(filePath)); // .txt
http:服务器操作模块
- 服务器构建基本步骤
//1.引入http模块 const http = require('http'); //2.创建服务器实例 const server = http.createServer(); //3.绑定事件,监听并处理请求 server.on('request', function (req, res) { //... /** * 请求相关API * * req.url:获取请求地址 * req.method:获取请求方式 * req.headers:获取请求头(返回对象格式) * req.rawHeaders:获取请求头(返回数组格式) */ /** * 响应相关API * * res.write("设置相应体") * res.end():表示响应完成,即响应结束标识,必须有 * res.end("<h1>相应体</h1>"):end方法一样可以用来设置相应体,不过只能用一次 * res.setHeader("content-type", "text/html;charset=utf-8"):设置响应头,注意要放在write前面 */ }); //4.设置端口,启动服务器 server.listen(56789, function () { //... });
- 不同请求路径返回不同的页面
const fs = require('fs'); const path = require('path'); const http = require('http'); const server = http.createServer(); server.on('request', (req, res) => { // res.setHeader('content-type', 'text/html;charset=utf-8'); 此处不能写死,因为可能是css等其他文件 // res.setHeader('content-type', mime.getType(req.url)); 使用mime模块设置对应的mime类型 if (req.url.startsWith('/index.') || req.url === '/'){ fs.readFile(path.join(__dirname, 'pages', 'index.html'), (err, data) => { if (err) { console.log(err.message); res.statusCode = 500; res.end("error"); } res.end(data); //注意,不要调用toString() }); //res.end("abc") 输出的将不是data而是此abc,原因不是end方法会覆盖,相反end方法只会执行一次,其真正的原因是readFile是异步方法 } else if(req.url.startsWith('/one')) { fs.readFile(path.join(__dirname, 'pages', 'one.html'), (err, data) => { if (err) { console.log(err.message); res.statusCode = 500; res.end("error"); } res.end(data); //注意,不要调用toString() }); } else { res.statusCode = 404; res.end("error"); } }); server.listen(56789, function () { console.log("服务器已启动:localhost: 56789") });
- 获取请求参数
- 获取 get 请求参数
const http = require('http'); const server = http.createServer(); const url = require('url'); server.on('request', (req, res) => { /** * 获取形式:由于只会有少量的数据,所以是一次性快速传递完成 * 得到的参数格式为: * { * param1: value1, * ... * } */ if (req.url.startsWith('/index.') || req.url === '/'){ let params = url.parse(req.url, true).query; res.write(JSON.stringify(params)); } res.end(); }); server.listen(56789, function () { console.log("服务器已启动:localhost: 56789") });
- 获取 post 请求参数
const http = require('http'); const server = http.createServer(); server.on('request', (req, res) => { /** * 获取形式:数据量较大,所以会将数据分成若干的小块,一块一块的上传,后台会先将接收到的小块放到缓存区,待传输完成后再将所有的小块拼接成原数据 * 得到的参数格式为: * { * "param1": "value1", * "param2": "value2" * } */ let params = ''; req.on('data', function (chunk) { //每当有一小块数据上传过来时,此事件就发触发 params += chunk; }); req.on('end', function () { //当数据传输结束时触发此事件 console.log(params); }); res.end(); }); server.listen(56789, function () { console.log("服务器已启动:localhost: 56789") });
mime:mime类型模块
- content-type
在后台返回给前端文件或数据时,最后指定该文件或数据的类型,指定方式通过
content-type
来指定,如果没有指定,浏览器会进行自动识别,而一旦识别出错将引发告警,乃至前端无法按正确的类型加载该文件。
常见的类型有
text/html
:html文件text/css
:css文件video/mp4
:mp4文件image/png
:png图片image/jpg
:jpg图片
- mime类型
全称 Multipurpose Internet Mail Extensions,指多用途Internet右键扩展类型,是一种表示文档性质和格式的标准化方式,浏览器确定如何去处理文档,通常使用的就是MIME类型,而非文件扩展名,故而服务器将正确的MIME类型添加到响应头就显得非常重要
- 安装
npm i mime //mime是第三方模块,非node核心模块,所以需要先进
- 引入和使用
const mime = require('mime'); mime.getType("a/test.jpg"); //image/jpg,输入文件名,获取mime类型 mime.getExtension('image/jpg'); //jpg,输入mime类型,获取文件扩展名
npm:基于node的包管理工具
- 组成
- 网站:npmjs.com
一个开发者查找包、设置参数以及管理npm的主要途径
- 注册表:registry
一个巨大的数据库,保存了每个包的信息
- 命令行工具:CLI
一个通过命令行或终端运行,和npm打交道的工具
- 初始化
npm init
,初始化主要有以下两大作用
- 会生成一个pakeage.json文件,它会将开发中所要用到的包,以及项目的详细信息等进行记录,方便版本迭代和项目移植
- 在进行项目传递的时候不需要将项目依赖包一起发送给对方,对方在接受到项目之后执行npm install就可以将项目依赖全部下载到项目里
- 包的安装和卸载
<script> /** * 常用安装命令: * 1. npm install 包名 * 2. npm install 包名@版本号 * 3. npm i 包名 * 4. npm install * 5. npm i * 说明:以上的i即为install的简写形式,当项目中存在package.json文件时,使用npm i / npm install将会自动下载并安装所需依赖包 * 注意:安装成功后,会生成一个package-lock.json文件,该文件中说明了下载的依赖包的版本和下载来源url * 如果安装失败,可以使用npm cache clean [-f],[强制]删除npm的缓存 */ /** * package.json字段说明 * name:包名 * version:包版本 * description:包描述信息 * main:入口文件(默认index.js) * scripts:配置的一些脚本(如vue中会用到) * keywords:当将包上传到npm网站时的搜索关键词 * author:作者 * license:许可证,开源协议,默认ISC * dependencies:项目的依赖包 */ /** * 全局安装: * npm i -g 包名 * 说明:非全局安装智能在当前项目下使用,全局安装则所有地方都可以使用,且可以是作为命令行工具使用 * 全局原理:C:\\Users\username\AppData\Roaming\npm此目录已被node放入到环境变量中,而全局安装的包都被安装到了该路径中 * npm i -g less //全局安装less * less a.less a.css //命令行使用 */ /** * 删除 * npm uninstall 包名 */ </script>
- nrm:npm仓库管理工具
- 安装
npm i -g nrm
- 显示可用的npm仓库地址列表
nrm ls
- 切换npm仓库
nrm use taobao
modemon:包自启工具
- 作用
可以监听js文件,当有修改动作时将会自动重启node程序
- 安装
npm i -g nodemon
- 使用
nodemon xxx.js; //建议关闭文件的自动保存功能
art-template:模板引擎
在node后台使用artTemplate模板引擎,大体步骤如下
/* 安装:npm i art-template 引入:const template = require('art-template'); 步骤1:准备模板(不需要script标签,整个html文件作为模板) 步骤2:准备数据(同前端,要求是个对象) 步骤3:绑定数据和模板(template('html模板文件路径', 数据对象)) */
queryString:查询字符串解析模块
const queryString = require('queryString');
/*
queryString.parse('queryString', '两个键值对之间的连接符,默认为&', '键值对连接符,默认为=')
*/
queryString.parse('id=1&name=zhangsan');
url:url解析模块
const url = require('url');
/*
url.parse(urlStr, parseQueryString)
urlStr:待解析的url
parseQueryString:是否将得到的查询字符串解析为对象,默认false,即否
返回值:对象,包括了协议、query(查询字符串)等
*/
express:基于node的web开发框架
- 基本使用与处理get请求
const express = require('express'); //1. 创建服务 const app = express(); //2. 路由 //2.1 处理get请求 app.get('/index', (req, res) => { res.send("首页"); //等价于res.setHeader('content-type', 'text/html;charset=utf-8') + res.end("首页") 并设置了 utf-8 编码 }); //3. 启动服务 app.listen(56789, () => {console.log("服务已启动")});
- 其他几种处理请求的API
const express = require('express'); const app = express(); //post:处理post请求 app.post('/index', (req, res) => { res.send("首页"); //等价于res.setHeader('content-type', 'text/html;charset=utf-8') + res.end("首页") }); //all:可以处理所有类型的请求 app.all('/all', (req, res) => { res.send("所有类型的请求"); }); //use:可以处理所有类型的请求,且可以处理该接口路径开头的所有请求 app.use('/use', (req, res) => { console.log(req.url); //localhost:56789/use/aa/bb --> /aa/bb //localhost:56789/useAaBb --> 404 res.send("以use请求开头的接口都可以"); }); app.listen(56789, () => {console.log("服务已启动")});
- req的扩展
const express = require('express'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.urlencoded({extended: true})); //注意处理的是post请求的 x-www-form-urlencoded 类型参数 app.use('/', (req, res) => { //路由为'/'时,可以省略掉 //req.query:get请求参数,得到一个对象 //http://localhost:56789/all?name=xiaoming&age=12 --> { name: 'xiaoming', age: '12' } console.log(req.query); //req.body:post请求参数,默认为undefined,当值存在时,可以使用如 body-parser 进行解析 //app.use(bodyParser.urlencoded({extended: true})):解析后就是个对象了 //extended:true表示使用qs库进行body解析,qu库是个第三方库,功能比默认方式更加丰富 //extended:false表示使用querystring库进行解析,是一个官方提供的模块,功能较弱 console.log(req.body); res.end(); }); app.listen(56789, () => {console.log("服务已启动")});
- res的扩展
//设置状态码 res.status(200); //相当于 res.statusCode = 200 //设置响应头 res.set("content-type", "application/json"); //相当于res.setHeader() //重定向 res.redirect(); //相当于res.statusCode = 302; res.setHeader("location", "/index"); //返回文件给浏览器 res.sendFile(path.join(__dirname, "pages/index.html"));
- 静态资源托管
通过静态资源托管,只需配置静态资源的存放路径即可,无需再去设置mime类型以辨别静态资源类型
const express = require('express'); const app = express(); app.use(express.static("pages")); //此处省略了'/' /* 注意此时虽然index.html存放在 ./pages 目录下,但是请求时输入的不是/pages/index.html,而是/index.html 因为已经进行了托管,static("pages")中的路径pages相当于就成了根路径 可以通过在use中配置路径进行解决:app.use("/pages", express.static("pages")) */ app.listen(56789, () => {console.log("服务已启动")});
- express中使用模板引擎
- 安装依赖
npm i express npm i art-template npm i express-arg-template
- 准备模板
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{title}} {{each data value index}} <li>{{value}}</li> {{/each}} </body> </html>
- 绑定使用
const template = require('express-art-template'); const express = require('express'); const app = express(); //app.engine(模板的后缀, 模板引擎):配置使用的模板引擎 app.engine('html', template); //模板存储位置默认是./views,这里设置为./pages app.set("views", "./pages"); app.use("/", (req, res) => { //render(模板的位置, {数据}):执行渲染数据,无需再执行res.send等操作 res.render("temp.html", { title: "科目", data: ["java", "python", "web"] }) }); app.listen(56789, () => {console.log("服务已启动")});
- express中间件的使用
- 中间件介绍
一个函数,可以访问请求对象req,以及响应对象res,如此就可以给这两个对象设置一些自定义的属性或方法,最后通过next方法将属性或方法传递给下一个中间件,实现了功能扩展
- 定义一个中间件
const express = require('express'); const app = express(); app.use('/index', function (req, res, next) { req.name = "xiaoming"; res.say = function () { return "hello, my name is "; }; next(); //传递给下一个中间件 }); app.use( "/", (req, res) => { console.log(res.say() + req.name); //hello, my name is xiaoming res.send(); }); app.listen(56789, () => {console.log("服务已启动")});
- express的外置路由
router.js
const express = require('express'); const router = express.Router(); router.get('/index', (req, res) => {res.send("首页")}); router.get('/login', (req, res) => {res.send("登录页")}); router.get('/detail', (req, res) => {res.send("详情页")}); module.exports = router;
index.js
const express = require('express'); const router = require('./router'); const app = express(); app.use(router); app.listen(56789, () => {console.log("服务已启动")});