Node学习笔记

简介

  • 特点
  1. 单线程
  2. 非阻塞I/O
  3. 事件驱动

当客户端请求建立连接/提交数据等行为,都会触发相应的事件,而且同一时间只能执行一个事件处理函数,不过可以在执行一个事件处理函数之时,转而执行其他事件处理函数,类似于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中模块的分类
  1. 核心模块

node自带的模块,如fs、path、http、url、queryString等

  1. 第三方模块

需要先进行下载安装的模块,如mime、art-template等

  1. 自定义模块
  • 模块查找规则
首先,检查模块名称中是否含有路径,有则判定为自定义模块,并到该路径中加载文件
如果不含有路径,首先当做核心模块,并到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对象,是个全局对象,可以在任何地方直接使用,且此模块无需进行引入

  • 常用属性/方法
  1. console
global.console.log("hello"); //可以省略global,功能同window的console
  1. 定时器、延时器
  2. __dirname
console.log(__dirname); //输出当前文件所处目录,注意不要写成global.__dirname这样会被解析成一个自定义的全局变量,结果为undefined
  1. __filename

输出当前文件路径 + 文件名称

  1. module

对当前模块的一个引用

  1. 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.txtdata.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,初始化主要有以下两大作用

  1. 会生成一个pakeage.json文件,它会将开发中所要用到的包,以及项目的详细信息等进行记录,方便版本迭代和项目移植
  2. 在进行项目传递的时候不需要将项目依赖包一起发送给对方,对方在接受到项目之后执行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中使用模板引擎
  1. 安装依赖
npm i express
npm i art-template
npm i express-arg-template
  1. 准备模板

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>
  1. 绑定使用
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("服务已启动")});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值