node-Api文档
Node 是一个软件, 可以脱离浏览器, 独立执行JS文件
node特点:单线程,跨平台,非阻塞,事件驱动+回调函数
Node实现了ECMAScript标准, 所以语言跟JavaScript基本一致因为想成为全栈开发(前后端都可以的大拿)
PHP等语言是阻塞的, 性能不如nodeJS
node是事件驱动, 非阻塞的
Node中是大量的, 事件和回调函数
单线程(主线程单线程,后台I/O线程池)
跨平台 (Node软件可以安装在window系统/linux系统/mac系统/等等)
1. 开发环境: 指编写代码, 时用到的环境,包含调试工具,打包工具等
2. 生产环境: 指发布到线上后的环境
安装环境
nodejs的基础语法: js+npm模块管理器
核心模块: 文件系统IO, 网络模块, 加密, 数据流
框架: express4.x, express-generator, socket.io, koa2
数据库: mysql
运行环境: win, mac, linux, unix, 只要安装了node环境
io指的是对磁盘的读写操作(input输入和output输出)
Node仅仅对ES标准进行了实现,所以在Node中不包含DOM 和 BOM
但是Node实现了 console 和定时器(setTimeout() setInterval())从官网下载.msi 安装包, 双击默认下一步安装即可!
.msi: windows的安装程序(建议) 会自动配置环境变量, 在环境变量中, 当使用.msi安装后,在path中会自动添加 D:\软件\node\ 路径
二进制文件: 直接运行node命令了, 自己配置环境变量
运行命令 > node -v 显示版本号即为安装成功
注意: 环境变量修改后, 一定重启cmd
cmd终端作用: 用于操作计算机
cmd终端中的命令:
cd 可以切换当前终端命令所在目录, 到后面指定的文件夹中
C:\Users\ThinkPad\Desktop> 代表你现在所处的文件夹
如果不会cd命令
先进入到目标文件夹下, 在上面输入框中敲击cmd回车, 也可以打开当前路径的终端窗口
cls (windows) clear (mac) 清空当前终端
路径/命令, 先敲击开头的1-3个字符, 然后用tab键补全, 多次按tab会切换可以使用的选项
启动命令行窗口: 开始菜单->搜索框->输入cmd->回车
命令行窗口(CMD): 其实就是用命令, 操作我们的电脑..
cd: 进入到指定目录下
cls: 清屏
start: 启动文件资源管理器 (start .)
dir: 显示当前文件夹下所有文件和文件夹信息
md: 创建一个文件夹
rd: 删除一个文件夹
mkdir: 新建一个文件夹
. 代表当前路径
.. 代表上一级文件夹路径
技巧: 快速的在终端切换到目标文件夹下, 先打开目标文件夹, 在当前目录下 shift+右键->在此处打开命令窗口
使用node命令, 可以直接执行一个js文件.(证明: 脱离浏览器)
js文件代码: console.log('hello, nodejs'); , 用node命令来执行此js文件查看效果
注意: 文件,不要叫node.js, 因为在执行node命令时, 会寻找node命令文件, 冲突
打开cmd终端
终端目录, 切换到目标js文件所在目录
node js文件名
回车执行, 查看结果
Node执行的js脚本文件中, 我们的顶层对象不再是window了, 而是global对象
console.log(global);
__filename: 当前模块文件的绝对路径 (带文件名)
__dirname: 当前模块文件夹的绝对路径console.log(__filename);
console.log(__dirname);
计算机中, 数据都是以2进制(0, 1)传输
二进制: 0 1
八进制: 0 1 2 3 4 5 6 7
十六进制: 0 1 2 3 4 5 6 7 8 9 A B C D E F (大小写均可)
几进制: 逢几进位
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
// 前端Javascript里, 装载数据
// 数组装载: 字符串, 数字, 布尔值, 数组, 对象, set, map, Symbol, Error, undefined, null (装载不了二进制数据)
// nodejs中, 定义了一种专门存储二进制数据的容器类型, 叫Buffer (类似于数组Array) (Buffer表现打印的时候, 是以16进制, 显示的二进制数据)
//===示例=====
// 1. Buffer.from() 把字符串转换成Buffer二进制数据
let buf = Buffer.from('豪子');
console.log(buf);//
//(buffer打印不会以0和1打印, 而是优化后, 以16进制数打印出来)
// 2. Buffer和Array很像, 都有索引下标来使用
console.log(buf[0]); //e8
console.log(buf[1]); //b1
console.log(buf[2]); //aa
// let arr = Array.from(buf)
// console.log(arr) //[ 232, 177, 170, 229, 173, 144 ]
// 3. 文件里的内容是由二进制组成, 需要用Buffer来接收, 输出到控制台上, 我们想看的不是二进制数据, 而是想看UTF-8编码的字符串 (需要把Buffer -> 字符串)
// .toString()
let str = new Buffer([0xe8,0xb1,0xaa,0xe5,0xad,0x90]);
console.log(str.toString(),buf.toString());
// 注意: [] 是单纯的数组, 是不能装在二进制数据的, 即时装了也会被转换成10进制展示
// 所以, 需要用Buffer来装我们的二进制数据
// [] 相当于是 new Array()的语法糖
// 想要Buffer数组, 需要new Buffer(), 因为Buffer没有语法糖
// 总结: Buffer是装在文件读出的二进制流数据, 调用.toString()会转换成我们能读懂的字符串
Buffer无需引入模块, 直接使用
Buffer结构和使用, 都与数组类似
在内存中, 数组不能存放二进制数据, 而Buffer则专门用于存放二进制数据
Buffer中的二进制数据, 打印时以16进制显示
常用于文件相关的操作上
Buffer中每一个元素的范围是从00 - ff(十六进制), 对应 0 - 255(十进制) 00000000 - 11111111 (二进制)
Buffer中的一个元素,占用内存的一个字节, 一个英文字符串占1字节, 一个中文占3字节
Buffer的大小一旦确定,则不能修改,Buffer实际上是对底层内存的直接操作
Buffer.from(str) 将一个字符串转换为buffer
length 打印buffer对象长度(其实也是所占内存大小, 单位为字节)
Buffer.alloc(size) 创建一个指定大小的Buffer, 还可以用for循环
可以向数组一样, 往里面每一位赋值, 甚至是取值都可 (但是注意了, 每一位最大范围是255)
buf.toString() 将buffer流中的二进制数据转换为字符串 ([0xe6, 0x99, 0x9a, 0xe4, 0xb8, 0x8a, 0xe5, 0xb0, 0x8f, 0xe6, 0xa0, 0x91, 0xe6, 0x9e, 0x97, 0xe8, 0xa7, 0x81])
模块: 其实类似于前端浏览器里引入的js功能文件, 里面拥有很多属性和方法供我们直接调用
Node.js 核心 API 基于异步操作 例: http模块的createServer会在每次有新连接时触发事件和回调函数
on() : 监听器, 用于将一个函数绑定到自定义事件上
once() : 监听以后, 只会被emit触发一次
emit() : 触发器, 用于触发自定义事件
// 前端Javascript其实也是靠事件驱动的
// click等点击事件 -> 点击时 -> 触发回调函数
// setInterval计时器事件 -> 等待指定时间 -> 触发回调函数
// ajax事件 -> 等待网络请求结果 -> 触发回调函数
// 后端Nodejs 也是靠事件驱动的
// fs读取指定的文件/文件夹 -> 读取事件结果 -> 触发回调函数
// 事件驱动 + 回调函数 -> 形成 非阻塞的代码 (一般情况下这种组合拳适用于异步操作)
// events模块, 就是nodejs中的一个事件模块, 用于自定义事件使用
const EventEmitter = require('events').EventEmitter; // 1. 从events模块中, 引出类 EventEmitter
const ev = new EventEmitter(); //2. 实例化一个事件对象
// 因为其他的类, 内部其实都实现了events模块, 我们要了解事件是如何监听的, 如何触发的
ev.on("dyhck", (result) => { // 3. 监听自定义事件名字, 当事件被触发时, 会开始执行参数2的回调函数
console.log(result);
});
//一次性
ev.once("dyhck", () => {
console.log('我是一次性的'); // 在每次执行完node运行过程中只会执行一次
// 类似于Jquery里的one方法: 刷新一次, 网页代码就会重新执行
// Node每次 node xxx.js文件名, 就算重新执行了一次
})
setInterval(() => {
ev.emit("dyhck", "返回信息"); // 4. 异步操作结果, 靠emit()来触发事件, 同时传递参数(参数是可选的)
}, 3000); // 定时器模拟异步任务(例如文件读取/Ajax)
// on: 监听事件
// emit: 触发事件
let EventEmitter = require('events').EventEmitter;
let emitter = new EventEmitter();
emitter.on("move", ()=>{
console.log("gogogo");
})
emitter.once("move", () => {
console.log("只有一次");
}) // 监听以后, 只会被emit 触发一次
setInterval(()=>{
emitter.emit("move") // 当事件被触发时, 绑定在这个事件上的函数都会被同步调用
}, 1000)
//传参使用
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent', 'arg1 参数', 'arg2 参数');
文件系统简单来说就是通过Node来操作系统中的文件/文件夹
使用文件系统,需要先引入fs模块,fs是核心模块,直接引入不需要下载
Flag | 描述 |
---|---|
r | 以读取模式打开文件。如果文件不存在抛出异常。 |
r+ | 以读写模式打开文件。如果文件不存在抛出异常。 |
rs | 以同步的方式读取文件。 |
rs+ | 以同步的方式读取和写入文件。 |
w | 以写入模式打开文件,如果文件不存在则创建。 |
wx | 类似 'w',但是如果文件路径存在,则文件写入失败。 |
w+ | 以读写模式打开文件,如果文件不存在则创建。 |
wx+ | 类似 'w+', 但是如果文件路径存在,则文件读写失败。 |
a | 以追加模式打开文件,如果文件不存在则创建。 |
ax | 类似 'a', 但是如果文件路径存在,则文件追加失败。 |
a+ | 以读取追加模式打开文件,如果文件不存在则创建。 |
ax+ | 类似 'a+', 但是如果文件路径存在,则文件读取追加失败。 |
// 同步写入
fs.writeFileSync("./fs-01.txt","fs写入内容",{flag:'w',encoding:'utf8'});
//异步方式
fs.writeFile("./fs-01.txt","fs写入内容");
var fs = require("fs"); // 1. 引入fs模块
// 1. require()是全局变量的一个函数, 专门用来引入其他的js文件 相当于 let $ = $;
// 此fs模块(fs.js文件)是安装node环境自带的
// 2. fs模块作用: 用于读取文件, 写入文件, 创建文件夹, 删除文件夹, 检测文件夹 (一个和文件+文件夹 打交道的一个功能文件, 里面有很多的方法供我们使用)
/**
* 2. 向文件内写入内容(异步)
* 参数1: 文件路径
* 参数2: 写入内容
* 参数3: 配置模式: (文件操作标识(a代表追加,如果文件不在则创建, 编码格式)
* 参数4: 回调函数, 异步读取后触发此方法
*/
fs.writeFile("hello3.txt", "这是通过writeFile写入的内容", {flag: "a", encoding: 'utf8'}, function (err) {
if(!err){
console.log("写入成功");
}else{
console.log(err);
}
});
fs.writeFileSync()
fs.writeFileSync("hello3.txt","这是通过writeFile写入的内容",{flag:"a",encoding:'utf8'})
//===fs.readFile()
var fs = require("fs"); // 1. 引入fs模块
fs.readFile("./txt/hello.txt", function (err, data) { // 2. 设置读的文件路径, data读出的buffer数据
if (!err) {
console.log(data.toString());
}
});
//===fs.readFileSync()
fs.readFileSync("./txt/hello.txt").toString()
// 重命名 同步
fs.renameSync("./fs-01.txt","./01-fs改名.txt");
//异步
fs.rename("./fs-01.txt","./01-fs改名.txt");
fs.renameSync("./txt/rename改的文件.txt", "./txt/再改一下.txt")
// 读取文件夹 同步
let arr = fs.readdirSync("./01");
//异步
fs.readdir("./01");
fs.readFileSync('./nodedyh.md',{flag:'r',encoding:'utf8'});// 如果不指定econding, 那么读出来的就是Buffer数组里装载文件里的16进制的数据(底层二进制)
console.log(result.toString());
//删除文件 同步 只能删除文件, 不能删除文件夹
fs.unlinkSync("./01-fs改名.txt");
//异步
fs.unlink("./01-fs改名.txt");
// 创建文件夹
fs.mkdirSync("./01/fs文件夹",{recursive: true});
//{recursive: true} 加上以后, 可以递归创建文件夹(无论嵌套多少层)
fs.mkdir("./01/fs文件夹",{recursive: true});
var fs = require("fs"); // 1. 引入fs模块
fs.mkdir("./dir/gogo", {recursive: true}, (err) => { // 2. 创建文件夹
if (err) console.error(err);
});
// {recursive: true} 设置后, 可以同时创建多层的文件夹
//=====fs.readdir()
let fs = require('fs');
fs.readdir('./dir', function (err,files) {
if(err){
throw err
}
console.log( files )
});
// 删除文件夹 同步
fs.rmdirSync("./01");
//异步
fs.rmdir("./01");
fs.rmdir("./dir/helloDir", (err) => {
if (err) console.error(err);
});
注意: 只能删除空的文件夹, 有隐藏文件也不行
如果要删除文件夹, 需要用代码先遍历里面的一切,删除掉里面的一切, 最后再删除此文件夹
//===fs.stat()===异步
fs.stat("./dir/gogo", (err, stats)=>{
if (err) console.error(err);
console.log(stats.isDirectory()); // 文件夹 -> true
console.log(stats.isFile()); // 文件 -> true
});
//========示例=========同步==
let thePath = "./01";
let thePath2 = "./01/01.txt";
let s = fs.statSync(thePath);// 分析路径, 返回状态对象
// 2个方法(其中一个, 来判断是文件/文件夹)
console.log(s.isFile());// false
console.log(s.isDirectory());// true
const fs = require("fs")
// 1.异步 --- 捕获错误 -- 回调函数里
// (1): 异步代码不会耽误主线程代码继续执行
// (2): 异步中的报错, 都会反馈到回调函数中, 由系统调用回调函数, 传递第一个错误信息数据
fs.readdir("./100", {}, (err, files) => {// files接收成功的结果
console.log(err);
});
console.log("下面代码");
// 2. 同步 --- 捕获错误 --- try+catch
// (1): 主线程遇到错误, 被阻塞了, 直接抛出异常到主线程中, 阻止代码继续向下执行
// (2): try+catch 捕获运行时的错误
try{ // 这里放入可能未来会报错的代码, 如果没放到这里, 是不会捕获到的
// 执行过程: 如果这里有错误, 直接跳转到catch里执行, 然后继续执行主线程代码
// 如果没有错误, try执行完了, 不会触发catch, 而是继续执行主线程代码
let files = fs.readdirSync("./100");
console.log(files);
} catch (err) { // (3): 自己捕获了错误, 主线程就不会阻塞, 继续向下执行
console.error(err);
}
console.log("下面代码还会执行吗?");
在Node环境中,每一个js文件就是一个模块
在Node环境中,每一个js文件中的js代码都是独立运行在一个函数中, 并且不是全局作用域,所以一个模块的中的变量和函数在其他模块中无法访问
分为三步: 模块定义(导出), 模块标识, 模块引用(导入)
// 什么是模块:
// 1. 在Node环境中,每一个js文件就是一个模块
// 2. 在Node环境中,每一个js文件中的js代码都是独立运行在一个函数中, 并且不是全局作用域,所以一个模块的中的变量和函数在其他模块中无法访问
// 3. 分为三步: 模块定义(导出), 模块标识, 模块引用(导入), 使用
// 模块标识分为2种: (模块标识就是模块的名字, 影响require()时, 里面的写法)
// 第一种: 核心模块和npm管理的模块 都是直接写模块名
// 第二种: 自定义模块, 需要写相对路径
const theObj = require("./dyhMod-01"); // 如果后缀是.js可以省略
console.log(theObj.sum(2,3));
console.log(theObj.PI);
const theObj2 = require("./dyhMod-02");
console.log(theObj2.sum(3,3));
//4
// 概念1: 模块之间都是独立的, 如果不导出, 外部是无法直接访问此模块内任何变量
// 概念2: 因为每个模块在运行时, 会被套住一个函数级作用域(为了防止模块之间变量重名)
/*function(__dirname, __filename, require, exports, module) {*/
const PI = 3.1415926;
module.exports = {
PI
}
/* } */
采用CommonJS规范, exports作用, 向外暴露变量和方法, 只将变量或方法设置为exports的属性即可
exports.PI = 3.1415926;
exports.sum = function(a, b){
return a + b;
}
exports.circleS = (r) => {
return (2 * PI * r);
}
// 注意: exports.xxx 是module.exports的语法糖
// 下面这里其实就是一次性给exports直接赋值了, 会覆盖掉上面exports的值 (因为module.exports === exports)
// module.exports = {
// PI,
// sum,
// circleS
// }
exports和module.exports等价的
注意1: module.exports = {}; 会覆盖掉exports上的东西
注意2: module.exports.xxx 则不会覆盖掉exports上的东西
实际暴露外部的是module.exports, 而exports只是辅助工具
口诀: 部分exports, 全部module.exports = {}
// 这是我要定义的一个js模块 (类似于前端定义的工具js文件)
// 封装模块里的方法
const PI = 3.1415926;
function sum(a,b){
return a+b;
}
function circle(r){
return 2 * PI *r;
}
// 1. 准备好属性/方法
// 2. 要导出你想被外部使用的属性/方法 (使用固定的全局变量module来导出东西, 配合exports使用)
// let obj = {
// PI,
// sum,
// circle
// }
// 把当前这个对象 导出出去, 让外部人使用 (此js里的所有属性和方法, 需要导出, 别人才能导入后使用)
module.exports ={
PI,
sum,
circle
};
采用CommonJS规范, require作用, 引入模块里暴露出来的属性/方法供我们使用
1. 核心模块: 可以直接写模块名, 引入
2. 自定义模块: 需要写相对路径 "./开头"
3. npm下载的模块: 可以直接写模块名, 引入require() 中可以传入一个模块标识符(模块名字), 或相对路径
由Node引擎提供的核心模块 (fs模块, events模块 , 还有http, path, url等模块)
由用户自己创建的模块js文件
文件模块的标识就是文件的路径(相对路径)
//=====================使用=================================
// 模块: 定义导出, require导入 (是一个具体的.js文件)
// 模块包: 定义导出, require导入 (导入的是一个文件夹)
// 决定导出的内容, 在package.json中的main属性指定的入口文件, require导入的就是这个入口文件里module.exports导出的内容
// 情况1: 导入一个.js 模块文件 到具体文件
const req = require("./dyhModel/mySum");
console.log(req.mySum(1,2));
//情况2:导入一个 模块包 包含模块的文件夹
const reqObj = require("./dyhModel");
console.log(reqObj);
console.log(reqObj.circle(6));
//=========自定义模块包-dyhModel 文件结构
-dyhModel
-index.js ->入口文件
-circle.js ->模块01
-mySum.js ->模块02
-package.json 配置文件
//========index.js 内容
// 自定义模块包: 其实就是把自己封装的各个功能模块
// 整合成一个模块包文件夹
// 1. 初始化package.json
// 2. 确认入口的.js文件 (index.js)
// 3. 在package.json的main属性上, 设置入口文件(如果不写, 默认就是index.js)
const circle = require("./circle");
const mySum = require("./mySum");
//合并成一个对象 不是对象的加 {}
const obj = Object.assign({},circle,{mySum});
module.exports = obj;
//===========circle.js 内容
const PI = 3.1415926;
function circle(r){
return 2 * PI *r;
}
module.exports ={
PI,
circle
};
//===========mySum.js内容
exports.mySum = function(a, b){
return a + b;
}
//===========package.json 配置信息
{
"name": "dyhmodel",
"version": "1.0.0",
"description": "自定义模块包",
"main": "index.js", //入口文件
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dyh": "npm -version" //自定义命令
},
"author": "by dyh",
"license": "ISC"
}
下面会讲npm 包管理器, 是被npm管理起来的模块
在node中, 有一个全局对象global, 它的作用和网页中window类似,在node中一定要记住是没有BOM和DOM的, 但是模拟了, setTimeout和setInterval等都是global的属性
注意, 在运行时, 每个模块js都会被套上一个这样的函数, 被系统传入5个参数
function (exports, require, module, __filename, __dirname) {
}
exports: 该对象用来将变量或函数暴露到外部
require: 用于引入外部的函数
module: module代表的是当前模块本身, exports最终要挂载在它身上
__filename: 当前模块文件的绝对路径 (带文件名)
__dirname: 当前模块文件夹的绝对路径
(Node Package Manager) node的包管理器
包 -> 一个/多个js模块组成
1.
模块: 就是一个独立的.js文件
(模块)包: 拥有多个封装的js模块文件, 形成的一个文件夹 (我们叫这个文件夹叫模块包)
以后写的node项目文件夹, 其实就是一个大模块包
2.
npm: (Node Package Manager) 主要负责 (上传, 下载, 查询, 删除)我们要使用的包
因为一个项目开发, 需要用到很多的工具, 而我不想写这些现有的工具(时间处理/jQuery/Swiper)
我们想下载这些工具包, npm 就是负责干这个活 (npm 其实也是一个命令)
node环境安装时, 会把npm 一并下载安装, 测试你有没有npm功能: cmd中命令: npm -v (-v是-version的简写)
3. npm使用
初始化+下载
把npm 当成管家, 让它去 下载 需要的模块包
(1) 先要明确模块包的名字 (百度看到的/别人告诉我的/书上写的)
(2) 去npmjs.com网站上, 确认包名以及, 版本号 (确认上次更新/下载量 但是不绝对, 如果找到的是一个偏僻的模块包, 确认名字就行了)
(3) cmd切换到当前文件/当前工程的文件夹中, 因为npm下载都是靠命令来操作的
(4) 要确认当前文件夹中是否拥有, package.json记录文件 (记录你下载了哪些包) (如果没有, 运行npm init命令, 初始化一个package.json记录文件)
Package name (当前模块包的名字, 小括号里的是默认值, 但是不能用中文, 但是可以用-)
version (是当前工程, 项目的版本号)
description 当前工程的一个说明
entry points 当前工程的入口文件 (但是工程没有入口文件, 只有封装的工具包, 需要指定入口文件, 所以这里直接回车)
(5) 在第2步, 确认了安装模块的命令, 直接在cmd中使用即可
npm i jquery
或者用 npm install jquery (i或者install都可以) 作用: 从镜像地址直接寻找jquery, 下载下来
查看:
查看package.json是否记录了你刚下载的模块名和对应的版本号
注意在cmd中, 在哪个目录下, 执行的npm i 命令就会下载到那个目录下
注意: 所有模块包都会把具体的文件夹+文件代码, 下载到node_modules目录中统一管理(node_modules是npm自己创建的, 并非我们自己手动创建)
注意: package-lock.json
功能1: 锁定最大版本号(例如Vesion.1.9.1 最大版本是1, Version3.5.1 最大版本是3, 再次运行npm i xxx模块包名, 会安装最大版本号下的最新版本) 注意: npm i xxx@版本号 可以忽略lock文件
功能2: 加快版本的下载速度
通过npm可以对node中的包进行上传、下载、搜索, 删除)等操作
npm功能会在安装node环境时,自动安装
建议切换成国内的镜像, 下载的快, 命令: npm config set registry https://registry.npm.taobao.org (彻底修改, 以后都不用再执行此命令了)
使用命令 npm config list 查看registry选项, 是否变成了taobao.org的地址npm下载的包搜索和教程
npm -v 查看npm的版本
npm init 初始化项目(创建package.json), 需要用户输入项:
ctrl + c 可以随时终止终端里正在执行的命令
npm init -y: -y的意思代表所有项都使用默认值, 如果报错了, 则执行失败,还得用npm init 自己来一个个输入 (建议大家, 当前文件夹不要起中文和特殊符号)
所有项, 直接回车, 默认使用括号里的值/空
输入项 | 含义 | 备注 |
---|---|---|
package name | 包名字 | 默认所在文件夹名,不能包含特殊符号和中文 |
version | 版本号 | 默认1.0.0, 包每次更新都会有版本号增加 |
description | 描述说明 | 默认空字符串 |
entry point | 包入口 | 默认文件夹下的index.js或者其他js文件 |
test command | 测试命令 | 默认空字符串 |
git repository | 项目git地址 | 默认空字符串 |
keywords | 关键词 | 默认空字符串, 用于别人搜索包 |
author | 包作者名 | 默认空字符串 |
license | ISC开源许可字符串 | 默认ISC |
产物讲解
package.json: 包的重要组成部分, 包含了npm相关的所有信息, npm自定义命令, 列出项目需要的所有包的名字和对应版本 (给别人传npm管理的项目, 不要传node_modules, 而是要传带package.json文件的整个项目源码)
如果npm install 下载了一个第三方包以后, 会额外产生一个文件: package-lock.json
package-lock.json: 是npm5以后引进, 负责锁定最大版本号, 防止不同项目出现不同的包版本.而且记录了下载地址, 加快npm下载 (无法阻止指定版本的安装)
补充package.json里字段解释:
scripts里面配置的命令, 是给npm扩展的自定义命令, 只能在package.json文件所在的目录使用
npm (run) xxx; 执行script里配置的 自定义命令 run可写可不写
输入项 | 含义 | 备注 |
---|---|---|
scripts | 指定npm可以运行的命令 自定义命令 | 自定义命令 |
dependencies | 发布环境依赖的 第三方模块包名 | 写完项目线上环境 |
devDependencies | 开发环境使用的 第三方模块包名 | 本地开发时候环境 |
npm i/install 包名 ----- 安装包 默认安装最新版本 (/代表或者)
npm i/install 包名@版本号 ----- 安装包 安装指定版本号的包
http://npmjs.com查找你要下载的包的版本, 注意周的下载量, 如果很少可能你找错了, 一定要知道有的版本号, 你才能正确下载
注意@指定的版本, 不受package-lock.json的 锁定
npm i/install 包名 --save ----- 安装包 添加依赖到dependencies列表中(不写默认就是这个)
npm i/install 包名 --save-dev ----- 安装包, 添加依赖到devDependencies列表中
npm i/install 包名 -g ----- 全局安装包(前提: 工具类(express-generator)的包安到全局, 普通包不要安到全局)
npm i/install ----- 安装当前项目里package.json文件记录的所有包名, 对应的包(不要传递node_modules因为它很大, 所以别人拿到带package.json文件的npm管理的项目, 先执行npm install来下载所需要的所有第三方包(包名都在package.json中记录着)
例如: cnpm来替代npm 安装我们的模块: 命令: npm install -g cnpm --registry=https://registry.npm.taobao.org (注意和我们逐级查找模块要区分开)
npm s/search 包名 ----- 搜索包 (必须在官方镜像下)
npm uninstall 包名 ----- 卸载哪个包 或uni
npm r/remove 包名 ----- 删除一个包
npm config list ----- 查看npm配置信息(可以看全局npm安装位置, 镜像地址)
这是一个时间格式化的包, 可以去https://www.npmjs.com/ 找到你想要的包, 和使用文档
包的使用: 下载 & 引入 & 使用
const timestamp = require('time-stamp'); // 引入包
console.log(timestamp());// 2019-02-13
console.log(timestamp.utc());// 2019-02-13
//自定义时间格式
console.log(timestamp('YYYYMMDD')); // 20190213
console.log(timestamp('YYYY年MM月DD日')); // 2019年02月13日
下载好的包都在node_modules, 可以应用在当前文件夹, 和所有的子文件夹项目中
npm i xxx -g 只能安装命令行工具包, 到 C:\Users\ThinkPad\AppData\Roaming\npm\node_modules\ (如果你电脑里没有找到AppData, 它可能是一个隐藏文件夹) (就是可以直接在终端使用命令的工具包)
npm i xxx 普通安装的模块包, 没有全局的概念, 只有父级的概念, 当前工程中没有找到require要引入的第三方包名, 就会一直向父级查找, 直到根目录为止, 如果还没找到就抛出错误 (打印console.log(module) 查看path)
npm内置的命令: npm init 、 npm install .....
npm的自定义命令:
(1): 在"当前目录"中的package.json中的 scripts里自定义命令的名字, 和对应要执行的代码/命令
(2): 执行自定义命令 用 npm run 自定义命令的key名
(3): 实际上, 在终端中还是执行了右侧value的代码/命令
使用场景: 可以给很长的命令, 配置一个简短的自定义名字
//=====示例===========
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dyh": "npm -version"
}
可以查看全局安装的模块的位置, 但是要注意, 只有工具模块才可以生效
C:\Users\ThinkPad\AppData\Roaming\npm\node_modules 全局包(模块)安装的地方注意: 1 npm需要有node环境才能使用哦
2. npm其实也是一个模块包, 只不过它被配置了命令行命令来使用 位置: D:\软件\node\node_modules\npm
3. npm i -g 安装的带命令行的模块包, 都会部署到 C:\Users\ThinkPad\AppData\Roaming\npm下, 然后连接到电脑的cmd终端中, 直接调用命令行
在一个入口IP中, 设置的分发网络, 可以让多台设备在这个小网络中共用这个入口IP进行上网
局域网的IP只在局域网内有效 (也叫内网, 上的外网叫互联网)本地:localhost == 127.0.0.1 本地环回地址
通过ip地址找到对应的服务器计算机,但是服务器上是有很多功能的,比如网页服务, 数据库服务, 邮箱服务, 通信服务, 自定义服务等等, 每个应用程序服务利用端口号, 加以区分
ip + 端口 确认你访问的功能
- web网页 --- 80
- mysql服务 --- 3306
- mongodb服务 --- 27017
- ftp服务 --- 21
- smtp服务 --- 25 等等 以上端口不是固定的, 可以随意更改, 只是默认
注意:
0-1023: 系统端口,有系统特许才能使用 (node拿到权限后也可使用)
1024~65535为用户端口:
1024-5000: 临时端口,一般的应用程序进行通讯
5001-65535:服务器(非特权)端口,给用户自定义端口
浏览器 -> 输入网址 -> 服务器响应 -> 返回HTML等资源内容 (这也是上网的过程)
网页访问过程:
注意: 浏览器上什么都没有, 都是通过网址来访问, 网址对应的服务器会返回内容给浏览器渲染展示
本地双击html文件运行, 无法查看cookie等, 找不到相对的资源
也就是说, 浏览器-> 根据url -> 访问服务器上资源 -> 拿到数据 -> 根据规则展示 给你看
浏览器: 就是用于请求&展示 的一个工具而已以前访问一台服务器电脑, 需要使用ip地址, 但是ip地址是4段数字, 而且很难记忆, 所以才出现了域名这个东西
域名工作的过程: 域名 -> IP地址, 你访问域名 实际上访问的就是它所对应的ip地址
例如: 百度ip: 39.156.69.79 -> baidu.com
例如: 本地电脑 ip: 127.0.0.1 -> localhost (固定)
注意: 域名也需要购买 和申请及备案, 默认所有的域名都应该在DNS中配置域名解析
Apache (lampp/xmpp/xampp) --- 一般PHP后台代码, 运行的环境在Apache上
Tomcat --- Java代码 - 运行在Tomcat上
Node --- node.js语言创建的web网页服务
拥有web网页服务功能的计算机, 可以叫做web服务器, 也可以叫http服务器
这里, 借助Node.js的核心模块(安装Node就带了), http模块, 来创建web网页服务
web服务器/http服务器 -> 这台计算机服务器中, 有web网页服务功能, 可以提供浏览器要访问的资源
// 在Node环境上, 使用node.js借助http模块, 来搭建一个web网页服务
// 1. 引入http模块(核心模块, 安装完Node自带的, 跟fs, events等一样都是核心)
const http = require("http");
// 2. 调用方法生成一个web网页服务对象
const setver = http.createServer((req, res) => {
// resquest: 请求对象 (里面有很多的属性和方法) -> 包含的是浏览器发送过来的一些信息
// request.url: 是浏览器要请求的url地址
// response: 响应对象 (....................) -> 可以给浏览器返回/设置一些信息
// res.writeHead() 代表响应时, 写入头部的信息(告诉浏览器要怎样处理我给你的数据)
res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});// 写入响应头部内容给浏览器
// Content-type: 内容类型, text/html html文本类型, 这些文本都以utf-8编码展示
// 200 状态码, 告诉浏览器, 你本次请求是成功的 (如果是, 404失败的)
//打印 请求地址和方式
console.log(req.url,req.method);
// 3. 此回调函数, 是当浏览器请求这个node的web网页服务时, 会触发此函数执行
console.log("浏览器发起了一次http请求");
res.end("{hello:'node-http-你好dyh'}");// 给浏览器响应(返回)一个内容, 并结束本次响应
// res.end("htllo-你好dyh");// 我们没有响应返回给浏览器, 告诉浏览器以什么方式编码解析网页上的中文 标签是作为字符串, 返回给浏览器了, 浏览器去解析这些标签字符串
}); 1
// 4. 给web网页服务, 分配端口号
setver.listen(3000);// 范围 1024 - 65535 (尽量用1024以后的, 以前的需要设置管理员权限)
// 5. 启动web网页服务
// (1): cmd切换目录, 执行node 01_http模块使用.js 启动此文件代码
// (2): 此终端不能关闭, 因为此时web网页服务 已经启动起来了, 终端里一直等待浏览器来访问这个web网页服务
// 注意: 每次修改完node代码, 必须重启后, 才生效 (以为得让node命令重新执行一下你新写的代码)
// 注意: (red.end())响应回给浏览器的内容, 必须是字符串/Buffer类型, 其余类型都不可以
// 注意: 如果打开多个node的web网页服务, 注意端口号不要冲突了(不要重复)
// 重启: ctrl+c先停掉, 然后再nonde xxx.js 重新执行一下
前提是一个http服务器, 请求对象和响应对象都是在服务器上的变量
浏览器 -> 请求 -> 服务器, 在服务器端会得到一个请求对象, 而且可以通过响应对象, 对浏览器设置一些响应内容
统一资源定位符, 上网其实就是输入网址, 网址就是url, 从互联网上得到的资源的位置和访问方式。寻找目标资源使用的一串字符串, 而且互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么访问它
资源: 视频/音频/html/txt/js/css等等文件/字符串
例如: 动物住址协议://地球/中国/浙江省/杭州市/西湖区/某大学/14号宿舍楼/525号寝/张三.人
例如: http://106.13.114.114:3000/biyao/public/static/image_source/goods_loop_img/Koala.jpg?firstid=901&secondid=100#hello
总结: url就是一段唯一的字符串, 用于在茫茫互联网中准确找到你想访问的资源 url = 网址
解析&& 格式化浏览器端, 发送过来的req对象
parse()
格式化url字符串
url.parse(https://sub.example.com:8080/p/a/t/h?query=string#hash)
{
protocol: 'https:',
slashes: true, // http后面跟着2个//就是true
auth: null, // 域名前面的用户名和密码
host: 'sub.example.com:8080',
port: '8080',
hostname: 'sub.example.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'https://sub.example.com:8080/p/a/t/h?query=string#hash'
}
//================url=====================
// 1引入 http模块
const http = require("http");
// 引入url模块来格式化req.url (作用: 负责提取get参数)
const urlObj = require("url");
// 调用方法 创建web服务
const server = http.createServer((req, res) => {
// 浏览器 -> 发起的请求 -> 请求对象中, 包含了浏览器发送过来的数据
if (req.url !== "/favicon.ico") {
console.log(req.url);
console.log(urlObj.parse(req.url));
// 因为req.url的值没有http还有ip和端口号, 所以格式化出来的对象中, 才显示null
}
res.end();
});
server.listen(3000);
// 1. 浏览器请求 http://127.0.0.1:9000 其实默认有一个/ 代表访问后台的根路径(目录)
// 2. req对象上, 包含了浏览器发起请求时, 的一些参数
// 在浏览器的地址栏中: 写的东西叫url/统一资源定位符/网址/地址
// url起到了一个, 在茫茫互联网中, 能准确的定位到某一个资源的位置 (url不会重复的, 定位到了浏览器想访问资源的完整路径)
// url例如: http://106.13.114.114:3000/biyao/public/static/image_source/goods_loop_img/Koala.jpg?firstid=901&secondid=100#hello
// url组成:
// (1) http:// 这部分叫做请求使用的 协议(规则), 叫http协议(超文本传输协议)
// (2) // ... : 之间这部分叫ip/域名, 作用: 是确定要访问的那台服务器地址
// (3) :3000 叫做端口号 作用: 访问这台电脑里的什么服务
// (4) /biyao/public/static/image_source/goods_loop_img/ 叫做路径 作用: 去服务器端在:3000端口的服务下, 去寻找这个路径下的资源
// (5) Koala.jpg 文件名 作用: 确定这个资源的文件名 (可以省略的)
// (6) ?firstid=901&secondid=100 要传递的提交的参数, 传给前面这个url(/后端)
// (7) #hello 锚点连接 作用: 附加上额外的参数 (一般只用在前端网页上, 第四阶段前端的路由vue中的路由使用的就是锚点连接)
url.parse(req.url, true)['query'];
// 总结:
// 路径决定想要的资源/功能
// 参数是传递额外数据
// 例如: 查找图书id为10086的 /find/book?id=10086
const http = require("http");
const urlObj = require("url");
const server = http.createServer((req,res)=>{
if(req.url === "/favicon.ico")res.end();//单独过滤小图标请求
// 设置响应头
res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
// console.log(urlObj.parse(req.url,true)['query']);
let queryObj = urlObj.parse(req.url,true)['query'];// 把请求对象里的url路径格式化一下 (参数2的意思, 是否让parse方法把query字符串格式化成对象)
switch(queryObj.dyh){// 注意前后端交互的都是字符串类型
case '1':res.end("请求:dyh="+queryObj.dyh);break;
case '10':res.end("请求:dyh="+queryObj.dyh);break;
case '100':res.end("请求:dyh="+queryObj.dyh);break;
case '1000':res.end("请求:dyh="+queryObj.dyh);break;
default : res.end("请求内容错误");
}
});
server.listen(3000);
前端浏览器发起的请求, 路径也可以看错是一个参数, 具体返回什么内容, 得看后端的判断和具体的逻辑代码以及res.end()
const http = require("http");
const urlObj = require("url");
const fs = require("fs");
const server = http.createServer((req, res) => {
//将请求过来的数据 格式化成对象,解构赋值给pathname
// console.log(urlObj.parse(req.url, true));
let { pathname } = urlObj.parse(req.url, true);// query只解析get参数?后面的, pathname接收路径参数
if(pathname === '/www/dyh/mydyh.html'){
// 注意: 后台要把文件返回给前端, 就需要先读取出内容作为字符串, 返回给前端浏览器, 让浏览器加载这些html文件内的字符串 (.html/.css/.js都是字符串内容)
console.log(fs.readFileSync("./www/index.html",{encoding:"UTF-8"}));
res.end(fs.readFileSync("./www/index.html",{encoding:"UTF-8"}));
// res.end(fs.readFileSync("./www/dyh/mydyh.html",{encoding:"UTF-8"}));// 这里是文件的相对路径
}
});
server.listen(3000);
// 自己写的node的web网页服务, 需要你自己来返回html文件里的内容, 响应回给浏览器显示
path 模块, 用于处理文件路径和文件夹路径的, 实用工具
//模拟个文件路径
const thePath = "/dyh/home/index.html";
const pathObj = require("path");
console.log(pathObj.parse(thePath));
/**提取文件夹+文件路径的 某一部分, 记得用path模块
* {
root: '/', 从什么开始 / 根路径
dir: '/dyh/home', 文件夹部分
base: 'index.html', 完整的文件名部分
ext: '.html', 扩展名
name: 'index' 文件名 (不包括扩展名)
}
*/
获取路径最后一部分
windows风格路径: C:\\temp\\myfile.html
posix风格路径: /temp/myfile.html
path.basename('C:\\temp\\myfile.html') // myfile.html
path.basename('/temp/myfile.html') // myfile.html
path.basename('/temp/a/b/c/'); // c
注意, 以上结果均在windows系统打印, 在mac系统中无法提取windows风格路径
解决: 无论在任何系统中, 推荐使用posix风格的路径
获取路径中文件夹部分
path.dirname("/a/b/c/d/e.html") // /a/b/c/d
path.dirname("/a/b/c/d/") // /a/b/c
获取路径中文件扩展名
path.extname('index.html'); // 返回: '.html'
path.extname('index.coffee.md'); // 返回: '.md'
path.extname('index.'); // 返回: '.'
path.extname('index'); // 返回: ''
path.extname('.index'); // 返回: ''
格式化路径, 获取每一部分的值
path.parse('/home/user/dir/file.txt')
返回结果
// { root: '/',
// dir: '/home/user/dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' }
设计一个静态服务器都比较麻烦。同时我们需要http、url、fs、path等模块, 后面我们会学express, 帮助我们封装这些功能
案例1: 访问html文件
let urlObj = url.parse(req.url);// url.parse()解析url字符串, 得到一个对象
console.log(urlObj);
if(urlObj.pathname === "/"){
res.writeHead(200,{"Content-type": "text/html"});
res.end(fs.readFileSync("./static/index.html"));
}
案例2: 加入css文件
if (urlObj.pathname === "/css/index.css"){
res.writeHead(200,{"Content-type": "text/css"});
res.end(fs.readFileSync("./static/css/index.css"));
}
注意, 需要制定Content-type类型为text/css, 浏览器方能解析
案例3: 引入js文件
if (pathName === "/js/index.js"){
res.writeHead(200,{"Content-type": "text/html"});
res.end(fs.readFileSync("./static/js/index.js"));
}
案例4: 引入图片
if (urlObj.pathname === "/img/1.png") {
res.writeHead(200,{"Content-type": "image/jpg"});
res.end(fs.readFileSync("./static/img/1.png"));
}
注意: 访问图片, 注意Content-type的设置为image/png, 浏览器才可以打开图片
优化代码: serverWeb服务
//引入模块
const http = require("http");
const urlObj = require("url");
const pathObj = require("path");
const fs = require("fs");
//创建web页面服务
const server = http.createServer((req, res) => {
//过滤ico图标请求
if (req.url === "/favicon.ico") {
res.end();
} else {
//获取请求路径
let { pathname } = urlObj.parse(req.url, true);///www/index.html
//判断 是否是根目录
if (pathname === "/") { //当访问根 默认访问index.html
pathname = "/index.html";
}
//获取扩展名
let { ext } = pathObj.parse(pathname);
//设置响应头信息
res.writeHead(200, { "Content-type": getMime(ext)+";charset=UTF-8"});
console.log("地址:"+pathname+"扩展名:"+ext+"文件类型:"+getMime(ext));
//响应相应文件
res.end(fs.readFileSync("." + pathname));
}
});
function getMine(ext) {
switch (ext) {
case ".html": return "text/html";
case ".css": return "text/css";
case ".js": return "text/html";
case ".jpg": return "image/jpeg";
case ".jpeg": return "image/jpeg";
case ".json": return "application/json";
default: return "text/plan";
}
}
//设置端口
server.listen(3000);
MIME的全称是Multipurpose Internet Mail Extensions,即多用途互联网邮件扩展类型。这是HTTP协议中用来定义文档性质及格式的标准。服务器通过MIME告知响应内容类型,而浏览器则通过MIME类型来确定如何处理文档。浏览器分辨文件是基于MIME的,而不会把文件打开查看是否是其他类型
文件扩展名 | mime类型 | 备注 |
---|---|---|
.html / .js | text/html | html文本 |
.css | text/css | css样式文件 |
.jpg/.jpeg | image/jpeg | jpg/jpeg图片 |
.gif | image/gif | gif图片 |
.png | image/png | png图片 |
.txt | text/plain | 普通文本 |
.word | application/msword | word文档 |
application/pdf | pdf文档 | |
.json | application/json | json数据 |
它是超文本传输协议, 作用就是指定浏览器 和服务器 之间 传输数据的 一个标准, 发起请求, 请求资源的目标先使用url地址, 然后服务器响应, 搭建连接, 利用http协议标准来传递数据
简单快速, 只传送请求方法和路径url
灵活, 允许传输任意类型数据对象, 只需要在请求头加入Content-Type标记
无长连接, 每次请求和响应结束即会断开.
无状态, 代表后面再处理某些参数, 需要进行重新传输.
支持 客户端/服务器(Client-Sever) 或者 浏览器/服务器(Browser-Server) 模式
一. 请求报文:
1. POST /work/1.php HTTP1.1
2. Host:www.wrox.com
3. User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET
CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
4. Content-Type: application/x-www-form-urlencoded
5. Content-Length:40
6. Connection: Keep-Alive
7.
8. name=Professional_Ajax&publisher=Wiley&page=1
// 第1行: 请求方法 请求路径
// 第2行到第6行, 请求头字段 (5对key-value)
// 第7行: 空格 (必须的, 规定, 把参数和请求头分隔开)
// 第8行: 请求体(请求参数的地方)
// 详解:
// 第4行代表的请求时, 发送的参数, 将会以什么样的内容类型发送给后端
// 设置成"application/x-www-form-urlencoded", 发给后台的参数样子就是key=value&key=value
// 请求报文: 请求方法 + 请求路径 + 请求头 + 请求体(参数)
二. 响应报文
1. HTTP/1.1 200 OK
2. Date: Fri, 22 May 2009 06:07:21 GMT
3. Content-Type: text/html; charset=UTF-8
4.
5.
// 第1行: 响应状态码
// 第2行到3行: 响应头 (告诉浏览器, 如何设置/解析一些数据)
// 第4行: 空格(规定, 把响应体的内容隔开)
// 第5行往下: 都是后台响应返回给浏览器的内容
200: 客户端请求, 和服务器成功做出响应
301: 重定向, 资源被重定向到一个***永久性***的地址上, 资源的原始url可能无法访问
302: 重定向, 资源被重定向到一个***临时性***的地址上, 资源的原始url还在
400: 语义错误, 无法被服务器理解, 请求参数有误
401: 请求需要用户验证, 包含了 Authorization 证书, 但是被服务器拒绝了
403: 服务器已经理解请求, 但是拒绝执行它
404: 请求url不存在 (一般常见于请求路径拼写错误)
500: 服务器遇到了一个未曾预料的请求, 一般出现在后台代码报错
502: 网关或者代理服务器请求时, 从服务器接收到了一个无效响应
503: 服务器过载, 当前无法完成响应, 拒绝客户端连接
504: 网关或者代理服务器请求时, 等待响应超时了
HTTP1.0, 三种请求方法: GET POST HEAD HTTP1.1, 新增五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法
GET 请求指定页面, 并返回目标实体上数据
HEAD 类似于GET请求,只不过返回的响应中没有具体的内容,用于获取响应头(偶尔用)
POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中PUT 从客户端向服务器传送的数据取代指定的文档的内容
DELETE 请求服务器删除指定的数据CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器 (偶尔用)
OPTIONS 允许客户端查看服务器的性能 (偶尔用)
TRACE 回显服务器收到的请求,主要用于测试或诊断 (不常用)作用:
用什么方法请求的后台, 就得用什么方法来接收参数 (用的时候, 一定要和后台规定的保持一致
Express 是一个简洁而灵活的 Node.js Web应用框架, 提供一系列强大特性帮助你创建各种Web应用。Express 不对 Node.js 已有的特性进行二次抽象,我们只是在它之上扩展了Web应用所需的功能。Express内部还是使用的http模块实现服务器创建和监听, 对http模块进行了二次封装
以前用http搭建服务器, 现在用express
1. 下载express模块, 命令 npm install express (如果已安装, 则忽略)
2. 在.js文件中, 引入express模块
3. 初始化服务器对象
4. 设置路由接口地址 (以供浏览器访问)
5. 监听端口号 (给服务器设置端口号)
利用http模块创建一个服务器
let http = require("http");
let server = http.createServer((req, res) =>{
res.writeHead(200, {"Content-type": "text/html;charset=UTF-8"});
if (req.url === "/") {
res.write("写点字给前端吧");
res.end();
}
});
server.listen(9090);
// 注意确保拥有package.json后, 再npm i express 下载此模块到当前工程
// 1. 引入express模块
const express = require("express");
// 2. 生成express应用对象 (服务器server对象是在里面的, 封装进去了)
const app = express();
// 3. 监听浏览器发起的请求
app.get("/", (req, res) => { // 监听浏览器以(get)方式, 请求 (/) 路径时, 才会触发后面的回调函数执行
res.send("你好, my name is express"); // end()被封装进send方法内(而且还包含了设置utf-8的代码也封装进send方法内)
});
// 4. 配置端口号
app.listen(3001);
// 创建一个web网页服务, 最基本的代码
上面目录部分太长了, 可以让后台规定路由的路径(/api/getNew), 然后当访问时, 让后台去转发寻找
例如: 访问 /image/rB.jpg 就相当于 访问的是上面的路径资源 (但是具体访问什么, 还是后台说了算)
不同的路径, 对应的不同路由 (路由 = 路径)
所以所有的请求, 都看后台如何处理
API接口 = 路由 = 路径 = url地址
app监听不同的路由, 当前端访问时, 做出不同的反映, 编写几个路径来试试
// 引入 express模块
const express = require("express");
// 生成对象
const app = express();
app.get('/www',(req,res)=>{
res.send('首页index')
});
app.get('/',function (req,res) {
res.send('首页index')
});
app.get('/logo',function (req,res) {
res.send('logo页面')
});
app.get('/b',function (req,res) {
res.send('bbb')
});
app.get('/new',function (req,res) {
res.send({title:'新闻标题',time:'2019-02-14'})
});
// 下面路径匹配包含a字母的任意路径 -->正则匹配路径
app.get(/a/, function (req, res) {
res.send('/a/');
})
// 下面路径匹配fly结尾的任意路径
app.get(/.*fly$/, function (req, res) {
res.send('/.*fly$/')
})
app使用不同的方式, 监听不同的路径, 每个路径即是一个独立的api接口, 编写几个用例试试
- get方式
- post方式
- put方式
- delete方式
// GET method
app.get('/', function (req, res) {
res.send('GET request to the homepage')
});
// POST method
app.post('/login', function (req, res) {
res.send('POST request to the homepage')
});
// PUT method
app.put('/changePass', function (req, res){
res.send("修改密码, 成功");
});
// Delete method
app.delete('/del', function(req, res) {
res.send("删除成功");
});
它是vscode内的一个插件, 专门用于进行接口开发的调试测试工作, 它可以进行GET/POST/PUT/DELETE等等方式的请求, 而且还可以当做文档保存下来 (如果没用vscode和插件, 可以选择使用postman软件来替代它)
const express = require("express");
const app = express();
const url = require("url");
// 前端传参的目的: 就是为了让后台能够返回对应不同的数据
// 例如: 前端想查看某个学生的信息, 需要传输一个学生的学号到后台
// 后台根据学号返回对应学生的信息
app.get("/stu", (req, res) => {
const {query: queryObj} = url.parse(req.url, true);
const theSno = queryObj['sno']; // 取到前端传递过来的sno参数的值
let arr = [{
sno: 10086,
stuName: "小东",
grade: [90, 89, 67]
}, {
sno: 1001,
stuName: "小豪",
grade: [99, 89, 97]
}, {
sno: 2002,
stuName: "小张",
grade: [97, 99, 89]
}];// 因为没学数据库, 所以先用数据模拟一下数据
let theObj = arr.find((obj => {
if (obj['sno'] == theSno){
return obj; // 如果找到了返回这个对象
}
}))
res.send(theObj);
})
const express = require("express");
const app = express();
// 1. 浏览器上接收到的 响应状态码, 无论是200 304 404 50x, 都是后台返回的响应状态码
// 2. express内置一个错误, 兜底的错误提示页面 (Cannot 请求方式 请求路径)
app.get("/dyh", (req, res) => {
res.send("访问了/dyh, 假设返回了找到的对象");
});
app.use((req, res) => {
res.writeHead(404, {});
res.end(`
Error
Cannot GET ${req.url}
`)
});
app.listen(3000);
1. 路径是我们传给后台的参数, 但是想把路径中的某一段当做具体的值来使用
2. express的路由匹配规则, 可以提前定义好哪几段是具体的值, 然后会被解析到req.params上使用, 后台定义的路径规则:后面是key属性名字, 而前端传来的路径参数用/区分一一对应, 把value接收上, 看下面
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/3498 /books/8989
req.params: { "userId": "3498", "bookId": "8989" }
app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params)
});
//=======示例=============
const express = require("express");
const app = express();
// /hello/10086/book/category 和 后端的路由api 匹配
// 路由路径中, 有:修饰的, 都是keyName(变量名), 用来匹配位置对应接收参数的值
app.get("/hello/:sno/book/:cid",(req,res)=>{
res.send(req.params);// {sno: 10086, bookid: "category"} // params固定的内置属性, 专门来提取这样的参数的
});
app.listen(3000);
注意: 路径参数的名称必须由“单词字符”([A-Za-z0-9_])组成
注意: 除了get方式, 其他方式, 均可以使用路径向后台传参
注意: 通过cookie/url/localStorage传递都可以(叫前端的, 页面传参), 均可以使用路径向后台传参
Express是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。由此可见,中间件在Express开发中的重要性,因此这里我们就专门来总结一下中间件。
中间件函数是可以访问请求对象 (req),响应对象(res)以及next应用程序请求 -响应周期中的函数的函数。该next功能是中间件函数中的一个功能,当被调用时,它将执行当前中间件之后的中间件。
app使用中间件函数, 把公共代码提取进来
app.use((req, res, next) => { // 每个app.use() 就是一个中间件
req.urlObj = url.parse(req.url, true); // 往请求对象身上扩展一个属性
// 所以在以后的所有中间件函数中, 都可以提取req里的urlObj属性
next();
});
使用xxxx.use() 来使用/定义中间件
//======示例==========
const express = require("express");
const app = express();
// 需求1: 我想在每次响应时, 把当前服务器的时间带上
// 抽离出来, 使用前置的中间件函数 (中间件是一个抽象的概念, 中间件函数是具体的代码)
// 注意:中间件调用是按照从上到下的顺序调用
// 触发: 当遇到一次请求时, 必定会执行一次中间件函数
// 注意: 如果这个中间件函数不去结束响应, 那就需要调用next()函数, 让中间件继续向下一个执行
app.use((req,res,next)=>{
//绑定自定义的参数 req res都可以绑定
req.noWtime = new Date().toTimeString();
next();// 继续向下执行(寻找对应的路由api执行)
})
app.get("/",(req,res)=>{
res.send("时间"+req.noWtime);
});
app.get("/dyh",(req,res)=>{
res.send("时间"+req.noWtime);
});
app.listen(3000);
使用 app.use(express.static('./static')), 暴露服务器上某个文件夹下所有内容到服务器根目录中
app.use(express.static('./static')); // 可以访问 static 目录中的所有文件了
// http://localhost:3000/list.html
// http://localhost:3000/images/zg_logo.png
// 设置虚拟路径
app.use('/up',express.static('./upload'))
// 通过带有 /up 前缀地址来访问 upload 目录中的文件了。(无法使用/upload访问)
// http://localhost:3000/up/01.txt
//========示例==============
const express = require("express");
const app = express();
// 对静态资源的访问, express有一个内置的中间件
// 1. 中间件函数 app.use() 给当前express应用添加一个中间件函数, 在app.use()中传入的是函数体(格式固定的)
// express.static() 返回的也是一个函数体, 这个函数体里的代码, 都在express内封装好了 (封装的是处理静态资源访问的过程, 就是昨天大家写的判断和fs读取文件)
// 2. 各个中间件函数之间, 共享req和res对象
app.use((req, res, next) => {
req.nowTime = new Date().toUTCString();
next();
})
// 内部拿到前端请求的路径: /www/index.html
// 内部fs模块在读取时, 都会在前端浏览器请求的路径上, 添加你传入的下面的值
// 总结: 在浏览器访问express项目的静态资源文件, 地址上, 拼接路径时, 把/路径就当做www文件夹
app.use(express.static("./www"));
app.listen(3000);
// http://127.0.0.1:3000/dyh/dyh.html
路由中间件, 只是为了减轻服务器文件的压力, 把所有业务逻辑拆分到各个路由文件当中, 项目结构更加清晰
==1==定义路由指定的xxx.js文件
var router = express.Router(); // express中间件Router路由
router.get('/', function (req, res) {
res.send('Birds home page')
});
router.get('/about', function (req, res) {
res.send('About birds')
});
module.exports = router;//导出
==2==引入路由对象, 监听前缀进行转发 (可以配置多个)
const myRouter = require('./myRouter.js'); // 使用express内置的路由Router中间件
const app = express(); // 实例化app
app.use('/my', myRouter); // 遇到/birds的请求, 转发到birds的路由中
//===========示例===========入口文件=========
const express = require("express");
const app = express();
app.use((req,res,next)=>{
req.newTime = new Date().toTimeString();
next();
});
app.use(express.static("./www"));//静态资源 网页, 图片
// 在这里管理很多的业务逻辑的api路由接口, 很乱, 最好能分堆进行管理
// 把相同类的放到一个.js模块中, 配合路由中间件函数进行管理
// 1. 先分散到具体的路由.js模块文件内
// 2. 引入到这里使用(导入过来的都是拥有具体的路由API接口的 router中间件函数对象)
// 引入 函数
const goodsRouter = require("./routes/goods");
const orderRouter = require("./routes/order");
app.use(goodsRouter);
app.use(orderRouter);
app.listen(3000);
//==============================goods===============
const express = require("express");
const router = express.Router();
router.get("/goodsList",(req,res)=>{
res.send(['dyh','goodsList']);
});
router.get("/goodsDetail",(req,res)=>{
res.send(['dyh','goodsDetail']);
});
//导出
module.exports = router;// 注意router本身就是函数
// 函数名也是变量可以添加属性
//==============================order===============
const express = require("express");
const router = express.Router();
router.post("/orderin",(req,res)=>{
res.send(['insert','jc']);
});
router.put("/orderup",(req,res)=>{
res.send(['update','jc']);
});
router.delete("/orderdel",(req,res)=>{
res.send(['delete','jc']);
});
module.exports = router;
在上面中间件结构中,我们知道了,中间件使用是一个Function,然而,要自定义一个中间件,就是倒腾一番这个Function。这个function总共有三个参数(req,res,next)当每个请求到达服务器时,nodejs会为请求创建一个请求对象(request),因为一个应用中可以使用多个中间件,而要想运行下一个中间件,那么上一个中间件必须运行next()
app.use(function (req, res, next) { // 注册中间函数的
req.now = new Date();
next();
});
app.get('/',function (req, res) {
console.log(req.now);
res.send('自定义中间件');
});
// 中间件常用作用: 在执行业务代码之前, 做出一些预先的处理(解析url/解析post参数/解析cookie/权限验证)
请求方式 |
传参的位置 |
后台接收模块 |
后台属性 |
备注 |
get方式 |
url的?后面 |
url模块 |
query属性 |
|
cookie |
请求头 |
cookie-parser三方模块包 |
req.cookies.key名字 |
设置浏览器的cookie(req.cookie(key名字, value) |
路径传参 |
url路径中 |
路由路径提前配置: |
req.params |
|
session(后台) |
无 |
express-session |
req.session |
此属性即可以存值, 也可以取值, 把他当做对象 |
post/put/delete |
请求体里 注意设置内容的类型 |
body-parser |
req.body |
|
别人封装好的一些中间件函数 (其他程序员自定义的中间件函数)
cookie-parser: 解析浏览器传过来cookie中的数据,并将其保存为req对象的cookie属性
注意: 1. 浏览器在发起请求时, 会默认把浏览器里的值, 都带上, 发给服务器端
案例: 登录后, 设置cookie保存登录用户名, 欢迎xxx/游客
// 安装命令: npm install cookie-parser
// 使用cookie-parser中间件
const cookieParser = require("cookie-parser");
app.use(cookieParser());
res.cookie('username',post.username); // 设置cookie
req.cookies.username; // 提取cookie
这里cookie保存在浏览器端, 在浏览器端的一切都是不安全的, 可能会被篡改, 数据尽量都来自于后台/保存在后台
//==========服务端==============
const express = require("express");
const cookieParser = require("cookie-parser");
const app = express();
//使用cookieParser中间件 让当前express拥有解析cookie的能力
app.use(cookieParser());// 调用CookieParser中间件函数, 让当前experss项目拥有解析cookie参数的能力 (好比url.parse) (中间件内, 会在req对象上, 自定义cookies属性, 放置解析到的cookie属性名和值)
// 设置静态资源路径
app.use(express.static("./public"));
//处理请求
app.get("/login",(req,res)=>{ //成功返回
res.send("cookie-OK");
})
app.get("/user",(req,res)=>{ //获取前端传过来的cookie
console.log(req.cookies);//{ username: 'cookie-OK' }
res.send(req.cookies.username);
})
app.listen(3000);
// 前端传递get参数, 后端url模块 从 query属性中取出
// 前端传递cookie参数, 后端cookie-parser模块, 集成到app的中间件函数中, 从req.cookies中取出
//=========客服端==========
//cookie:可以实现浏览器端存储一些数据,只要不过期,就会一直存储在浏览器cookie中
//当前页面发起请求时,都会默认把cookie添加在请求头中传给后端
// 1设置cookie
// document.cookie = "username=dyh123456";
//模拟登录接口 测试cookie
// console.log(document.cookie);
$.ajax({
url:'/login',
success(res){
console.log(res);
document.cookie = `username=${res}`;
}
});
$.ajax({
url:'/user',
success(res){
console.log(res);
}
})
express-session: 解析服务端生成的sessionid对应的session数据,并将其保存为Request对象的session属性
案例: 登录后, 还是从服务器上获取用户的名字
session其实就是服务器上一个临时存储的对象
浏览器的SessionStorage 和后台的 session 是两码事
// cookie:存储在浏览器上 不安全
// session:是有别于数据库的一种后台临时存储技术,session存储在后端中 (但是express-session并非真正的存储在后端的一种技术, 而是采用唯一cookieid来代替我们的key和value, 从而让我们感觉到session是存储在后台的)
const express = require("express");
const session = require("express-session");// 1. 下载并引用express-session
const app = express();
app.use(session({
// name :'cookies', //显示的名字
secret:'secret',// 对session id 相关的cookie 进行签名
resave:true,// 是否每次请求后, 都重新存储一次session的值
saveUninitialized:false,// 是否保存未初始化的会话
cookie:{
maxAge:1000*60*3,// 设置 session 的有效时间,单位毫秒 (3分钟后过期)
}
}))//使用session模块
// 登录接口
app.post('/login', function(req, res){
let post = req.body;
req.session.userName = post.username // 登录成功,设置 session
res.send('登录成功')
});
app.get('/getuser', function(req, res){ // 获取用户信息
if(req.session.userName){
res.send('欢迎您:'+req.session.userName) // 获取服务器上session中key对应的值
}else{
res.send('欢迎您:游客')
}
});
app.listen(3000);
body-parser: 解析请求体中的数据,并将其保存为req对象的body属性
案例: post方式提交给服务器的参数
// 安装命令: npm install body-parser
// false: 键值对中的值就为'String'或'Array'形式
// true: 可以是任意类型
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); // parse application/json
req.body 中提取post传递的参数
//======示例============
// GET传参, 路径传参, cookie传参, session使用(后端), POST传参(body-parser)
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({extended:true})); // 集成body-parser的能力, 到当前的这个express应用上, 让web网页服务器可以解析post传递过来的参数
// 注意: post解析后的参数, 会在上面这个中间件函数内, 给你挂载到req的body属性上
app.post("/post",(req,res)=>{
console.log(req.body);
res.send(req.body);
})
app.put("/put",(req,res)=>{
console.log(req.body);
res.send(req.body);
})
app.listen(3000);
为express应用,轻松的创建HTTP错误,返回HTTP状态的一个第三方中间件函数
首先,安装此模块到express应用中去
npm install http-errors
当用户访问时,我们直接返回一个错误的状态给前端查看,你可以用在任何的一种错误的逻辑下,返回一些错误提示信息给客户端,
const express = require("express");
const createError = require("http-errors");// 1. 下载并导入模块
const app = express();
app.get("/", (req, res, next) => { // 可以用next, 直接执行默认的错误提示
next(createError(401, 'Please login to view this page.')); // 没有响应内容, createError默认响应错误提示的消息格式给前端
})
app.listen(3000);
// 2. 在兜底的中间件函数中, 调用createError()和next() 覆盖express默认的返回信息 (作用: 自定义错误信息返回的提示)
// 3. 工作过程: 调用next() 传参到 下一个中间件函数(但是下面没有了, 实际上后面还有express内置的一个默认错误返回的页面, 但是你传参了, 给它覆盖掉了)
用于服务器端设置favicon视觉提示,其实就是浏览器标签标题栏上的小图标
先下载此模块到express项目中去
npm install serve-favicon
const express = require("express");
const favicon = require('serve-favicon'); // 下载安装引入图标中间件
const path = require('path');
const app = express();
// path.join()跟数组的join一样, 把里面所有的参数用/拼接起来
// 当前文件所在文件夹的绝对路径 + "/" + "public" + "/" + favicon.ico
// path.join(__dirname, 'public', 'favicon.ico')
// 集成小图标中间件函数, 指定小图标的路径
app.use(serveFavicon("./img/favicon.ico"));
app.get("/", (req, res) => {
res.send("图标成功了吗?");
}),
// 注意, 每个端口号的favicon.ico图标有缓存
// 解决方式1: 清理浏览器的缓存, 再运行查看
// 解决方式2: 换一个express的端口号即可
这是一个svg图形验证码插件,把text内容转成svg图形返回使用
首先在express项目中,下载此模块到项目中来
npm install svg-captcha
const svgCaptcha = require("svg-captcha");
app.get("/", (req, res) => {
const captcha = svgCaptcha.create();
res.type("svg");
res.status(200).send(captcha.data);
})
//======服务端============
//验证码 svg 图片
const express = require("express");
const cookieParser = require("cookie-parser");//cookie
const bodyParser = require("body-parser");
const svgCaptcha = require("svg-captcha");
const app = express();
app.use(express.static("./public"));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.get("/getCode", (req, res) => {
const captcha = svgCaptcha.create();
res.cookie("code", captcha.text);// 把真正的验证码保存到cookie上,用于下面接口的调用哦
res.type("svg");// 告诉浏览器, 这里返回的是图片的数据
res.send(captcha.data);// 提取验证码图片的data数据返回给前端
})
app.post("/register", (req, res) => {
if (req.cookies.code.toUpperCase() == req.body.userCode.toUpperCase()) {// code是上面设置的cookie名, userCode是前端传参用的参数名
res.send("验证码成功");// 调用数据库的代码存储用户
} else {
res.send("验证码失败");
}
})
app.listen(3000);
//=========客户端=================
登录
用户名:
密码:
验证码:
一个Node.js模块,用于解析表单数据,特别是文件上传,先安装此模块到工程目录中
npm i formidable
准备14_formidable使用.js,准备引入模块和初始化form对象并且进行配置
const express = require("express");
const app = express();
const fs = require("fs");
const formidable = require("formidable");
const formObj = new formidable.IncomingForm(); // 新建form对象
formObj.encoding = 'UTF-8'; // UTF8编码
formObj.uploadDir = "./tempDir"; // 接收的文件缓存路径
编写接口, 格式化接收到的form表单的信息,fields是接收到的post参数,files是接收到的文件保存在缓存路径中,需要自己利用fs模块,把缓存中的图片文件移动到具体业务文件夹中保存起来
app.post("/uploadFile", (req, res) => {
formObj.parse(req, (err, fields, files) => {
let fileObj = files.image;// 4. 获取真正的文件对象, (image是前后端传递值时的key值名称)
let oldPath = fileObj['path'];// 6. 从缓存里把图片移动到真正的业务文件夹中
let newPath = "./uploadFile/" + fileObj['name'];
fs.rename(oldPath, newPath, (err) => { // 7. 挪动
if (err) {
console.error(err);
res.send("上传失败, 至于为啥:" + err.toString());
} else {
res.send("上传成功");
}
});
});
});
//===========示例=========
const express = require("express");
const fs = require("fs");
// 1. express4.x版本以后, 内置了body-parser模块
const app = express();
app.use(express.urlencoded({extended: true})); // 解析前端参数内容类型application/x-www-form-urlencoded
app.use(express.json()); // 对application/json内容类型, 做出解析挂载到body上
// 2. 文件是一种二进制流的形式, 并不是key=value那种字符串, 所以后台需要用另外的一个模块来接收, formidable(接收二进制流数据)
const formidable = require("formidable");
app.use(express.static("./public"));
app.post("/one", (req, res) => { // 接收application/x-www-urlencoded
res.send(req.body);
})
app.post("/two", (req, res) => { // 接收multipart/form-data
const formObj = new formidable.IncomingForm(); // 新建form-data接收的对象
formObj.encoding = 'UTF-8'; // UTF8编码
formObj.uploadDir = "./tempDir"; // 接收的文件缓存路径(formidable会把接收到的文件流写入到这个缓存文件中)
formObj.parse(req, (err, arg, files) => { // 把req对象中的, 浏览器请求时的内容进行格式化, 把文件缓存到指定目录中, 解析提交上来的keyvalue参数
console.log(arg.username, arg.password);
console.log(files.head_img);
// 注意: 上传的文件现在都在临时目录里(而且文件名也不是我想要的), 而且以后前端肯定要查看上传的图片, 所以我们需要把临时文件, 挪动到public目录下
let oldPath = files.head_img.path;
let newPath = "./public/image/" + files.head_img.name;
fs.renameSync(oldPath, newPath);
res.send("上传成功");
})
})
app.listen(3000);
//==========前端代码================
function register5(){
$.ajax({
url: "/one",
type: "POST",
headers: {
"Content-type": "application/json"
},
data: JSON.stringify({
user: $("#username").val(),
pass: $("#password").val(),
arr: [{obj: 234}]
}),
success (res) {
console.log(res);
}
})
}
function register4(){
$.ajax({
url: "/one",
type: "POST",
data: {
user: $("#username").val(),
pass: $("#password").val()
},
success (res) {
console.log(res);
}
})
}
function register() {
// 1. 要把form标签里的值, 转换成formData表单数据, HTML5提供了一个新的类 FormData类
let formd = new FormData($("#myForm")[0]); // 会把name+value转换成一个大的表单内容类型(multipart/form-data)
$.ajax({
url: "/two",
type: "POST",
data: formd, // 直接把表单对象看成参数的整体
contentType: false, // 内容类型, 根据data参数的值来决定
processData: false, // 不要处理参数data
success (res) {
console.log(res);
}
})
}
以上后台接口部分就已经准备完毕,请求的方式为post,地址为/uploadFile,图片参数名为image,前端可以使用form表单标签+input的type为file,name设置image进行图片的上传即可,注意表单method为post并且enctype为multipart/form-data(以二进制流形式传递数据给后台解析),提交成功后,后台的uploadFIle文件夹中就会出现前端上传到服务器的图片文件,打开可以预览。
它的作用, 可以实时监听服务器.js文件里代码的变更, 一旦变更, nodemon会重新执行node xxx.js文件的命令, 无需我们再自己停止服务器, 再启动服务器的过程
1. 全局环境 安装nodemon工具模块, 命令: `npm install nodemon -g` (全局模块才有命令直接使用)
2. 在当前express项目下,新建nodemon.json配置文件,配置nodemon相关的设置
3. nodemon.json配置中内容如下:
4. 用nodemon xxx.js 命令来启动热监听
效果: 当项目中的js/json文件里的代码, 改变时, 终端中, 会实时刷新重启服务器 (我们不必再手动重启服务器了)
restartable:重启的命令,默认是 rs
ignore:忽略的文件后缀名或者文件夹,文件路径的书写用相对于 nodemon.json 所在位置的相对路径 (这些忽略的文件变更内容不会触发服务器的重启)
verbose:true 表示输出详细启动与重启信息
execMap:运行服务的后缀名和对应的运行命令,"js": "node --harmony" 表示用 nodemon 代替 node --harmony 运行 js 后缀文件
watch:监控的文件夹路径或者文件路径。
env:运行环境 development 是开发环境,production 是生产环境。
ext:监控指定后缀名的文件,用空格间隔。
{
"restartable": "rs",
"ignore": [
".git",
".svn",
"node_modules/**/node_modules"
],
"verbose": true,
"execMap": {
"js": "node --harmony"
},
"watch": [
],
"env": {
"NODE_ENV": "development"
},
"ext": "js json"
}
为了保证各项任务施工过程中顺利完整的进行, 而搭建的工作平台 (比如工地用的铁架子)
代码中体现: 下载了一套配置好的文件夹+文件+代码+插件的组合等
这是一个express的脚手架模块, 可以帮我们快速搭建一套完整的文件夹+文件+配置的express的web项目
1. 全局安装此模块 npm install express-generator -g (工具类的模块, 要安装到全局) (都会在电脑中添加一条命令)
2. express --view=ejs myapp 使用ejs作为项目模板引擎, 初始化项目文件夹名字为myapp
3. cd myapp下, 执行npm install 安装所需要的所有第三方依赖
4. npm run start 启动express项目
文件/文件夹 | 解释 | 备注 |
---|---|---|
bin | 项目核心 | |
---www | 服务器核心文件 | 启动服务器 |
pulbic | 静态资源目录 | 图片/样式/js等 |
routes | 每个路由对应的逻辑代码 | |
views | 模板引擎文件 | 用的什么命令就生成对应模板 |
app.js | 项目基础配置 | |
package.json | 配置文件 |
//==========www======================
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app'); // 项目的基础代码, 在app.js中
var debug = require('debug')('myapp:server'); // 调试的包, 在日志中记录一些服务器的请求/报错信息
var http = require('http'); // 创建web网页服务的
/**
* Get port from environment and store in Express.
*/
// process 是一个全局变量, 可以获取到配置文件中的(package.json/nodemon.json中的env的配置的值)
// 参数1: 是从配置文件里读取端口号, 如果没有, 就用后面的3000
// 逻辑或返回的是一个具体的值(而非大家熟知的true/false) (只要不是null, undefined, '', 0, !true)其他的值都可以当做true的现象来使用
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app); // 把express对象, 又变成了一个http创建的服务器对象
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port); // 在这里配置的真正的端口号
server.on('error', onError); // 监听服务器错误动作(系统级, 终端)
server.on('listening', onListening); // 执行的动作
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
//==========app.js======================
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan'); // 记录请求的路径和响应的时间, 可以打印在控制台中
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup (模板文件ejs存放的路径)
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev')); // dev代表是开发环境, 才打印
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 路由前缀, 或者叫虚拟路径, 作用区分不同的类型的api接口 (还可以统一某一类接口的统一前缀)
// 浏览器发起请求时, 前缀路径要先匹配这里, 再匹配"剩余"的路径 (根除外)
// 路由前缀(随便定义)
app.use(indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404)); // next() 调用下一个中间件, 同时给下一个中间件传参
});
// error handler
app.use(function(err, req, res, next) {
// err参数1, 接收的是上一个中间件next()里传下来的参数
// set locals, only providing error in development
res.locals.message = err.message; // 提出错误信息的文字
// env 负责记录开发环境/线上环境的一个英文字符串的标识
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500); // 设置状态码
res.render('error'); // 渲染error.ejs错误提示的模板文件, 作为返回给浏览器的页面
});
// 这里有listen吗? 所以这个文件并不是node要执行的文件(因为压根没有分配端口号)
module.exports = app; // express应用导出, 导出到bin/www中使用
//==========index.js======================
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
// res.render('index', { title: 'Express' }); // 后端渲染模板使用的方法
res.send("欢迎使用express");
});
module.exports = router;
请求方式 | 请求数据存放位置 | 请求内容类型 | 浏览器载体显示 | 后端使用模块 | 备注 |
---|---|---|---|---|---|
GET | url的?后面, 格式key=value&key=value | application/x-www-form-urlencoded(绝大部分默认类型) | Query String Praramter | url模块 | get方式传参大小有限制4kb |
GET | url路径中传参 | application/x-www-form-urlencoded | 无 | 提前配置好路径中的":变量名" | 传参不易过大 |
POST | 请求体 | application/x-www-form-urlencoded | Form Data | body-parser | |
POST | 请求体 | application/json | Request Payload | body-parser | 前端需要把参数格式化成JSON格式的字符串 |
POST | 请求体 | multipart/form-data | Request Payload | formidable/multer | 前端参数内容是表单数据(并非普通的key-value数据 |
PUT | 同POST | 更新数据 | |||
DELETE | 同POST | 删除数据 |
数据库是按照数据结构来组织、存储和管理数据的仓库。数据库就是存储数据的仓库
我们的程序都是在内存中运行的,一旦程序运行结束或者计算机断电,程序运行中的数据都会丢失。所以我们就需要将一些程序运行的数据持久化到硬盘之中,以确保数据的安全性。而数据库管理系统就是数据持久化的最佳选择。
互联网世界,充斥着大量的数据。即这个互联网世界就是数据世界。数据的来源有很多,比如出行记录、消费记录、浏览的网页、发送的消息等等。除了文本类型的数据,图像、音乐、声音都是数据
我们可以设计数据存放的结构, 进行有序和结构化的管理, 方便我们进行查找和使用. 现在的大数据和所有你用的网站以及应用都离不开数据库在后面默默的支持.
## 人工管理阶段
1. 结绳计数: 绳子每打一个结代表一个或一次
2. 筹码计数: 每一筹码代表1,或10,或100等
3. 在木头上画道。每一道代表1,或10,或100等
4. 算盘 ,使用算盘计数,以及进行计算。
5. 账本记账的方式来管理数据
## 计算机文件管理
1. 记事本/写字板来管理数据
2. excel表格/word等来管理数据
3. 微软后来出了Access数据库软件(类似于excel表格)
## 数据库管理系统
1. 关系型数据库: (利用二维结构来组织数据结构, 有行和列, 类似表格)
- Oracle
- MySQL
- DB2
- Microsoft SQL Server
- Sqlite (IOS设备中)
特点: 存储的格式可以直观地反映实体间的关系。和常见的表格比较相似,关系型
数据库中表与表之间是有很多关联关系的。
虽然关系型数据库有很多,但是大多数都遵循 SQL(结构化查询语言,Structured Query Language)标准。
常见的操作有查询,新增,更新,删除,求和,排序等。
关系型数据库对于结构化数据的处理更合适,如学生成绩、学生信息, 学科信息等,这样的数据一般情况下需要使用结构
化的查询,例如 join,这样的情况下,关系型数据库就会比 NoSQL 数据库性能更优,而且精确度更高。
2. 非关系型数据库 (多靠key-value格式来存储数据)
- Redis
- Mongodb
特点:NoSQL 数据存储不需要固定的表结构,通常也不存在连接操作。在大数据存取上具备关系型数据库无法
比拟的性能优势。Key/value模型对于IT系统来说的优势在于简单、易部署、高并发。
典型产品:Redis
文档型数据库可以 看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查
询效率更高。面向文档数据库会将数据以文档形式存储。
典型产品:MongoDB。
MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。
术语 | 解释 |
---|---|
数据库服务 | 安装到某个电脑上的一个数据库管理系统, MySQL默认端口3306 |
数据库 | 多个数据表的集合, 一个数据库服务下, 可以有多个数据库 |
数据表 | 一个数据库里包含很多的数据表存在 (每个数据表类似于excel表格) |
列 | 一列包含了相同类型的数据, 类似excel里的列, 也叫字段 |
行 | 一行(也叫元祖或记录), 代表了一条数据记录 |
主键 | 主键必须是唯一不可重复的, 每条数据都有一个唯一值作为主键来标识(类似身份证号码) |
第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。
第二范式:需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关
同一个订单中可能包含不同的产品,因此主键必须是“订单号”和“产品号”联合组成,
但可以发现,产品数量、产品折扣、产品价格与“订单号”和“产品号”都相关,但是订单金额和订单时间仅与“订
单号”相关,与“产品号”无关,这样就不满足第二范式的要求第三范式:需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
反三范式: 没有冗余的数据库设计可以做到。但是,没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。具体做法是:在概念数据模型设计时遵守第三范式,降低范式标准
的工作放到物理数据模型设计时考虑。降低范式就是增加字段,允许冗余,达到以空间换时间的目的。
1. DDL操作: 用于定义和管理 SQL 数据库中的所有对象的语
言,对数据库中的某些对象 ( 例如,database,table) 进行管理。包括的关键字有:create、alter、drop、
truncate、comment、grant、revoke 等, 隐性提交的,不能 rollback
2. DML操作: DML(Data Manipulation Language)数据操作语言 - 数据库的基本操作,SQL 中处理数据等操作, 统称为数据操纵语言 , 简而言之就是实现了基本的“增删改查”操作。包括的关键字有:select、update、
delete、insert 等, 可以手动控制事务的开启、提交和回滚的
1. 创建数据库: `create database IF NOT EXISTS ujiuye CHARACTER SET utf8` (含义: 如果ujiuye数据库不存在, 这创建此数据库, 设置字符集为UTF8编码)
2. 创建表格: 注意列的字段名字 (可以是大写也可以小写, 也可以驼峰/下划线连接, 只要组内的沟通好即可, 我的习惯是_连接的形式)
3. 删除表格: `drop table student;`
4. 删除数据库: `drop database ujiuye;`
工具设置主键, 注意一个表最好有一个主键(唯一的值, 如果不确定就用id序号做主键, 而且可以让id自增, 注意自增只能是数字类型的列字段)
不想重复的字段, 可以设置Unique索引
use ujiuye;
create table IF NOT EXISTS student(
id int,
sno varchar(50),
name varchar(30),
sex varchar(30),
family varchar(30),
province varchar(30),
education varchar(30),
grade varchar(30),
tno varchar(50)
)
use ujiuye;
create table IF NOT EXISTS teacher(
id int,
tno varchar(50),
name varchar(30),
t_sex varchar(30),
t_age varchar(30)
)
格式1: 因为id是自增可以省略, 其他字段要一一对应
INSERT INTO teacher(tno,t_name,t_sex,t_age)
VALUES(10001,'李老师','男',50);
格式2: 可以省略具体的字段, 按照表的默认顺序字段插入, 注意id位置可以传null, 让id自增 (但是如果id不连续, 可以看下面参考文档里设置下)
INSERT INTO teacher
VALUES(null, 10002,'赵老师','男',46);
格式3: 某些字段可以不写(只要数据库没限制, 不可以是null的条件, 也可以不写, 执行默认值/null)
INSERT INTO teacher(tno, name)
VALUES(10004, '周老师');
对于含可空字段、非空但是含有默认值的字段、自增字段,可以不用在 INSERT 后的字段列表里面出现, VALUES 后面只写对应字段名称的 VALUE
格式4: 多条同时插入
INSERT INTO student(sno, name, sex, family, province, education, grade, tno)
VALUES
(20150001,'小花', '女', '3口人', '甘肃', '硕士', '研二', 10001),
(20150002,'大黄', '男', '8口人', '河南', '本科', '大四', 10001),
(20150003,'王明', '女', '5口人', '河南', '专科', '大二', 10004),
(20150004,'刘二', '男', '4口人', '河南', '博士', '博三', 10003),
(20150005,'王五', '男', '3口人', '河南', '博士', '博一', 10003),
(20150006,'赵四', '男', '2口人', '河南', '本科', '大一', 10002)
格式1: 更新某个字段的值等于... (切记, 更新前一定要做好备份, 因为忘记加条件就把整个表的这个字段全都重新覆盖了)
UPDATE teacher SET t_age = 68 WHERE tno = 10004
基本用法: SELECT * FROM student
条件查询: SELECT * FROM student WHERE sno = 20150001
还可以使用 >、<、>=、<=、!= 等比较运算符;多个条件之间还可以使用 or、and 等逻辑运算符进行多条件联合查询
排序查询: SELECT * FROM teacher ORDER BY t_age
DESC 和 ASC 是排序关键字,DESC 表示按照字段进行降序排列(上大 - 下小),ASC 则表示 升序排列(上小 - 下大),如果不写此关键字默认是升序排列。 (可以在ORDER BY 前加WHERE 限制条件)
限制条数: SELECT * FROM teacher LIMIT 2, 1
(从下角标2开始, 查询1条)
聚合查询: SELECT [field1,field2,......fieldn] fun_name FROM tablename [WHERE where_contition] [GROUP BY field1,field2,......fieldn
很多情况下,我们需要进行一些汇总操作,比如统计整个公司的人数或者统计每个部门的人数,这是就要 用到 SQL 的集合操作。
对其参数进行以下说明: fun_name 表示要做的集合操作,也就是聚合函数,常用的有sum(求和)、count(*)(记录数)、max(最 大值)、min(最小值)。 GROUP BY 关键字表示要进行分类聚合的字段,比如要按照部门分类统计员工数量,部门就应该写在 group by 后面。 注意:having 和 where 的区别在于 having 是对聚合后的结果进行条件的过滤,而 where 是在聚合前 就对记录进行过滤,如果逻辑允许,我们尽可能用 where 先过滤记录,这样因为结果集减小,将对聚合的效 率大大提高,最后再根据逻辑看是否用 having 进行再过滤。
表连接分为内连接和外连接,它们之间的最主要区别是:内连接仅选出两张表中互相匹配的记录,而外连
接会选出其他不匹配的记录。我们常用的是内连接。外连接有分为左连接和右连接,具体定义如下:
左连接:包含所有的左边表中的记录甚至是右边表中没有和它匹配的记录
右连接:包含所有的右边表中的记录甚至是左边表中没有和它匹配的记录
SELECT ename,deptname FROM emp LEFT JOIN dept ON emp.deptno=dept.deptno;
某些情况,当我们查询的时候,需要的条件是另一个 SELECT 语句的结果,这个时候,就要用到子查询。 用于子查询的关键字主要包括 in、not in、=、!=、exists、not exists 等。
表连接在很多情况下用于优化子查询
SELECT * FROM emp WHERE deptno IN(SELECT deptno FROM dept);
格式1:删除数据 `DELETE FROM tablename [WHERE CONDITION];
格式2:DELETE t1,t2,...tn FROM t1,t2,...tn [WHERE CONDITION];不管是单表还是多表,不加 where 条件将会把表的所有记录删除,所以操作时一定要小心。
所以一般使用逻辑删除, 不会真正的删除数据
注意表名替换
ALTER TABLE teacher DROP id; ALTER TABLE teacher ADD id int NOT NULL FIRST; ALTER TABLE teacher MODIFY COLUMN id int NOT NULL AUTO_INCREMENT,ADD PRIMARY KEY(id)
//MySQL操作数据库执行
// 1. 引入mysql包
const mysql = require("mysql");
// 2. 创建连接对象
const con = mysql.createConnection({
host: "localhost",
port: 3306, // 默认不写就是3306
user: "root",
password: "root", // 注意: 如果是本地还是root用户, 密码可以省略, 但是远程数据库必须写ip/域名以及非root用户登录
database: "dyh" // 连接后, 选中哪个数据库(类似于命令use dyh)
});
// 3. 发起连接
con.connect(err => {
console.log(err);
})
// 注意: mysql和express一样, 都会建立一个服务, 停留在内存中(终端显示的就是未执行完毕的状态)
// 4. (可选)关闭数据库连接
con.end();
connection.query(sqlString, values,callback)
// 第一个参数查询语句 ( 带?占位符 ),占位符对应的参数,, 第二个参数查询后的回调函数
connection.query(‘SELECT * FROM student WHERE name = ? AND sex = ?", [urlArg, '女'], function (
error, results, fields) {
// error will be an Error if one occurred during the query
// results will contain the results of the query
// fields will contain information about the returned results fields (if any)
});
多占
//========使用=================
const express = require("express");
const app = express();
const mysql = require("mysql");
const con = mysql.createConnection({
host: "localhost",
user: "root",
port: 3306,
password: "",
database: "ujiuye"
});
con.connect();
app.get("/stuList", (req, res) => {
con.query("SELECT * FROM student", (err, results) => {
res.send(results); // 等数据库操作完毕, 再响应回结果
})
})
app.listen(3000);
// express项目, 和mysql数据库连接, 都准备就绪
// 启动此js文件时, web网页服务+mysql数据库连接 都准备就绪
// 等待浏览器请求stuList, 执行sql语句, 返回数据
exports.to = (promise) => { // Promise对象和错误信息
// 相当于对Promise的结果进行了2次封装
return promise.then(data => [null, data])
.catch(err => [err, undefined])
}
//===============to实现原理==================
// 名字随便起, 意思就是await 等待to方法, to方法封装
// 同步代码 --- 捕获错误 --- try+catch捕获
// 目的: 捕获错误, 给用于一个中文显眼的提示
try {
const arr = "";
arr.map();
} catch (err) {
console.log("map方法是数组特有的");
}
console.log("下面代码?");
// 所以, try+catch 如果再配合if, else, 就会变得非常多的大括号
// 同步代码? +promise
function aFn() {
return new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
// resolve([1, 2, 3, 4]);
reject("数据库语句错误");
}, 2000);
});
}
// 配合async+await, 把异步回调函数干掉, 改成了同步代码的流程
// async function getData() {
// try {
// const arr = await aFn(); // await后面如果遇到了错误, 此代码就会一直在这里暂停
// } catch (err) {
// console.log(err);
// }
// console.log("会执行吗?");
// }
// getData();
// 替换成await-to.js的使用
async function getData() {
const [myErr, result] = await awaitToJS(aFn()); // await后面如果遇到了错误, 此代码就会一直在这里暂停
if (myErr !== null) {
console.error(myErr);
} else {
console.log(result);
}
}
getData();
// 不想用try+catch, 替代品await-to.js工具就出现了
// 本质就是一个对Promise对象, 进行二次处理的一个封装
// 管家的管家.
// 在await 使用时的地方, 同步代码, 就可以使用同步的判断, 不要使用try+catch
function awaitToJS(promise) {
let p = promise.then(data => [null, data])
.catch(err => [err, undefined]);
return p; // 因为它的resolve里的结果, 是[null, data]; 一会儿await接到的就是这个数组
// 1. resolve()成功 -> then()里回调函数执行, 接收成功结果
// 2. reject()失败 -> catch()里回调函数执行, 接收错误结果
// 目的: 为了把成功/失败的结果, 形成一个数组, 封装返回出去
// .then()函数会, 返回一个新的Promise对象
// .then()/.catch()里的return结果, 都会作为resolve成功的结果返回给一个新的Promise对象里
// 使用: const [myErr, result] = await awaitToJS(aFn()); // await后面如果遇到了错误, 此代码就会一直在这里暂停
}
const dbConfig = {
host: "localhost",
port: 3306,
user: "root",
password: "",
database: "ujiuye"
}
const to = require("./awaitTo");
class Db {
static connect(){ // 数据库连接的动作
this.con = mysql.createConnection(dbConfig);
this.con.connect();
}
static operator(sqlStr){ // 此方法利用Promise把数据库的执行sql语句的异步代码封装进去
return new Promise((resolve, reject) => {
this.con.query(sqlStr, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
})
})
}
static async query(sqlStr){ // static修饰的方式里的this, 指向的是类
return await to(this.operator(sqlStr));
}
}
module.exports = Db; // 导出当前封装的Db工具类
//==================base64
const base64 = (str) => { //base64加密方法
if (typeof str !== 'string') {
str = JSON.stringify(str);
}
return Buffer.from(str).toString("base64");
}
const base64decode = (str = "") => { // base64解密
return Buffer.from(str, "base64").toString('utf-8');
}
// 生成jwt字符串, 用于替代密码, 在前端使用, 标识用户的身份, 方便一些需要识别用户的接口来使用
// jwt 三部分组成
// (1):header (说明jwt生成的一些方式, 比如使用base64加密) (2): payload(内容) (3): sign(用于核对, jwt是否被人串改)
// console.log(dataArr,dataArr[0]['uid'])
let jwtHeader = {// 告诉别人我这个jwt字符串生成时, 类型是token(作用), 生成的方式使用的是base64加密方式
type:"tocen",
sec:"base64"
}
let jwtPayload = {
uid:dataArr[0]['uid'],//得到用户的uid身份标识
createTime: new Date().getTime(),//生成时时间 毫秒
expire: 10 * 60 * 60 * 1000 //有效期 10分钟
}
let sign = base64(JSON.stringify(jwtHeader) + JSON.stringify(jwtPayload));
// 用base64加密上2个对象生成一个base64字符串(为了在解密使用时, 对前边2个东西, 进行匹配判断, 查看前2个参数的值有没有被人串改)
let jwtStr = base64(JSON.stringify(jwtHeader) + "." + JSON.stringify(jwtPayload) + "." + sign); // .可以随便用, 目的为了以后解密时, 方便分割
步骤:
1. 入门 (web前端入门 --- 能够独立解决问题, 实现目标需求, 前端实现效果)
2. 进阶 (学一些vue.js / react.js + nodejs+mysql) 为了高效的实现需求 ---- 能够胜任工作 + 刷点面试题
3. 基础 (设计模式 / 算法 / 数据结构) --- (可选, 计算机组成原理, 计算机网络技术(皮毛就行), web网络安全)
4. 如果选择js扎根, 深入学习js的原理. 以及vue.js和react.js的基础原理.
5. ......
设计模式: 虚拟抽象的概念, 只是为了, 按照一种编码的规则/文件规则, 把你的代码, 分散到有组织的一种结构上
(设计模式就是为了让你的代码, 管理起来更加的优雅)
Model --- 数据模型, 操作数据 --- 代码中如何体现? 可以定义表对应的Model文件
View --- 视图层 , 负责展示 --- res.send() 返回给前端, 前端的网页就是 view层操作 (ajax+js铺设)
Controller --- 控制器层 , 路由接口动作 --- app.xxx(路径, 回调函数) 路由接口的回调函数, 就是我们的Controller控制器层
定义一套模具, 往里填充动态变化的内容, 在后台就把数据和标签生成完毕, 直接把完整的html网页返回给浏览器, 无需再使用Ajax来请求数据了
它是一套模板语法, 有自己的规则, 在NodeJS后台, 把模板+数据, 生成一套HTML代码响应回给浏览器
安装pug模块, npm install pug, 无需require引入
app.set('view engine', 'pug'); // 让服务器, 使用pug模板引擎
app.set("views", "./template"); // 设置模板文件, 所在文件夹 (默认是views文件夹)
在对应api接口中, res.render('index.pug', { theTitle: 'Hey', message: 'Hello there!', user: {age: 19}, arr: [1, 2, 3, 4, 5]}) 替代send()方法, 返回模板加载后的内容, 并向模板中传值
doctype html -> HTML5的DOCTYPE声明
html -> 顶头写的代表html标签
head -> 缩进代表嵌套关系, head标签, 上下并列代表并列关系
title= 变量名 -> 代表标签的innerHTML的值
html
head
title= theTitle
body
标签= 变量名 -> 直接在body缩进书写标签, 使用变量即可实现数据展示
标签 嵌入文本 -> 直接在标签内嵌入文本, 中间加一行空格即可
标签(属性= 值) -> 属性的使用
标签(属性= 变量)
style属性的使用 -> 对应对象, 但是注意字符串要加引号
标签#id -> 给标签加id属性
标签.class -> 给标签加lclass属性
h1= message
span span内的文本
a(href= 'http://www.baidu.com') 去百度逛逛
p(title= theTitle) 我是个p标签
p(style= {color: 'white', background: 'black'}) 我是p标签
h2#app 我是加了id的h2标签
h3.bpp 我是加了class的h3标签
each 临时变量 -> 循环的使用
ul
each val in arr
li= val
EJS 是一套简单的模板语言,在NodeJS后端利用ejs模板引擎, 生成 HTML 页面
纯 JavaScript
快速开发
执行迅速
语法简单: 可以直接在HTML中书写JS代码
同时支持前后端环境
相比pug, 更贴近于我们现使用的HTML标签语法
安装ejs模块, npm install ejs
app.set('view engine', 'ejs') // 设置模板引擎
app.set("views", "./template");
在对应api接口中, res.render('index_ejs', { user:{name:'小明',age:18} ,title:'hello ejs'}) 替代send()方法
`<%=` 输出数据到模板
<% '脚本' 标签,用于流程控制,无输出。(if / 循环)
`%>` 一般结束标签
<%- include 包含一段另外的模板标签 (在A.ejs 可以引入 B.ejs的代码)
<%- include('./user/show', {user: user}); %>
//==========模板里代码==========
<%= title %>
<% if (user) { %>
<%= user.name %>
<%= user.age %>
<% } %>
<% theArr.map(obj => { %>
-
<%= obj['name'] %>
<%= obj['hobby'] %>
<% }) %>
// 捕获错误,用过next传递给下一个中间件
app.use((req, res, next) => {
let err = new Error('Not Found'); // 新建错误对象
err.status = 404; // 给错误对象扩展status属性, 值为404
next(err); // 把错误对象, 传递给下一个中间件
});
app.use((errObj, req, res, next) => {
res.status(errObj.status); // 设置响应状态码为404
res.render("404.ejs", {status: errObj.status, msg: errObj, title: "资源走丢了!!"});
});
//注意: 一般写在所有api路径最后.
在服务器端, 把模板标签渲染完毕, 把整个html上内容直接返回给浏览器显示
浏览器请求网页资源
再从浏览器发送Ajax请求数据
JS操作DOM, 铺设数据
回顾异步任务, Promise的使用, 还有async+await的使用
思考: setTimeout() 如果给0秒, 会马上执行吗?
// 1. 任务的划分:
// js的代码的任务, 分哪个东西的调度?
// (1): JS引擎 -> 异步 -> Promise(调用动作) (微任务)
// (2): 浏览器宿主 -> 异步 -> XMLHttpRequest(Ajax) / setTimeout / setInterval / ....... (宏任务)
// 2. 主线程任务(CPU) / 微任务 / 宏任务
// 执行顺序: 先执行全部的主线程的同步任务(代码), 全都执行完毕有空闲了, 才开始调度微任务, 所有的微任务执行完毕, 再开始调度宏任务执行
// async函数 + 主线程 + 微任务 + 宏任务
// await内其实有一个resolve, await的时候, 是先执行后面的代码
// await这里等待就进入到微任务, 排队
// 主线程开始执行
// promise开始执行 (因为new Promise一旦被创建就会马上执行回调函数里的代码)
// resolve被调用以后 (注意上面没有await, 所以代码会执行完毕)
// 主线程执行完毕
// 接收到了resolve的动作
1. Webpack 前端资源模块化管理和打包工具。可以将许多松散的模块按照依赖和引用关系打包成符合生产环境部署的前端资源。并将按需加载的模块进行代码分隔,等到实际需要的时候再加载。
2. webpack 运行在node环境上的一个 包
// webpack 可以把前端的任何资源, 当做模块, 来进行打包整合, 也可以支持不同的代码(ES6模块代码, CSS文件, LESS文件, 图片....)
// 编写前端代码后, 可以被webpack打包整合, 运行在浏览器上
// webpack当成一个人物养成游戏
1.开发的时候需要一个开发环境,要是我们修改一下代码保存之后浏览器就自动展现最新的代码那就好了(热更新服务)
2.本地写代码的时候,要是调后端的接口不跨域就好了(代理服务)
3.为了跟上时代,要是能用上什么ES678N等等新东西就好了(翻译服务)
4.项目要上线了,要是能一键压缩代码啊图片什么的就好了(压缩打包服务)
1. 丰富的插件,流行的插件, 方便进行开发工作。
2. 大量的加载器,便于处理和加载各种静态资源。
3. 将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载.
相对于其他模块打包工具(Grant/Gulp)优势
1. Webpack 以 CommonJS 的形式来书写脚本,对 AMD / CMD / ES6 模块 的支持也很全面,方便旧项目进行代码迁移。所有资源都能模块化。
2. 开发便捷,能替代 Grunt / Gulp 的工作,比如打包js/css、打包压缩图片、CSS分离, 代码压缩等。扩展性强,插件机制完善,支持模块热替换等
node是nodejs运行的环境, npm是安装node一起安装的包管理器的工具, 可以方便的管理我们需要的所有第三方依赖包
webpack通常使用npm包管理工具进行安装。现在webpack对外公布的稳定版本是webpack4
全局安装webpack : 命令 npm install webpack -g
命令 | 安装环境 | 备注 |
---|---|---|
npm view webpack versions --json | 不安装, 查看 | 查看现在所有webpack模块的版本号 |
npm install webpack -g | -g 全局安装 | 在全局安装webpack 在电脑就可以使用webpack命令了(工具类模块要全局) |
webpack -v | 不安装, 查看全局webpack版本号(注意, webpack4.x版本, 还要安装webpack-cli工具才可以运行此命令) | 可能出现的问题: 1. webpack不是内部或外部命令(证明你全局安装失败/计算机的环境变量node的配置失效) |
webpack的命令, 大多都会执行webpack-cli里的Api方法, 来实现具体的功能效果, 所以webpack4.x版本需要在全局安装此模块, 而webpack3.x没有抽离出来那些API方法, 所以webpack3.x则不需要安装此模块
命令: npm i webpack-cli -g
注意webpack4 配合 webpack-cli3.x版本
本地开发环境(development): 我们在本地写代码的时候
线上发布环境(production): 我们在本地开发完代码, 进行打包后, 对外的环境
Webpack为开发者提供了程序打包的配置信息入口,让开发者可以更好的控制, 管理程序的打包过程与最后程序的输出结果。默认的webpack配置文件是webpack.config.js, 运行webpack打包命令, 会自动查看运行命令时, 所在目录下的webpack.config.js文件
注意: webpack4.x版本可以省略这个文件, webpack3.x版本是必须声明此文件的
官网链接: https://www.webpackjs.com/concepts/
webpack的概念名 | 解释 |
---|---|
入口起点 | 基础目录, 指定了"./src"目录, 那么下面所有的配置中使用的相对路径, 都是以src为起点 |
入口 | 入口起点指示 webpack 应该使用哪个模块来作为构建其内部依赖图的开始 进入起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的 |
出口 | output告诉 webpack 在哪输出它所创建的结果及如何命名文件,默认值为 ./dist |
加载器 | loader 让 webpack 能去处理非 JavaScript 文件(webpack 自身只理解 JavaScript)loader 可以将所有类型的文件转换为webpack 能够处理的有效模块 然后你就可以利用 webpack 的打包能力,对它们进行处理。 |
插件 | loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。 插件接口功能极其强大,可以用来处理各种各样的任务。 |
模式 | 通过选择 development 或 production 之中的一个,来设置 mode 参数, 你可以启用相应模式下的 webpack 内置的优化 |
官网链接: https://www.webpackjs.com/configuration/
键名 | 概念 | 解释 |
---|---|---|
context | 入口起点 | 基础目录,绝对路径,用于从配置中解析入口起点(entry point) |
entry | 入口 (必须) | 配置打包入口文件的名字 |
output | 出口 (必须) | 打包后输出到哪里, 和输出的文件名 |
module | 加载器配置 | 在rules对应数组中, 定义对象规则 |
plugins | 插件配置 | 配置插件功能 |
mode | 模式 | 选择线上/线下环境模式 |
devtool | 开发工具 | 如何生成 source map, 记录代码所在文件的行数 (调试方式) |
准备前端模块js文件, 和主入口文件main.js(名字自定义), 在主入口文件里使用前端封装的模块
在当前工程目录中声明webpack.config.js的 webpack配置文件, 并且填入配置对象信息(入口+出口必须的)
dist不存在会自动创建
output.path的值必须是绝对路径 (因为webpack是从全局开始创建dist目录, 所以必须从全局出发)
在当前工程目录中执行webpack打包命令, 查看出口生成的打包后的js文件
自己新建index.html文件引入打包后的js, 执行查看效果
webpack命令会自动查找当前命令所在目录下的 webpack.config.js 配置文件, 根据配置文件进行代码的打包
单个入口, 可以引入很多个要使用的模块部分, 单入口(指的是打包时候指定的入口文件)
单个出口, 指的打包所有js, 最后要输入到一个单独的.js文件当中使用
module.exports = {
context: __dirname + "/src", // 拼接src的绝对路径, context设置入口的文件前缀, 代表入口要从这个文件夹开始寻找 (必须是绝对路径) __dirname: 指的当前文件所在文件夹的路径路径
entry: "./main.js",
output: {
path: __dirname + '/dist', // 给我输出到同级下的dist目录中(如果没有会自动创建)
filename: 'bundle.js' // 输出js文件的名字
}
};
多入口: 告诉webpack, 去哪些文件里进行打包
module.exports = {
context: path.resolve(__dirname, "src"),
entry: ["./main.js", "./center.js"], // 设置多入口文件路径
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
}
};
module.exports = {
context: path.resolve(__dirname, "src"),
entry: {
"first": "./main.js",
"second": "./center.js"
}, // 如果是多入口单出口用数组结构, 如果是多入口, 多出口用对象结构, 而且key值是打包后的文件名
output: {
path: __dirname + '/dist',
filename: '[name].js' // [name]是webpack内置的字符串变量, 对应entry里每个key
}
}
1、需要下载2个加载器模块 (目的是为了让webpack认识css文件)
css-loader: 接收一个css文件, 并且解析import方式
style-loader: 接收css代码, 并且将其注入到html网页中的
2、在webpack.config.js中, 加载器配置
module: { // 对加载器进行配置
rules: [ // 加载器的使用规则
{ // 独立的规则对象
test: /\.css$/, // 以.css结尾的文件类型
use: [ 'style-loader', 'css-loader' ] // 使用哪些加载器进行转换
// 注意: 2个加载器的顺序, 默认是从右往左进行使用
}
]
}
3、在入口文件引入css模块
import "./style/home.css" // 注意无需用变量接收
4、会把css代码以字符串的形式, 打包进js文件当中
5、在dist下新建index.html, 只要引入打包后的bundle.js, 来查看css代码被打包进js的效果即可
1、需要下载1个插件模块
html-webpack-plugin: HtmlWebpackPlugin
简化了HTML文件的创建,你可以让插件为你生成一个HTML文件,使用默认模板, 或使用你自己指定的模板
2、webpack.config.js插件配置
const HtmlWebpackPlugin = require("html-webpack-plugin");
plugins: [ // 配置各种插件
new HtmlWebpackPlugin({ // 插件配置对象
title: "webpack ldx使用",
filename: "index.html", // 产出文件名(在dist目录查看)
template: __dirname + "/index.html", // 以此文件来作为基准(注意绝对路径, 因为此文件不在src下)
inject: true, // 代表打包后的资源都引入到html的什么位置
favicon: "./assets/favicon.ico", // 插入打包后的favicon图标
// base: "./", // html网页中所有相对路径的前缀 (一般不给/给./, 虚拟路径)
// 控制html文件是否要压缩(true压缩, false不压缩)
minify: { //对html文件进行压缩,
collapseBooleanAttributes: true, //是否简写boolean格式的属性如:disabled="disabled"简写为disabled
collapseWhitespace: true, //是否去除空格,默认false
minifyCSS: true, //是否压缩html里的css(使用clean-css进行的压缩) 默认值false
minifyJS: true, //是否压缩html里的js(使用uglify-js进行的压缩)
removeAttributeQuotes: true, //是否移除属性的引号 默认false
removeComments: true, //是否移除注释 默认false
removeCommentsFromCDATA: true, //从脚本和样式删除的注释, 默认false
useShortDoctype: true //使用短的文档类型,将文档转化成html5,默认false
}
}) // 数组元素是插件new对象
]
3、src/index.html 静态网页模板
4、执行webpack打包命令, 观察在dist生成的目录中, 是否新增了xxx.html文件, 并且会自动引入所有需要的外部资源
Cannot find module "webpack/lib/node/NodeTeplatePlugins"
在安装html-webpack-plugin插件的工程中, 单独的在本地安装一下跟全局webpack对应的版本
选项key | 值类型 | 默认值 | 解释 |
---|---|---|---|
title | String | Webpack App | 在生成html网页中 |
filename | String | index.html | 生成的html网页文件的名字 (也可以设置目录+名字) |
template | String | 以哪个现有的html文件作为基础模板, 在此模板的基础上, 生成html网页文件 | |
inject | Boolean/String | true | 值的范围(true || 'head' || 'body' || false) true/'body' -> script等引入代码, 放到body标签内部末尾 'head'/false -> script等引入代码, 放到head标签内部末尾 |
favicon | String | 将制定favicon.ico图标的路径, 插入到html网页中去 | |
base | String | 制定html中所有相对路径, 都以它的值为出发起点, 例如: base的值为/bar/, 那么你HTML网页里的img, src="my.img", 那实际上去找的路径其实是 /bar/my.img | |
minify | Boolean | 看mode的值 | 是否压缩html代码, 如果mode为'production', 那么minify的值为true, 否则为false |
需要引入1个插件模块
extract-text-webpack-plugin@next: 会将所有的入口中引用的*.css
,移动到独立分离的CSS文件。因此,你的样式将不再内嵌到JS中,而是会放到一个单独的CSS文件中。如果你的样式文件较大,这会做更快加载,因为CSS会跟JS 并行加载。
此插件没有压缩css代码的功能
1、webpack.config.js加载器修改:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
rules: [ // 加载器的使用规则
{
test: /\.css$/,
use: ExtractTextPlugin.extract({ // 从一个已存在的 loader 中,创建一个提取(extract) loader。
fallback: "style-loader", // 应用于当CSS没有被提取(正常使用use:css-loader进行提取, 如果失败, 则使用fallback来提取)
use: "css-loader" // loader被用于将资源转换成一个CSS单独文件
})
}
]
2、插件配置:
其他选项默认即可
new ExtractTextPlugin("style.css"), // 输出的文件名
3、在dist打包生成的目录中, 就会分离出单独的.css文件
Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead
"extract-text-webpack-plugin": "^3.0.2" 此插件3.x版本对应webpack3.x, 所以我们需要更高的extract版本, 所以下载extract-text-webpack-plugin@next (@next下载下一个内测最新版)
1、需要安装less 和 less-loader 来解析less代码, 和加载less文件
2、在webpack.config.js中 配置加载器, 解析.less文件
{
test: /\.less$/,
use: ['style-loader', 'css-loader', "less-loader"]
}
3、但是这样发现css代码没有分离出来, 所以还需要使用extract-text-webpack-plugin的配置, 分离出css代码
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ['css-loader', "less-loader"]
})
}
4、观察打包后style.css中多了less文件里的样式代码
是一个转换 CSS 代码的工具和插件 (postcss转换css代码, 为了兼容不同的浏览器)
类似于babel.js把浏览器不兼容的js转换成兼容的js代码 (babel转换js代码, 为了兼容不同浏览器)
注意它本身是一个工具, 和less/sass等预处理器不同, 它不能处理css代码
而是靠各种插件来支持css在不同浏览器和环境下正确运行的
增加可读性, 会自动帮你添加特定浏览器厂商的前缀 (插件: autoprefixer)
px单位自动转rem (插件: postcss-pxtorem)
先下载postcss-loader 和postcss到当前工程中
postcss: 集成这个工具, 可以让它发挥它集成的翻译css的插件
postcss-loader: 对css文件进行处理
新建webpack.config.js同级的postcss.config.js 配置文件
去webpack.config.js中, 把postcss使用到css相关的加载器中
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [{
loader: 'css-loader',
options: { importLoaders: 1 }
}, "postcss-loader"]
})
// importLoaders 用于配置「css-loader 作用于 @import 的资源之前」有多少个 loader。
},
在css和less文件中, 准备一些代码
自动补全前缀,
1.先下载此插件模块: npm i autoprefixer
2.postcss.config.js 配置如下:
module.exports = {
plugins: { // postcss在翻译css代码的时候, 需要使用哪些插件功能
// 1. 写使用插件模块的名字, postcss会自己去require引入
// 2. 必须配置浏览器列表才可以 自动添加前缀
'autoprefixer': {
// 浏览器支持列表放到了package.json中browserslist中进行编写
}
}
}
package.json的browserslist下设置
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"iOS 7",
"last 3 iOS versions"
]
// defaults相当于 "> 5%", 国内浏览器使用率大于5%的
// not ie < 11 不兼容IE11以下的浏览器 (支持ie11)
// 支持最后2个版本
// iOS苹果手机操作系统, 支持ios7
// 支持最后3个IOS的版本 ios13, 12, 11
打包观察生成的style.css文件中代码是否拥有浏览器兼容的前缀
浏览 && 画图, 解释rem 如何适配的
此插件是自动把(css/less..文件里 px转换成适配的rem单位), 无需再手动计算了
先下载此插件模块 npm i postcss-pxtorem
在postcss.config.js中配置如下
'postcss-pxtorem': {
rootValue: 16, // 这个值就是你看设计稿上基准字体是多少, 就写多少, 1rem=16px
unitPrecision: 6, // 小数点几位
propList: ['*'], // 指定需要转换rem的属性 例如: 'font-size' 'line-height' *代表所有属性
mediaQuery: false, // 媒体查询的px是否转换
minPixelValue: 0, // 小于指定数值不转换
// 默认px识别大写小, 所以不写成px不转换
}
注意: 只对css/less文件内代码有效, 因为webpack.config.js中, 加载器使用了postcss-loader
注意: 如果html中使用px转rem, 可以安装插件, 来自动把px转换成rem使用
注意: html的font-size不会自动随着网页的变化而变化
在index.html模板文件中, 根据当前网页设置html的fontSize, 来让所有rem单位在运行时得到真正的像素大小
想要压缩打包后的css文件, 可以使用 optimize-css-assets-webpack-plugin, 先下载这个插件
在webpack.config.js中配置
const OptimizeCss = require('optimize-css-assets-webpack-plugin');
plugins: [ // 新增
new OptimizeCss()
]
需要引入2个加载器模块
url-loader: url-loader
功能类似于 file-loader
,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL(base64字符串)
file-loader: 产出, 寻找文件的引用位置
准备工作: 注意打包的资源, 都要和入口文件产生直接/间接关系, 所以不要写在index.html模板文件中, 那样是不会被webpack处理的
assets 下准备 图片 和字体图标
在main.js中, 创建标签, 使用图片/字体图标样式
注意: webpack认为, 图片也是一个模块, 所以才需要loader来解析图片, 所以图片需要import/require引入)
1、webpack.config.js加载器配置:
/*{
test: /\.(png|jpg|jpeg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: { // 参数
limit: 8192 // 8kb内的文件都转换成DataURL, 如果超出的都转换成base64字符串
}
}
]
},
*/
// 上面options可以简写成下面?传参数的格式
{
test: /\.(png|jpg|jpeg|gif|svg)$/, // 处理这些结尾的文件
use: [
{ // options传参的方式被我改成了?传参
// ? 代表给加载器传入配置参数
// limit字段: 对打包的图片限制字节大小为8KB以内, 超出此限制,会被file-loader产生一个文件
// [name]: 代表直接使用打包目标文件的名字,
// [ext]: 代表直接使用打包目标文件的扩展名
// name字段: 代表给打包后的文件定义名字(可以使用[]这种规则)
// 8KB
loader: 'url-loader?limit=8192&name=assetsDir/[name].[ext]'
}
]
}
// 如果处理字体图标需要引入这个
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
2、执行打包命令, 查看dist目录中和index.html中的效果
总结: 小于limit限制的assets下的图片资源, 都会被打包进js文件中, 先被url-loader转换成了base64字符串 (这种加密方式的字符串, 可以被img标签的src所接受, 但是一定要在base64加密的字符串前, 声明一个表示 data:image/png;base64, 后面跟base64字符串)
需要引入1个插件模块
copy-webpack-plugin (指定版本@5.1.1)
注意: 非打包的资源, 不再被当做模块使用, 所以我们只能使用路径引入的方式, 不要使用import/require
注意: 引用的相对路径, 要以打包后dist目录中index.html为起点, 来查找static (而且static也会被复制到dist中)
1、配置插件
new CopyWebpackPlugin([{
from : __dirname + "/static",//从哪个文件夹开始复制
to : __dirname + "/dist/static"//复制到那个文件夹
}])
2、在src同级下, 声明static文件夹, 放置我们的静态资源
3、使用static资源, 需要直接写打包后的相对路径, 项目要打包以后放到服务器上查看
静态资源存放位置 | 使用在哪里 | 资源大小 | 产出到哪里 | 备注 |
---|---|---|---|---|
assets (会被webpack处理) | img标签 | 超过8KB | dist/生成独立文件 | import 图片 |
assets (会被webpack处理) | img标签 | 小于8KB | 打包进了js中, base64字符串 | import 图片 |
assets (会被webpack处理) | css背景 | 超过8KB | url'图片路径' | import 图片 |
assets (会被webpack处理) | css背景 | 小于8KB | url('base64字符串") | import 图片 |
assets (会被webpack处理) | i标签 | 判断10KB/8KB | 使用了字体和字体样式(超过限制生成独立文件, 小于限制大小, 打包进js中) | import css样式文件 |
static (不会被wp处理) | img标签 | 超过8KB | copy了src同级下static到dist目录中 | 写static的路径 |
static (不会被wp处理) | img标签 | 小于8KB | copy了src同级下static到dist目录中 | 写static的路径 |
static (不会被wp处理) | css背景 | 超过8KB | copy了src同级下static到dist目录中 | 写static路径 |
static (不会被wp处理) | css背景 | 小于8KB | copy了src同级下static到dist目录中 | 写static路径 |
static (不会被wp处理) | i标签 | 不用管 | copy了src同级下static到dist目录中 | 需要在模板index.html中引入css文件 |
总结:
小于8KB的放在assets中, 会被webpack打包进js中, 减少http请求文件的次数, import/require引入静态资源
太大的文件, 直接放在static中, 直接引用地址就可以了
在webpack打包的项目中, 使用jQuery功能
1、下载jquery模块, npm i jquery
2、在webpack中配置resolve, 模块解析方式
resolve: { // 配置如何解析模块的引入
alias: { // 别名
// import xxx from 'jquery' 实际上 运行时是 import xxx from 'jquery/dist/jquery.min.js'
jquery: 'jquery/dist/jquery.min.js',
}
}
3、在main.js中引入jquery, import $ from 'jquery', 创建标签插入到网页中, 在前端使用了npm下载的模块
// 1. npm init初始化的文件夹, 其实跟node_module里的第三方包模块文件夹 是一个意思
// 2. require/import引入此文件夹时, 实际上引入的是此文件夹的包入口模块(package.json下main属性指定的)
// 默认路径索引
// 模块内: 看package.json 里 main 指定的
// 普通文件夹: index.html
此选项控制是否生成,以及如何生成 source map。
source_map, 记录代码行数
原因: 因为浏览器运行的是打包后的js文件, 打包后的文件无法定位错误所在行数和源文件的位置...
配置webpack.config.js中的devTool选项:
eval模式: 这种方式是将当前每个模块解析为字符串,并由eval进行包裹
优点是模块内的代码在废弃或不进行使用时不会占用过多的系统内存。提升内存的优化效率
缺点文件打包后的文件会略大。
source-map模式: 最原始的 source-map 实现方式,就是打包代码同时创建一个新的 sourcemap 文件(sourcemap文件记录了当前文件代码的位置信息),并在打包文件的末尾添加 //# sourceURL 注释行告诉 JS 引擎文件在哪儿。当我们对文件进行调试的时候调试的文件行与列对应源文件中的行与列方便调试。
hidden-source-map模式:这种方式与source-map一致,唯一的区别是没有了sourcemap的注释,完全通过文件名称进行查找。
inline-source-map:这种方式不会生成sourcemap文件,而是将sourcemap文件通过base64转译写到了打包文件最后的注释中。
eval-source-map:这种方式同样也不会生成sourcemap文件,他将sourcemap进行base64转译以dataurl的形式写到了eval的注释中
cheap-source-map:这个方式会生成sourcemap文件,不过sourcemap文件中不会记录当前代码的列信息,只会记录行信息,并且不会生成loader中的文件的sourcemap。
cheap-module-source-map:这个方式会生成sourcemap文件,只记录行信息,并且简化loader的相关内容。
注意: 配置模式不只是上面几种, 我们还可以随意组合, 比如我们常用 cheap-module-eval-source-map 方式
在webpack.config.js中 配置选项: devtool: " cheap-module-eval-source-map",
在main.js中, 随便写点JS报错的代码, 观察没加devTool和加了之后, 报错提示的代码位置的序号
(可选): 有的模式会生成 source-map文件, 它用来记录代码和代码文件行数的位置信息
webpack-dev-server会实时监听文件变化, 自动打包到内存中, 而不是生成硬盘上的dist目录
webpack-dev-server 它还会启动一个本地的服务器, 来支持你访问打包后的html网页
下载webpack-dev-server -g 要全局安装, 因为它也是一个终端中的命令
webpack-dev-server --version (注意是两个 杠 )
在项目下运行 命令: webpack-dev-server 即可启动热更新服务器, 会根据运行时所在目录下的webpack.config.js进行打包
注意: 如果修改了配置文件, 还是需要重启终端, 如果是src下的代码, 会自动打包
注意: 默认需要打包输出的文件名必须为 index.html文件
默认在端口号:8080上启动我们热更新服务器, 项目都在这个服务器上打开访问
可以对热更新模块创建的服务器, 进行一些设置, 在webpack.config.js中添加
devServer: { // 在这里可以对webpack-dev-server进行一些配置
stats: 'minimal', //只在发生错误或有新的编译时输出
port: 9090,
open: true, // 自动调用默认浏览器打开
}
其他的设置如下: https://www.webpackjs.com/configuration/dev-server/#devserver-open
前提需要配合热更新服务器webpack-dev-server配合使用才可以
只需要在devServer中添加配置即可, 实现代理服务器的使用, 帮你转发前端发起的API请求, 转到真正的后台地址去请求数据
解析跨域原因 和 代理转发原理图
前端请求本地的API接口 (不要写http://)
$.ajax({
url: "/nc/article/headline/T1348647853363/0-20.html",
success (res) {
console.log(res);
}
})
devServer: {
proxy: { // 代理转发的规则使用
"/nc": {
target: "http://c.m.163.com",
changeOrigin: true, // 发送请求头中host会设置成target 让网易新闻知道发起请求的是c.m.163.com而不是localhost
}
}
}
虚拟路径 (为了统一前缀, 统一转发到同一个后台接口服务器地址)
$.ajax({
url: "/api/nc/article/headline/T1348647853363/0-20.html",
success (res) {
console.log(res);
}
})
"/api": {
target: "http://c.m.163.com",
changeOrigin: true, // 发送请求头中host会设置成target 让网易新闻知道发起请求的是c.m.163.com而不是localhost
pathRewrite: {"^/api" : ""} // 如果用了虚拟路径, 统一前端请求的前缀, 那么这里就要把虚拟路径处理掉, 因为真正要请求的后台接口没有这部分 (把/aaa虚拟路径, 替换成空字符串)
}
后台开启跨域支持
app.all("*", (req, res, next) => { // 统一开启跨域支持
res.header("Access-Control-Allow-Origin", "*"); // 开启跨域支持 (重要)
res.header("Access-Control-Allow-Headers", "*"); // 前端可以使用任意的请求头
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); // 前端可以使用的请求方法
res.header("Access-Control-Expose-Headers", 'Manager'); // 允许前端获取的响应头
next();
});
找插件/加载器, 下载插件/加载器模块, webpack.config.js中进行配置, 编码/准备资源, 打包, 把打包后的资源部署到服务器上
部署: 配置环境和需要的各种软件参数等, 上传代码资源包
命令举例 | 解释 |
---|---|
webpack | 默认找运行命令时, 所在目录中的webpack.config.js的配置进行打包 |
webpack --config xxx | 可以指定某个文件作为配置文件, 进行打包 |
webpack -h | 查看帮助 |
webpack --json | 以JSON格式打印终端中启动的统计信息, 可以使用webpack --json > stats.json, 把统计信息不再终端中显示, 保存到stats.json文件中查看 |
webpack --env.production | webpack --env.production # 设置 env.production == true / 设置development用于代码中判断当前执行webpack命令, 生成东西所使用到的环境 |
webpack --progress | 显示打包的进度 |
webpack --watch | webpack打包结束会继续监视文件变化, 不会退出命令, 而且当文件变化时, 会自动触发打包任务执行 |
webpack --display | 会根据webpack.config.js中devServer对象中的stats配置预设 |
其他请参考 | https://www.webpackjs.com/api/cli/#%E4%BD%BF%E7%94%A8%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E7%94%A8%E6%B3%95 |
__dirname (注意2个下划线): 代表当前文件所在文件夹的 绝对路径
path.resolve: 合并2个路径
https://www.webpackjs.com/plugins/
https://www.webpackjs.com/loaders/
base64字符串: 是一个加密方式, 会把数据(任何类型)加密成数字+字母的一串很长的字符串组合, 这种加密方式可以进行解密
注意: 需要在src指定的data64字符串前拼接data:image/jpeg;base64, 来告诉img标签, 你要加载的是图片的base64字符串数据
是指实现某特定功能的一组方法和代码。可以更方便地使用别人的代码, 需要功能引入, 不需要的功能不引入
模块内东西想被别人使用, 必须暴露
想使用模块里的方式/属性, 必须引入
网页过多的script引入, 导致管理混乱, 因为最初javaScript没有模块化概念.无法将一个大程序拆分成互相依赖的小文件,多个插件内的变量容易互相影响
Nodejs require, Ruby 的require、Python 的import,甚至就连 CSS 都有@import 引入模块的功能
ES6没出现前, 制定了一些JS的模块化开发方案, 比如CommonJS 和 AMD/CMD 两类。前者用于服务器,后者用于浏览器。
ES6制定了非常简单的模块化解决方案, 完全可以取代 AMD/CMD 规范,成为浏览器模块解决方案, requirejs插件/sea.js插件来模拟前端的模块化引入和导出
以前大家都用CMD和AMD规范来封装模块, ECMASCript6官方更新了模块功能, 于是我们可以用ES6模块化规范来一统江湖了
但是现在浏览器对ES6模块化支持还不是特别好, 所以需要借助babel等编译工具, 把ES6的模块化代码转换成ES5代码去运行
//===========导出: 定义模块
export function add (a, b) {
return a + b;
}
export function add2(a, b, c) {
return a + b + c;
}
export function max(a, b, c) {
return a > b ? (a > c ? a : c) : (b > c ? b : c);
}
export default { // 只要这样外面才可以用变量接收
add,
add2,
max
}
//===========导入前提准备
或者使用 script type="module" 的形式也可以
//===========导入: import xxx from ...
import myAdd from './add.js';
import {sub} from './sub.js';
console.log(sub(5, 3));
规范名/标准 | 浏览器端/服务器端 | 导出 | 导入 | 备注 |
---|---|---|---|---|
CommonJS(偶尔用) | 服务器端 | exports.xxx / module.exprots | require | exports. 是module.exports 辅助工具, 所以最后还是要module.exports |
CMD(Seajs) | 浏览器端 | define() | seajs.use() | seajs.config()可以配置别名 模块的工厂函数, 使用时只初始化一次 |
AMD(Requirejs) | 浏览器端 | define() | define() / 参数不同 | 在script中data-main属性引入主入口.js文件 |
ES6(代替之前AMD/CMD)(常用) | 浏览器端 | export / export default | import..from... | 如果想要引入模块内某几个方法/属性, 使用export导出, 如果想让模块使用者得到全部的方法和属性, 使用export default导入对象 |
import {add} from 'xxx.js' -> export const add = () => {};
import myObj from 'xxx.js' -> export default {}
模块的核心规则: 定义+导出 导入+使用
1. 每个模块只加载一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个对象l
2. 每一个模块内声明的变量都是局部变量, 不会污染全局作用域
3. 模块内部的变量或者函数, 通过export / export default导出
即时通信, 就是能够实时进行对话, 无需主动发送请求的一种连接技术. (场景: 直播/聊天/语音...)
缺点:
轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,
然后由服务器返回最新的数据给浏览器。这种传统的模式带来很明显的缺点,
显然这样会浪费很多的带宽等资源。
根本原因: 服务器不会主动推送消息给客户端, 我们用的是http协议.无法实现长连接
HTML5新出的API, 是WebSocket, 一种在单个 TCP 连接上进行全双工通讯的协议, WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向, 浏览器推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手, 两者之间就直接可以创建持久性的连接,并进行双向数据传输。
这是一个基于WebSocket封装的前后端通用的即时通信模块
前后端都要实现socketio才能实现即时通信
前后端绑定/触发同名的事件 (核心思想 on / emit)
后端基于[Engine.IO](https://github.com/socketio/engine.io)进行的封装来实现WebSocket服务器
使用Socket.io目的: 为了获得更多的功能(如果使用ws模块, 需要自己封装很多的功能)
注意: socket.io 不在使用send方法, 而是替换成on监听自定义事件, emit触发事件传送参数
这里是socket的服务器端
中文文档: https://www.w3cschool.cn/socket/socket-odxe2egl.html
英文文档: https://github.com/socketio/socket.io/blob/master/docs/API.md
还是先搭建一个基于express的服务器
const server = app.listen(5050)
用服务器对象初始化socket的IO流对象
const io = require('socket.io')(server);
监听浏览器的连接
io.on('connect', (socket)=>{})
向连接的当前客户端发送欢迎信息
socket.emit("send_to_client", "恭喜你链接上了!");
监听前端触发的事件, 同时传值给服务器端
socket.on('news', (data)=>{});
向所有客户端广播发送信息, 触发前端监听的事件
io.sockets.emit("send_to_client", data);
发送给除了主动发送者以外的 所有客户端
socket.broadcast.emit("send_to_client",data);
//=============示例
const express = require('express');
const app = express();
// 在express应用上集成即时通信
const server = app.listen(3000);
// 1. 引入socket.io, 传入服务器对象, 让socket.io注入到web网页服务里
const io = require("socket.io")(server);
// 2. 监听浏览器端的连接事件 (是socket的连接, 而非地址的访问)
io.on('connect',(WebSocketObj)=>{// connect是固定的, 叫连接的事件
//1接收前端url传递过来的房间id
let {query:{roomid}} = WebSocketObj.handshake;
console.log(WebSocketObj.handshake['query']);
// query: { roomid: '1111', EIO: '3', transport: 'polling', t: 'N9LoXzC' }
WebSocketObj.join(roomid,()=>{ //2加入指定房间
WebSocketObj.emit("send_to_client",`恭喜加入 ${roomid} 房间成功!`);
});
WebSocketObj.on("news",(data)=>{//3监听前端发送消息, 前端需要传递roomId过来, 要广播给这个房间内所有的人
console.log(data)
io.to(data['roomid']).emit("client_message",{// io.to()里传入的是房间号, 代表只给这个房间号里的浏览器连接对象, 触发client_message事件
nickName:data['userName'],
message:data['msg']
});
});
})
这里是socket的客户端
中文文档: https://www.w3cschool.cn/socket/socket-k49j2eia.html
英文文档: https://github.com/socketio/socket.io-client/blob/master/docs/API.md
中文文档里都是常用的, 如果查不到去英文文档里查找
引入socketio插件
连接服务器
const socket = io("http://localhost:5050"); //得到io方法, 调用传入要连接的即时通信服务器的ip地址+端口号
监听是否连接上了服务器 (可选)
socket.on("connect", ()=>{
console.log("链接上了");
});
向服务器主动发送消息
socket.emit("news", {
'name': '小豪',
'msg': "吃了没?"
});
监听从服务器发过来的信息
```javascript
socket.on("send_to_client", (data)=>{
console.log(data);
})
//=====示例
//1 加入房间 连接即时通讯服务器 传入房间id
let socket;
function joinRoomFn(){
let theRoomId = $("#roomInp").val();
if(theRoomId == ''){alert('房间号不能为空!');return;}
socket = io(`http://127.0.0.1:3000?roomid=${theRoomId}`);
socket.on("send_to_client",(arg)=>{// 接收欢迎消息
alert(arg);
})
socket.on("client_message",(obj)=>{
let {nickName,message} = obj;
let elStr =`${nickName}说: ${message}
`;
$("#result").html($("#result").html() + elStr);
})
}
//2 发送消息
function send(){
if(socket === undefined){
alert("请先连接房间");
}else{
socket.emit("news",{
userName:$("#userInp").val(),
msg: $("#myInp").val(),
roomid: $("#roomInp").val()
});// 触发后台的news事件, 把值传给后台
}
}
1. 后台接收前端连接socket传递过来的id: socket.handshake['query']['id'] (id指的是连接着的身份/要加入的房间标识)
2. 把id加入到房间中 socket.join(memberId, () => {});
3. 根据id, 发送信息 io.to(data['id']).emit("send_to_client", data['msg']);
环信SDK
容联云SDK
只要前后端使用的技术 对上, 就能正常连接
RESTful架构,是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源标志符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。综合上面的解释,我们总结一下什么是RESTful架
每一个URI代表一种资源;(URI包含URL)
客户端和服务器之间,传递这种资源的某种表现层;
客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
HEAD(SELECT)只获取某个资源的头部信息
GET(SELECT)获取资源
POST(CREATE)创建资源
PATCH(UPDATE)更新资源的部分属性(很少用,一般用PUT代替)
PUT(UPDATE)更新资源,客户端需要提供新建资源的所有属性
DELETE(DELETE)删除资源
GET http://www.u.com/api/user # 获取列表
POST http://www.u.com/api/user # 创建用户
PUT http://www.u.com/api/user/{id} # 修改用户信息
DELETE http://www.u.com/api/user/{id} # 删除用户信息
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
全局安装koa-generator: 命令如下: npm i koa-generator -g
使用脚手架初始化项目, 命令如下: koa2 myKoaApp (如果需要ejs模板, 在koa2 后 加 -e)
进入目录, 安装所有需要的模块, 命令如下: cd myKoaApp && npm install
启动项目: npm start
浏览器访问 http://localhost:3000页面
主线程执行完, 再执行所有await, 最后默认返回ctx.body内容给前端
绕过 Koa 的 response 处理是 **不被支持的**. 应避免使用以下 node 属性:
res.statusCode
res.writeHead()
res.write()
res.end()
koa 把 express 中内置的 router、view 等功能都移除了,使得框架本身更轻量化。
Koa使用了Promise配合Generator函数, 来组合各种中间件使用, 做到异步代码同步化使用
框架 | 说明 | 对应 | 特性 |
---|---|---|---|
express | web框架 | es5 | 回调嵌套 |
koa | web框架 | es6 | Generator函数+yield语句+Promise |
koa2 | web框架 | es7 | async + await + Promise |
Babel 是一个工具,主要用于将 ES5以后以及React中JSX 等语法代码转换为向下兼容的 JavaScript 语法, 以便能够运行在当前和旧版本的浏览器或其他环境中.