前后端交互笔记
node.js
fs 模块
node官方提供的,操作文件的模块。
- fs.readFile() 读取内容
- fs.writeFile() 写内容
fs.readFile(path[, options], callback) 读取
- options 表示以什么编码格式来读取文件
const fs = require('fs');
fs.readFile('file/abcd.txt', 'utf-8', function(err, dataStr) {
if (err) {
return console.log(err.message);
}
console.log(dataStr);
})
fs.writeFile(file, data[, options], callback) 写入
- data 要写入的内容
- options 编码格式
var fs = require('fs');
fs.writeFile('./file/2.txt', '你好啊', 'utf-8', function(err) {
if (err) {
return console.log('文件写入失败' + err.message);
}
console.log('文件写入成功');
})
相对路径的写法,在非js所处当前文件夹路径下执行时可能会出错。所以要用动态路径拼接
const fs = require('fs');
// __dirname 动态获取当前所在路径
fs.readFile(__dirname + '/file/成绩-ok.txt', 'utf-8', function(err, dataStr) {
if (err) {
return console.log(err.message);
}
console.log('读取成功' + dataStr);
})
路径中有不存在的文件夹时会报错
path模块
- path.join() 将多个路径的片段拼接成完整的路径字符串
- path.basename() 从路径字符串中,将文件名解析出来
path.join([…path])
const path = require('path');
// 可以识别 ../ 等
const pathStr = path.join('/a', '/b/c', '../../', '/d', 'e');
console.log(pathStr); // \a\d\e
const pathStr2 = path.join(__dirname, '/e', 'd');
console.log(pathStr2); // C:\NodeDemo\e\d
path.basesname(path[, ext]) 获取路径中的最后一部分
- ext 文件扩展名
const path = require('path');
const fpath = '/a/b/c/index.html';
// 获取最后的文件
var fullname = path.basename(fpath);
console.log(fullname); // index.html
const fpath = '/a/b/c';
console.log(fullname); // c
// 去除扩展名获取
var fullname = path.basename(fpath, '.html');
console.log(fullname); //index
// 获取文件后缀
var fext = path.extname(fpath);
console.log(fext);
http 模块
创建最基本的web服务器
const http = require('http')
const server = http.createServer()
// 监听客户端的请求
server.on('request', (req, res) => {
console.log('Someone visit out web server.');
})
// 启动服务器
server.listen(8080, () => {
console.log('server running at http://localhost:8080');
})
req 请求对象
server.on('request', (req, res) => {
console.log('Someone visit out web server.');
// 请求相关的方法
const url = req.url
const method = req.method
})
res 响应对象
server.on('request', (req, res) => {
console.log('Someone visit out web server.');
// 请求相关的方法
const url = req.url
const method = req.method
const str = `your url is ${url}, your method is ${method}`
console.log(str);
// 向客户端发送指定内容,并结束这次请求处理过程
res.end(str)
})
解决响应的中文乱码
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(`请求的URL是${url},请求的方式是${method}`)
根据不同的url,实现不同的响应
server.on('request', (req, res) => {
const url = req.url;
let content = '<h1>404 not found</h1>'
if (url == '/' || url == '/index.html') {
content = '<h1>首页</h1>'
} else if (url == '/about.html') {
content = '<h1>关于页面</h1>'
}
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content)
})
模块化
模块化的基本概念
遵守固定规则,把一个大文件拆成独立并相互依赖的多个小模块
好处:提高代码的复用性、提高代码的可维护性、可以按需加载
模块化的规范:用什么语法来引用模块,向外暴露成员
Node.js 中模块的分类
- 内置模块 node官方提供
- 自定义模块 用户创建的js
- 第三方模块 非官方的,需要下载的
加载模块: require( ) 加载时,会执行 加载自定义模块时可以省略js后缀
module.exports 对象
在自定义模块中,可以使用 module.exports 对象,将模块的成员共享出去,供外界使用。外界使用 require() 方式导入自定义模块时,得到的就是 module.exports 所指向的对象。
exports 与 module.exports 的使用误区
使用 require( ) 获取模块时,获取的永远是 module.exports 指向的对象
module.exports.username = 'lisi'
exports = {
gender: '男',
age: 18
}
const mo1 = require('./exports')
console.log(mo1); // { username: 'lisi' }
CommonJS 规范
- 每个模块内部,module 变量代表当前模块
- module 变量是一个对象,他的 exports 属性是对外的接口
- 加载某个模块,其实是加载该模块的 module.exports 属性
npm 与包
npm: Node Package Manager
node.js中第三方模块又叫做包
项目中安装包的命令:
npm install 包的完整名称
// 简写
npm i 包的完整名称
装包后多了哪些文件:
node_modules 的文件夹 package-lock.json 的文件
node_modules文件夹 存放所有已安装到项目中的包,require就是从中导入的
package-lock.json 文件 记录node_modules目录下每个包的下载信息
安装指定版本的包
// 安装指定版本的moment 无需卸载原先的版本
npm i moment@版本号
包管理配置文件
在项目的根目录中需要提供,package.json 包管理配置文件,用来记录与项目有关的一些配置信息
- 项目的名称、版本号、描述
- 项目中用了哪些包
- 哪些包只在开发期间使用
- 哪些包在开发和部署时使用
package.json 类似pom文件
快速创建 package.json
// 作用:在执行命令所处的目录中,快速新建 package.json 文件
npm init -y
上述命令不能包含英文目录,也不能包含空格
dependencies 节点 (开发和上线都要用)
用来记录 npm install 安装了哪些包
# 一次性安装 package.json 中所有的包
npm i
# 卸载包
npm uninstall 包名
devDependencies 节点
只在开发阶段会使用的包就放到该节点中
npm i 包名 -D
# 简写
npm install --save-dev 包名
# 顺序不重要
npm install 包名 --save-dev
改变 npm 的下载链接
淘宝 npm 镜像服务器
# 查看当前下包镜像源
npm config get registry
# 切换镜像源
npm config set registry=https://registry.npm.taobao.org/
nrm 快速切换镜像源
# 通过 npm 包管理器,将 nrm 安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源
nrm ls
# 将下包的镜像源切换为 taobao 镜像
nrm use taobao
包的分类
项目包:安装到 node_modules 目录中
- 开发依赖包 devDependencies
- 核心依赖包 denpendencies
全局包:默认安装到用户目录\AppData\Roaming\npm\node_modules 目录下
# 全局安装
npm i 包名 -g
# 全局卸载
npm uninstall 包名 -g
只有工具性质的包才有全局安装的必要性,因为有好用的终端命令
i5ting_toc
可以把 md 文档转换为 html 页面的工具,自己看文档
规范的包结构
- 以单独的目录存在
- 包的顶级目录必须包含 package.json 这个包管理配置文件
- package.json 中必须包含 name、version、main 三个属性,分别代表包的名字、版本号、包的入口
开发属于自己的包
function dateFormat(dtStr) {
const dt = new Date(dtStr)
const y = padZero(dt.getFullYear())
const m = padZero(dt.getMonth() + 1)
const d = padZero(dt.getDate())
const hh = padZero(dt.getHours())
const mm = padZero(dt.getMinutes())
const ss = padZero(dt.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
function padZero(n) {
return n > 9 ? n : '0' + n
}
module.exports = {
dateFormat
}
function htmlEscape(htmlStr) {
return htmlStr.replace(/<|>|"|&/g, match => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
function htmlUnEscape(htmlStr) {
return htmlStr.replace(/<|>|"|&/g, match => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
module.exports = {
htmlEscape,
htmlUnEscape
}
const date = require('./src/dateFormat')
const html = require('./src/htmlEscape')
module.exports = {
...date,
...html
}
此时便能从外部导入模块并获取到相关的方法
编写说明文档:
- 安装方式
- 导入方式
- 相应的功能
- 开源协议
# 登录 npm,记得切换为官网
npm login
# 发布包 发布名称是 package.json 中的 name
npm publish
# 删除包,只能删除 72 小时内的包,删除后 24 小时内不能再删
npm unpublish 包名 --force
模块的加载机制
优先从缓存中加载
模块在第一次加载后会被缓存。多次调用 require() 不会导致模块被重复加载。
内置模块的加载
内置模块加载优先级最高,例如,require(‘fs’)始终返回内置的fs模块,即使在node modules目录下有名字相同的包也叫做fs。
自定义模块的加载
使用require()加载自定义模块时,必须指定以./或…/开头的路径标识符。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node,js 会按顺序分别尝试加载以下的文件:
- 按照确切的文件名进行加载
- 补全 .js 扩展名加载
- 补全 .json 扩展名加载
- 补全 .node 扩展名加载
- 加载失败,终端报错
第三方模块的加载机制
如果传递给require()的模块标识符不是一个内置模块,也没有以 ./ 或 …/ 开头,则Node.js 会从当前模块的父目录开始,尝试从/node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
目录作为模块
当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为加载入口
- 如果目录下没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件
- 如果以上都失败,则 Node.js 会在终端打印错误信息,报告模块缺失
AJAX
ajax 入门
网页中请求数据,要用到 XMLHttpRequest 对象
XMLHttpRequest 简称(xhr)是浏览器提供的 js 成员
常用场景
- 用户名检测
- 搜索提示
- 数据分页显示
- 数据的增删改查
jQuery中的 ajax
- $.get()
- $.post()
- $.ajax()
$.get(url, [data], [callback])
// 不带参数的请求
$.get('https://www.baidu.com', function(res) {
console.log(res);
})
// 带参数的请求
$.get('https://www.baidu.com', {id: 1}, function(res) {
console.log(res);
})
$.post(url, [data], [callback])
$.get('https://www.baidu.com', {uname: '李四', age: 18}, function(res) {
console.log(res);
})
$.ajax()
$.ajax({
type: '', // 请求方式
url: '', // url地址
data: {}, // 携带的数据
success: function(res) {} // 成功后的回调函数
})
form 表单与模板引擎
form 表单基本使用
action属性 URL地址 提交的地方
target属性 页面打开方式 _self 当前页 _blank 新标签页
method属性 post 与 get
enctype 发送数据之前,如何对数据进行编码 默认:application/x-www-form-urlencoded
值 | 描述 |
---|---|
application/x-www-form-urlencoded | 发送前编码所有字符,默认编码 |
multipart/form-data | 不对字符编码,在使用包含文件上传控件的表单时,必须使用 |
text/plain | 空格转换为“+”号,但不对特殊字符编码。(不常用) |
通过ajax提交表单数据
jQuery 监听表单提交事件
$('#form').submit(function(e) {})
$('#form').on('submit', function(e) {})
阻止默认行为(提交)
$('#f1').on('submit', function(e) {
e.preventDefault();
})
获取表单中的数据
$('#f1').on('submit', function(e) {
e.preventDefault();
console.log($(this).serialize()); // username=123&password=456
})
art-template 模板引擎
标准语法 {{ }} 还可以插入运算
原文输出 输出html标签 {{@ value }}
条件语句
<div>
{{if flag === 0}}
显示内容
{{/if}}
</div>
遍历,循环输出
<!-- 遍历数组类型属性,记住加$ -->
{{each hoby}}
{{$index}}{{$value}}
{{/each}}
过滤器
// 模板中的写法
<h1>{{regTime | dataFormat(函数名)}}</h1>
// 定义管道(过滤)运算 dataFormat 是自定义的
template.defaults.imports.dataFormat(函数名) = function(date) {
var y = date.getFullYear();
var m = date.getMonth();
var d = date.getDate();
return y + '-' + m + '-' + d
}
模板引擎的实现原理
输出符合规则的
let str = 'hello';
let pattern = /o/;
console.log(pattern.exec(str)); //["o", index: 4, input: "hello", groups: undefined]
分组
// 能获取符合相应条件的,利用分组返回
let str = '<div>我是{{name}}</div>';
let pattern = /{{([a-zA-Z0-9]+)}}/;
console.log(pattern.exec(str)); // ['{{name}}', 'name', index: 7, input: '<div>我是{{name}}</div>', groups: undefined]
替换
var str = '<div>我是{{name}}</div>'
var pattern = /{{([a-zA-Z]+)}}/
var patternResult = pattern.exec(str)
// console.log(patternResult)
str = str.replace(patternResult[0], patternResult[1])
console.log(str) // <div>我是name</div>
使用while循环替换
var str = '<div>{{name}}今年{{ age }}岁了</div>'
var pattern = /{{\s*([a-zA-Z]+)\s*}}/
var patternResult = null
while (patternResult = pattern.exec(str)) {
str = str.replace(patternResult[0], patternResult[1])
}
console.log(str)
变量替换
let data = {name: '小米', age: 15};
var str = '<div>{{name}}今年{{ age }}岁了</div>';
let pattern = /{{\s*([a-zA-Z]+)\s*}}/;
while (patResult = pattern.exec(str)) {
str = str.replace(patResult[0], data[patResult[1]]);
}
console.log(str);
自己封装模板引擎
function template(id, data) {
let str = document.getElementById(id).innerHTML;
let pattern = /{{\s*([a-zA-Z]+)\s*}}/;
while (patResult = pattern.exec(str)) {
str = str.replace(patResult[0], data[patResult[1]]);
}
return str;
}
AJAX 加强
xhr 的基本使用
- 创建 xhr 对象
- 调用 xhr.open() 函数
- 调用 xhr.send() 函数
- 监听 xhr.onreadystatechange 事件
let xhr = new XMLHttpRequest();
xhr.open("get", "http://www.liulongbin.top:3006/api/getbooks");
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
}
了解 xhr 对象的 readyState 属性
使用 xhr 发起 get 请求,直接在 url 后追加参数
get 请求实质:都是直接将参数以查询字符串的形式,追加到 URL 地址后面的。
URL 编码,英文表示非英文
let str = "黑马程序员";
console.log(encodeURI(str));
console.log(decodeURI("%E9%BB%91%E9%A9%AC"));
// 发送 post 请求
let xhr = new XMLHttpRequest();
xhr.open("post", "http://www.liulongbin.top:3006/api/addbook");
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('bookname=追风筝的人&author=卡勒德·胡赛尼&publisher=人民出版社');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
}
数据交换格式
xml 与 json
json:全称 JavaScript object notation,JSON 是 JavaScript 对象和数组的字符串表示法。他使用文本表示一个js对象或数组的信息,因此 json 本质是字符串。
json 结构
对象结构:对象结构在 json 中表示为 { } 括起来的内容。数据结构为 {key: value, …} 的键值对结构。其中,key 必须是使用英文的双引号包裹的字符串。value 可以是数字、字符串、布尔值、null、数组、对象6种类型。
数组结构
json 与 js 对象的关系
json 是 js 对象的字符串表示法,使用文本表示一个 js 对象信息,本质是一个字符串。
// 这是一个对象
let obj = {a: "hello", b: "world"}
console.log(obj);
// json 字符串
let json = '{"a": "hello","b": "world"}';
console.log(json);
json 与 js 相互转换
// json 字符串转换为 js 对象
let json = '{"a": "hello","b": "world"}';
let obj = JSON.parse(json);
// js 对象转换为 json
let obj = {a: "hello", b: "world"};
let str = JSON.stringify(obj);
序列化与反序列化就是上述过程
封装自己的Ajax函数
处理data参数
function resolveData(data) {
let arr = [];
for (let k in data) {
arr.push(k + '=' + data[k])
}
return arr.join('&');
}
封装的ajax
function lqs(options) {
let xhr = new XMLHttpRequest();
let qs = resolveData(options.data);
if (options.method.toUpperCase() == "GET") {
xhr.open("GET", options.url + "?" + qs);
xhr.send();
} else if (options.method.toUpperCase() == "POST") {
xhr.open("POST", options.url);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(qs);
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
let result = JSON.parse(xhr.responseText)
options.success(result);
}
}
}
xhr level2 新特性
- 可以设置http请求时限
- 可以使用 FormData 对象管理表单数据
- 可以上传文件
- 可以获得数据传输的进度信息
设置时限
let xhr = new XMLHttpRequest();
// 设置超时时间
xhr.timeout = 30;
// 回调函数
xhr.ontimeout = function() {
console.log('超时了');
}
xhr.open("get", "http://www.liulongbin.top:3006/api/getbooks");
FormData 对象
发送 post 请求
// 方便表单处理,相当于表单操作
// 1. 创建 FormData 实例
var fd = new FormData()
// 2. 调用 append 函数,向 fd 中追加数据
fd.append('uname', 'zs')
fd.append('upwd', '123456')
var xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
xhr.send(fd)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
}
}
获取网页表单的值
let fd = new FormData(form);
xhr.send(fd);
上传文件
- 定义ui结构
- 验证是否选择文件
- 向 FormData 中追加文件
- 使用 xhr 发起上传文件的请求
- 监听 onreadystatechange 事件
// 向 FormData 中追加文件
let fd = new FormData();
fd.append('avatar', files[0]);
显示文件上传进度
xhr.upload.onprogress = function(e) {
if (e.lengthComputalbe) {
let percentComplete = Math.ceil((e.loaded / e.total) *100)
}
}
监听上传完成事件
xhr.upload.onload = function() {}
jQuery高级用法
$('#btnUpload').on('click', function () {
// 获取文件控件的原生对象以及里面的值
var files = $('#file1')[0].files
if (files.length <= 0) {
return alert('请选择文件后再上传!')
}
var fd = new FormData()
fd.append('avatar', files[0])
// 发起 jQuery 的 Ajax 请求,上传文件
$.ajax({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/upload/avatar',
data: fd,
// 不对 formdata 里的数据进行 url 编码,而是将 formdata 数据原样发送到服务器
processData: false,
// 不修改conten-type 属性,使用默认的值
contentType: false,
success: function (res) {
console.log(res)
}
})
})
实现loading 效果
// 监听到Ajax请求被发起了
$(document).ajaxStart(function () {
$('#loading').show()
})
// 监听到 Ajax 完成的事件
$(document).ajaxStop(function () {
$('#loading').hide()
})
axios
axios 是专注于网络数据请求的库。 相比于原生xhr,更简单易用。相比jQuery更加轻量化。
发起get请求
axios.get(url, {params: {/* 参数 */}}).then(callback);
let paramObj = {
name: 'zs',
age: 12
};
axios.get('http://www.liulongbin.top:3006/api/get', {
params: paramObj
}).then(function (res) {
console.log(res);
})
发起post请求
axios.post(url, {/* 参数 */}),then(callback);
let dataObj = {
address: '大连',
location: '秦皇岛'
};
axios.post('http://www.liulongbin.top:3006/api/post', dataObj).then(function (res) {
console.log(res);
})
直接发起请求
document.querySelector('#btn3').addEventListener('click', function () {
var url = 'http://www.liulongbin.top:3006/api/get'
var paramsData = { name: '钢铁侠', age: 35 }
axios({
method: 'GET',
url: url,
params: paramsData
}).then(function (res) {
console.log(res.data)
})
})
document.querySelector('#btn4').addEventListener('click', function () {
axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/post',
data: {
name: '娃哈哈',
age: 18,
gender: '女'
}
}).then(function (res) {
console.log(res.data)
})
})
跨域与jsonp
了解同源策略和跨域
同源是指两个页面,协议,域名和端口号都相同,则两个页面具有相同的源。
同源策略(Same origin policy) 是浏览器提供的一个安全功能
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
- 无法读取非同源网页的Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 Ajax 请求
跨域,即不同源
如何实现跨域数据请求:jsonp 与 cors
jsonp:出现的早,兼容性好。临时解决方案。缺点是只支持get请求,不支持post请求。
cors:出现较晚,属于w3c标准,属于跨域 ajax 请求的根本解决方案。支持 GET 与 POST 请求,缺点是不兼容低版本浏览器。
jsonp
json with padding 是json一种使用模式。解决跨域问题。由于同源策略的限制,无法请求非同源的接口。但可以通过script标签的src属性来引入。
<script>
function abc(data) {
console.log(data);
}
</script>
<script src="http://www.liulongbin.top:3006/api/jsonp?callback=abc&name=ls&age=30"></script>
缺点:只支持get请求
jQuery中的jsonp
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
// 代表我们要发起JSONP的数据请求
dataType: 'jsonp',
// 发送的服务端的回调函数参数名
jsonp: 'callback',
// 回调函数名称
jsonpCallback: 'abc',
success: function (res) {
console.log(res)
}
})
jQuery实现过程:动态生成和移除 script 标签来进行的。
防抖与节流
防抖:事件被触发多次,确保只执行一次回调。有效减少请求次数,减小服务器压力。
// 设置延迟器
let timer = null;
function debounceSearch(keywords) {
timer = setTimeout(function() {
getSuggestList(keywords)
}, 300);
}
// 清空上次的,并执行新的,确保只执行一次
clearTimeout(timer);
debounceSearch(keywords);
搜索缓存
let cacheObj = {};
// 列表展示,即搜索到后,将结果存入到缓存对象中
cacheObj[k] = res;
// 判断缓存中是否有,有则直接返回
if (cacheObj[keywords]) {
return renderSuggestList(cacheObj[keywords])
}
节流策略(throttle) 减少一段时间内事件的触发次数
let timer = null;
$(document).mousemove(function(e) {
if (timer) return;
timer = setTimeout(function() {
$('#angel').css('left', e.pageX + 'px').css('top', e.pageY + 'px');
// 时间到后,记住要给 timer 清空
timer = null;
}, 16);
})
防抖和节流的区别
- 防抖:事件被频繁触发时,防抖能保证只有最后一次触发生效。
- 节流:如果事件被频繁触发,节流能够减少事件触发的频率。
http协议加强
http请求
请求头部
描述客户端的基本信息,User-Agent 来说明当前是什么类型的浏览器,Content-Type 用来描述发送到服务器的数据格式,Accept 用来描述客户端能接收什么类型的返回内容,Accept-Language 用来描述客户端期望接收哪种语言的内容。
请求体
存放通过 post 提交到服务器的数据。
http响应
响应头部
描述服务器信息,键值对组成
http请求方式
http响应状态码
2相关状态码
3相关状态码
4相关状态码
5相关状态码
Git
安装并配置
配置用户信息
git config --global user.name "lqs"
git config --global uer.email "2677388021@qq.com"
查看配置信息
# 查看所有 全局配置项
git config --list --global
# 查看指定全局配置项
git config user.name
git config user.email
获取帮助信息 git help
# 打开 config 的帮助手册
git help config
# 简明的帮助
git config -h
Git 的基本操作
初始化 git init
工作区文件的四种状态
可分为两大类:未被git管理与已被git管理
检查文件的状态 git status
精简的显示 git status -s
跟踪新文件 git add file
提交更新 git commit -m “信息”
对已提交文件进行修改
修改过的、没有放入暂存区的文件前面有红色的M标记
暂存已修改的文件
已修改,需要暂存,再次运行 git add 这是个多功能命令
- 可以开始跟踪新文件
- 把已跟踪的、且已修改的文件放到暂存区
- 把有冲突的文件标记为已解决的状态
提交已暂存的文件 git commit -m “信息”
撤销对文件的修改 git checkout – 文件名
将工作区中对文件的修改还原成git仓库中所保存的版本。危险性教高,慎重操作。
暂存区添加多个文件
# 一次性全部提交到暂存区
git add .
取消暂存的文件
git reset Head 文件名
跳过使用暂存区提交
git commit -a -m "信息"
移除文件
# git 仓库和工作区同时移除
git rm -f 文件名
# 只从 git 仓库移除,保留工作区中的文件
git rm --cached 文件名
git 忽略文件 不让某些文件总是出现在未跟踪目录中
查看提交历史 git log
回退到指定的版本中
# 一行中展示所有提交历史
git log --pretty=oneline
# 根据指定的id回退到指定版本
git reset --hard commitid(版本id)
# 这时需要使用新的log命令,展示所有提交信息
git reflog --pretty=oneline
# 再次根据id,跳转到新版本
git reset --hard commitid(版本id)
GitHub
5种常见的开源协议
ssh 连接
Git分支
查看分支列表
git branch
创建新分支
# 基于当前分支创建,还位于当前分支
git branch 分支名
切换分支
git checkout 分支名
快速创建和切换分支
# -b 表示创建一个新的分支
# checkout 表示切换到新创建的分支上
git checkout -b 分支名称
合并分支
# 1.切换到master分支
git checkout master
# 2. 运行命令,合并
git merge 分支名
删除分支 要处于被删除分支以外的分支上
git branch -d 分支名称
冲突时的分支合并
# 打开包含冲突的文件,解决冲突后,执行下列命令git add .git commit -m
远程分支操作
# -u 表示本地与远程关联 只在第一次推送的时候要用git push -u 远程仓库别名 本地分支名称:远程分支名称# 演示git push -u origin payment:pay# 远程分支与本地分支名称一致git push -u origin payment
查看远程分支
git remote show 远程仓库名称
跟踪分支
# 从远程仓库中,下载到本地,且名称相同
git checkout 远程分支名称
# 演示
git checkout origin
# 从远程仓库中,重命名下载下来
git checkout -b 本地分支名称 远程仓库名称/远程分支名称
# 演示
git checkout -b payment origin/pay
拉取远程分支的最新代码
# 从远程仓库拉取当前分支的最新代码
git pull
删除远程分支
# 删除远程仓库中,指定名称的分支
git push 远程仓库名称 --delete 远程分支名称
# 示例
git push origin --delete pay
Express
初识 Express
基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
Express 的基本使用
const express = require('express')
const app = express()
app.get('/user', (req, res) => {
res.send('这是一个get请求')
})
app.post('/user', (req, res) => {
res.send({name : 'jack', age: 12})
})
app.get('/', (req, res) => {
console.log(req.query)
res.send(req.query)
})
// 获取动态参数 即路径中,冒号后所填的值会自动赋值给相应的参数
app.get('/user/:id', (req, res) => {
console.log(req.params) // {"id": 1}
res.send(req.params)
})
// 可以写多个参数
app.get('/user/:id/:name', (req, res) => {
console.log(req.params) // {"id":"1","name":"zhangsan"}
res.send(req.params)
})
app.listen(80, () => {
console.log('express is running at http://127.0.0.1')
})
const express = require('express')
const app = express()
// 托管静态资源目录,可托管多个,按顺序查找
app.use(express.static('./day1128/clock'))
app.use(express.static('./day1128/files'))
// 挂载路径前缀
app.use('/public', express.static('./day1128/public'))
app.listen(80, () => {
console.log('server is running at http://127.0.0.1');
})
nodemon 实现热部署
Express 路由
路由即映射关系,Express 中的路由指客户端的请求与服务器处理函数之间的映射关系
Express 中的路由分三部分构成:请求类型、请求URL地址、处理函数 app.METHOD(PATH, HANDLER)
路由匹配的注意点:
-
按照定义的先后顺序进行匹配
-
请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
最简单的用法: 直接挂载到 app 上
app.get('/user', (req, res) => {
res.send('这是一个get请求')
})
代码量太多,不好管理
模块化路由
- 创建路由模块的 .js 文件
- 调用 express.Router() 函数创建路由对象
- 向路由对象上挂载具体的路由
- 使用 module.exports 向外共享路由对象
- 使用 app.use() 函数注册路由模块
// 模块文件
const express = require('express')
const router = express.Router()
router.get('/user/list', (req, res) => {
console.log('user list');
res.send('this is get')
})
router.post('/user/add', (req, res) => {
console.log('user add');
res.send('this is post')
})
module.exports = router
// 引入
const express = require('express')
const app = express()
const router = require('./router')
// 注册全局中间件
app.use(router)
// 添加统一的访问前缀
// app.use('/api', router)
app.listen(80, () => {
console.log('http://127.0.0.1');
})
Express 中间件
中间件的作用:
多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
// 定义全局中间件,简化写法
app.use((req, res, next) => {
//req.date = Date.now()
console.log('调用了1中间件');
next()
})
app.use((req, res, next) => {
req.date = Date.now()
console.log('调用了2中间件');
next()
})
// 完整写法
const mw = function(req, res, next) {}
app.use(mw)
局部生效的中间件:
// 定义局部中间件
const mw1 = function(req, res, next) {
console.log('局部中间件1');
next()
}
const mw2 = function(req, res, next) {
console.log('局部中间件2');
next()
}
app.get('/user', mw1, mw2, (req, res) => {
res.send('User page.')
})
// 等价写法
app.get('/user', [mw1, mw2], (req, res) => {
res.send('User page.')
})
中间件的注意事项:
- 要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用 next() 函数
- next() 后,不要再写代码
- 多个中间件之间共享 req 与 res
中间件的分类
- 应用级中间件
- 路由级中间件
- 错误级中间件
- Express 内置中间件
- 第三方中间件
应用级中间件:
通过app.use()或 app.get())或 app.post(),绑定到app实例上的中间件,叫做应用级别的中间件
路由级别中间件:
绑定到express.Router()实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到app实例上,路由级别中间件绑定到router"实例上
const express = require('express')
const app = express()
const router = express.Router()
router.use((req, res, next) => {
next()
})
app.use('/', router)
错误级别中间件:
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
const express = require('express')
const app = express()
app.get('/', (req, res) => {
throw new Error('报错了')
res.send('Page home')
})
// 错误级别中间件
app.use((err, req, res, next) => {
console.log('报错了');
res.send(err.message)
})
app.listen(80, () => {
console.log('http://localhost');
})
注意错误级别的中间件要写在路由后面
几种中间件用法
Express 内置的中间件
// 配置 json 解析中间件
app.use(express.json())
app.post('/user', (req ,res) => {
console.log(req.body);
res.send(req.body)
})
// 配置urlencoded 解析中间件
app.use(express.urlencoded({
extended: false
}))
const express = require('express')
const bdps = require('body-parser')
const app = express()
// body-parser 中间件的使用
app.use(bdps.urlencoded({
extended: false
}))
app.post('/user', (req ,res) => {
console.log(req.body);
res.send(req.body)
})
自定义中间件
const qs = require('querystring')
const parser = (req, res, next) => {
// 监听 req 的 data 事件,数据量较大时,客户端会将数据切割传输,此时服务端进行拼接
let str = ''
req.on('data', chunk => {
str += chunk
})
// 监听数据传输结束事件
req.on('end', () => {
// console.log(str);
const body = qs.parse(str)
req.body = body
console.log(body);
next()
})
}
module.exports = parser
const express = require('express')
const app = express()
const parser = require('./constom-body-parser')
app.use(parser)
app.post('/user', (req, res) => {
res.send(req.body)
})
app.listen(80, () => {
console.log('http://localhost');
})
使用 Express 写接口
cors 解决跨域问题
const cors = require('cors')
app.use(cors())
什么是 cors
CORS响应头部 Access-Control-Allow-Origin
(服务端设置)设置允许来自 某域名的 资源请求 * 表示所有
res.setHeader('Access-Control-Allow-Origin', '*')
CORS 响应头部 Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败!
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
CORS响应头部 Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求。
如果客户端希望通过 PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Alow-Methods来指明实际请求所允许使用的HTTP方法。
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
// 允许所有
res.setHeader('Access-Control-Allow-Methods', '*')
cors 请求分类
简单请求
- 请求方式:GET、POST、HEAD 三者之一
- HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
预检请求:
- 请求方式:GET、POST、HEAD 之外的请求
- 请求体包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求与预检请求的区别
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
jsonp 接口
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明 JSONP 的接口。否则,JSONP接口会被处理成开启了CORS的接口。示例代码如下。
实现步骤
- 获取客户端发送过来的回调函数的名字
- 得到要通过 jsonp 形式发送给客户端的数据
- 根据前两部得到的数据,拼接处一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的 script 标签来解析
app.get('/api/jsonp', (req, res) => {
const funcName = req.query.callback
const data = {
name: 'lisi',
age: 12
}
const scriptStr = `${funcName} (${JSON.stringify(data)})`
res.send(scriptStr)
})
数据库与身份认证
MySQL 的基本使用
-- 更新值
update users set username='zs' where id = 1;
-- 删除值
delete from users where id = 1;
-- 插入值
insert into users(id, username, password) values(1, 'zs', '123456')
在 Express 中操作 MySQL
// 创建数据库连接池
const mysql = require('mysql')
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '12345600',
database: 'new_schema'
})
查询语句
// 执行查询语句
const sqlStr = 'select * from users'
db.query(sqlStr, (err, result) => {
if (err) return console.log(err.message);
console.log(result);
})
插入数据
// 插入数据
const user = {
username: 'Spider-Man',
password: '123456'
}
const sqlStr = 'insert into users(username, password) values(?, ?)'
db.query(sqlStr, [user.username, user.password], (err, result) => {
if (err) return console.log(err.message);
if (result.affectedRows === 1) console.log('插入成功');
})
// 一次性插入
const user = {
username: 'SuperMan',
password: '123456aa'
}
const sqlStr = 'insert into users set ?'
db.query(sqlStr, user, (err, result) => {
if (err) return console.log(err.message);
if (result.affectedRows === 1) console.log('插入成功');
})
更新数据
// 更新数据
const user = {
id: 4,
username: 'Jenny',
password: '1234aa'
}
const sqlStr = 'update users set username=?, password=? where id = ?'
db.query(sqlStr, [user.username, user.password, user.id], (err, result) => {
if (err) return console.log(err.message);
if (result.affectedRows === 1) console.log('更新成功');
})
// 简化写法
const user = {
id: 4,
username: 'Jennyfe',
password: '1234aa'
}
const sqlStr = 'update users set ? where id = ?'
db.query(sqlStr, [user, user.id], (err, result) => {
if (err) return console.log(err.message);
if (result.affectedRows === 1) console.log('更新成功');
})
删除数据 (太危险,不建议)
const sqlStr = 'delete from users where id = ?'
db.query(sqlStr, 8, (err, result) => {
if (err) return console.log(err.message);
if (result.affectedRows === 1) console.log('更新成功');
})
前后端的身份认证
web 开发模式
- 基于服务端渲染的传统 Web 开发模式
- 基于前后端分离的新型 Web 开发模式
基于服务端渲染的传统 Web 开发模式
优点:
- 前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
- 有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。
缺点:
- 占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
- 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。
基于前后端分离的新型 Web 开发模式
优点:
- 开发体验好。前端专注于U页面的开发,后端专注于api的开发,且前端有更多的选择性。
- 用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
- 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
缺点:
- 不利于SEO。因为完整的HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用Vue、React等前端框架的SSR(server side render)技术能够很好的解决SEO问题!)
如何选择 Web 开发模式
- 比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务器端渲染;
- 而类似后台管理项目,交互性比较强,不需要考虑SEO,那么就可以使用前后端分离的开发模式。
身份认证
不同模式下的身份认证:
- 服务端渲染推荐使用Session认证机制
- 前后端分离推荐使用JWT认证机制
什么是 Cookie:
Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
不同域名下的Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
- 自动发送
- 域名独立
- 过期时限
- 4 Kb 限制
cookie 不具有安全性
由于Cookie 是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie,很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
Express 中使用 session
// TODO_01:请配置 Session 中间件
const session = require('express-session')
app.use(session({
secret: 'lqsznb',
resave: false,
saveUninitialized: true
}))
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
req.session.user = req.body
req.session.islogin = true
// TODO_04:清空 Session 信息
req.session.destroy()
session 认证的局限性
Session认证机制需要配合Cookie 才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。
JWT 认证机制
JWT (英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
JWT通常由三部分组成,分别是Header(头部)、Payload (有效荷载)、Signature(签名)。
- Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
- Header和Signature是安全性相关的部分,只是为了保证Token的安全性。
使用 JWT
npm install jsonwebtoken express-jwt
// 生成要发送给客户端的 token 字符串
jwt.sign({
username: userinfo.username
}, secretKey, {
expiresIn: '30s'
})
// 注册将 JWT 字符串解析还原成 JSON 对象的中间件
app.use(expressJWT({
secret: secretKey,
algorithms: ['HS256']
}).unless({
path: [/^\/api\//]
}))
- 基于服务端渲染的传统 Web 开发模式
- 基于前后端分离的新型 Web 开发模式
基于服务端渲染的传统 Web 开发模式
优点:
- 前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
- 有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。
缺点:
- 占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
- 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。
基于前后端分离的新型 Web 开发模式
优点:
- 开发体验好。前端专注于U页面的开发,后端专注于api的开发,且前端有更多的选择性。
- 用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
- 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
缺点:
- 不利于SEO。因为完整的HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用Vue、React等前端框架的SSR(server side render)技术能够很好的解决SEO问题!)
如何选择 Web 开发模式
- 比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务器端渲染;
- 而类似后台管理项目,交互性比较强,不需要考虑SEO,那么就可以使用前后端分离的开发模式。
身份认证
不同模式下的身份认证:
- 服务端渲染推荐使用Session认证机制
- 前后端分离推荐使用JWT认证机制
什么是 Cookie:
Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
不同域名下的Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
- 自动发送
- 域名独立
- 过期时限
- 4 Kb 限制
cookie 不具有安全性
由于Cookie 是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie,很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
Express 中使用 session
// TODO_01:请配置 Session 中间件
const session = require('express-session')
app.use(session({
secret: 'lqsznb',
resave: false,
saveUninitialized: true
}))
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
req.session.user = req.body
req.session.islogin = true
// TODO_04:清空 Session 信息
req.session.destroy()
session 认证的局限性
Session认证机制需要配合Cookie 才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。
JWT 认证机制
JWT (英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
JWT通常由三部分组成,分别是Header(头部)、Payload (有效荷载)、Signature(签名)。
- Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
- Header和Signature是安全性相关的部分,只是为了保证Token的安全性。
使用 JWT
npm install jsonwebtoken express-jwt
// 生成要发送给客户端的 token 字符串
jwt.sign({
username: userinfo.username
}, secretKey, {
expiresIn: '30s'
})
// 注册将 JWT 字符串解析还原成 JSON 对象的中间件
app.use(expressJWT({
secret: secretKey,
algorithms: ['HS256']
}).unless({
path: [/^\/api\//]
}))