知识点1:学会cookie在服务端的使用方式
- 实际场景:七天内免登录功能
知识点2:学会cookie在客户端的使用方式
- 实际场景:页面换肤功能
知识点3:学会使用localStorage及sessionStorage做数据的持久化
- 实际场景:页面换肤功能、添加歌曲列表功能
项目初始化搭建
- 本文以koa来搭建项目的服务器端,借助了
koa-setup第三方库(点击进入官网)来快速搭建起项目目录和安装必要的依赖,该项目最终的package.json如下:
{
"dependencies": {
"koa": "^2.7.0",
"koa-bodyparser": "^4.2.1",
"koa-router": "^7.4.0",
"koa-static": "^5.0.0",
"koa-views": "^6.2.0",
"md5": "^2.2.1",
"pug": "^2.0.3"
}
}
- 在
koa-stup自动构建的帮助下得到该项目的初始化模板后,便可以着手书写配置入口文件,让项目成功运行起来,下面是配置的基础代码:
const Koa = require("koa")
//用来处理动态路由
const Router = require("koa-router")
//静态资源托管
const serve = require("koa-static")
const views = require("koa-views")
//该中间件对从客户端post请求到服务器的数据进行了封装
const bodyParser = require("koa-bodyparser");
//加密数据
const md5 = require("md5");
const app = new Koa()
//托管静态资源
app.use(serve(__dirname+"/static"))
//选择模板引擎映射视图
app.use(views(__dirname + "/views"), {
map: {
html: "pug"
}
})
app.use(bodyParser());
const router = new Router()
router.get("/",(ctx)=>{
ctx.body = "hello test"
})
router.get("/login",(ctx)=>{
ctx.body = "hello login"
})
app.use(router.routes())
app.listen(8080,()=>{
console.log("open server localhost:8080")
})
- 在配置完基础代码后,通过
nodemon第三方库来启动该文件,启动后通过端口号打开页面,可以观察到服务器搭建成功了,效果图如下:
实现七天内免登录功能
- 流程图如下:
- 项目目录如下:
第一步:构造登录、错误、展示页面的视图和样式
- views/login.pug文件的视图:
>>>这里是views/login.pug文件的代码
>>>注意点:这里的表单是以POST的方式往/checkUser地址发送数据
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
link(rel='stylesheet', href='css/login.css')
title Document
body
.loginContainer
h1 登录
form(action='/checkUser', method='post')
| 姓名:
input.inputStyle(type='text', name='username')
br
| 密码:
input.inputStyle(type='password', name='pwd')
br
input.loginStyle(type='submit', value='登录')
| |
input(type='checkbox', name='memberMe')
| 记住我
- views/error.pug文件的视图:
<!DOCTYPE html>
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title Document
body
| 用户名或者密码错误
br
span 4
| 秒之后自动跳转回登录页面...
br
a(href="/login") 不等了点击直接跳转回登录页面
script.
let timer = document.querySelector("span").innerText;
//字符串类型转为数字类型
timer = parseInt(timer);
setInterval(()=>{
timer--
document.querySelector("span").innerText = timer;
if(timer<1){
window.location.href = "/login";
}
},1000)
- views/list.pug文件的视图:
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
link(rel='stylesheet', type='text/css', href='css/list.css')
script(src="js/list.js" type="text/javascript")
title Document
body
button.changeSkin 点击换肤
第二步:服务器页面的实现逻辑部署
- ./index.js文件的服务器页面部署
router.get("/login", async (ctx, next) => {
//1.在每次访问/login页面时先通过服务器端获取是否存在该cookie
let cookieInfo = ctx.cookies.get("isLogin")
//2.如果存在证明用户可能已经登录成功
//需在判断该cookie的合法性因为cookie是会被伪造的
if (cookieInfo) {
//3.这里为了演示将用户数据写死并做简单的md5加密
//实际场景中会去检索数据库里是否有匹配的用户
let serverInfo = md5("张三" + "123");
//4.在判定该cookie不仅存在并且与实际的用户信息相匹配的情况下
//即判定用户已经登录过了且cookie是合法的未过期的
if (serverInfo == cookieInfo) {
//直接重定向至展示页面
ctx.redirect("/list");
}
}
//5.其他情况下访问/login时呈现给用户的是登录页即可
await ctx.render("login.pug");
})
//1.借助koa-bodyparser来拿到用户填写的登录表单的数据
//使用该库后可以通过ctx.request.body来查看表单提交过来的数据
router.post("/checkUser", (ctx, next) => {
console.log(ctx.request.body);
//2.为了演示这里假定用户名是张三 密码是123
//只有用户输入用户名张三 密码123才可以判定为登录成功
if (ctx.request.body.username == "张三" && ctx.request.body.pwd == "123") {
//console.log(ctx.request.body);
//3.只有当用户勾选了 记住我 这个按钮
//ctx.request.body.memberMe的结果才为true
if (ctx.request.body.memberMe) {
//4.当用户勾选记住我按钮后
//服务端需要使用cookie来告知客户端储存用户登录成功的相关信息
//为了演示这里只做了简单的md5加密
//实例场景中不应该在客户端存储敏感信息特别是用户信息
let loginStatus = md5("张三" + "123");
//5.ctx.cookies.set(name, value, [options])
//服务端使用cookie来告知客户端储存用户信息并设置过期时间
//这里过期时间设置为 7天
ctx.cookies.set("isLogin", loginStatus, {
maxAge: 3600 * 1000 * 24 * 7
})
}
//6.用户登录成功后重定向跳转到/list页面;
ctx.redirect("/list");
} else {
//7.用户名或者密码错误跳转到/error页面;
//这里是只要不满足用户名张三 密码123都会跳转到/error页面
ctx.redirect("/error");
}
})
router.get("/list", async (ctx, next) => {
// await ctx.body='我是list页面'
await ctx.render("list.pug");
})
router.get("/error", async (ctx, next) => {
// await ctx.body='我是error页面'
await ctx.render("error.pug");
})
实现页面换肤功能
- 效果图:
实现方法一:通过构建一个对象池子来存储换肤效果从而控制页面的换肤效果
- views/list.pug文件的视图:(这里引入了js/list.js文件)
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
link(rel='stylesheet', type='text/css', href='css/list.css')
script(src="js/list.js" type="text/javascript")
title Document
body
button.changeSkin 点击换肤
- 封装实现了换肤效果的js/list.js文件:
/*
缺点是在页面刷新之后换肤的效果没有记忆性
因为key值会重新变为0
注意该js文件是在客户端运行的
如何做到即使是页面重载后依旧能记忆用户的换肤效果呢???
*/
window.onload = function(){
//定义页面的颜色库
let colorArr = ["white","rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];
//颜色库的默认索引为0 默认肤色
let key = 0;
//当点击换肤的按钮后
document.querySelector(".changeSkin").onclick = function(){
key++;
//索引递增到超出颜色库的索引值时置空
key = key>3?0:key;
//基于颜色库实现的换肤功能
document.body.style.background = colorArr[key];
}
}
实现方式二:通过构建一个对象池子来存储换肤效果,并在客户端设置与当前页面的肤色对应的cookie来做到记忆的功能
- 封装实现了换肤效果的js/list.js文件:
/*
缺点:
浏览器会在每次请求的时候主动组织
所有域下的cookie到请求头 cookie 中
发送给服务器端
单单针对换肤功能来说存储换肤的cookie信息有必要在每次的浏览器请求中
将此cookie信息发送给服务端吗???
*/
window.onload = function(){
//定义页面的颜色库
let colorArr = ["white","rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];
//颜色库的默认索引为0 默认肤色
//影响肤色的是key值
//在每一次点击换肤的时候可以保存key值到cookie中 同步记忆效果
let key = 0;
if(getCookie('key')){
key=getCookie('key')
}
document.body.style.background = colorArr[key];
//当点击换肤的按钮后
document.querySelector(".changeSkin").onclick = function(){
key++;
//索引递增 超出颜色库的索引值时置空
key = key>3?0:key;
//在每一次点击换肤的时候可以将key值同步保存在cookie中
setCookie('key',key,{
"Max-Age":3600
})
//基于颜色库实现的换肤功能
document.body.style.background = colorArr[key];
}
}
//封装一个设置cookie的方法
function setCookie(name,value,options={}){
//存储cookie的用法:document.cookie='test=hello;Max-Age=3600;'
//获取cookie的用法:console.log(document.cookie)
//document.cookie的返回值是当前域名下的所有cookie
//在客户端获取cookie时数据结构类比test=hello; test2=hello2 test=hello;
//注意点:末尾的分号一定不能忘记!!
let cookieData = `${name}=${value};`;
//注意点:如果options是空对象不会产生结果
for(let key in options){
let str = `${key}=${options[key]};`;
cookieData += str;
}
document.cookie = cookieData;
}
//封装一个获取cookie的方法
function getCookie(name){
//注意点:这里是以'; '来分隔cookie字符串
//一定不要漏写空格!!!
let arr = document.cookie.split("; ");
for(let i =0;i<arr.length;i++){
//再次分割 循环对比得到匹配值
let arr2 = arr[i].split("=");
if(arr2[0]==name){
return arr2[1];
}
}
//如果对比失败则表示获取不到该name对应的值
//返回一个空字符串
return "";
}
实现方式三:通过构建一个对象池子来存储换肤效果,并在客户端设置与当前页面的肤色对应的localStorage来做到记忆的功能
- 封装实现了换肤效果的js/list.js文件:
/*
localStorage的基本使用
1.localStorage.setItem('local','测试文字')
2.console.log(localStorage.getItem('local'));
3.localStorage.removeItem('local')
4.localStorage.clear()
sessionStorage的api使用与localStorage一致
换肤功能的需求分析:
1.换肤功能需要有时间限制吗?
1.不需要(localStorage如果不清除是没有过期时间的)
2.换肤的信息需要发送给服务器端吗?
2.不需要(localStorage的信息不会主动发给服务器端)
综上:换肤功能使用localStorage来实现是比较正确的
*/
window.onload = function(){
//定义页面的颜色库
let colorArr = ["white","rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];
//颜色库的默认索引为0 默认肤色
//影响肤色的是key值 在每一次点击换肤的时候可以保存key值在cookie中
let key = 0;
if(localStorage.getItem('key')){
key=localStorage.getItem('key')
}
document.body.style.background = colorArr[key];
//当点击换肤的按钮后
document.querySelector(".changeSkin").onclick = function(){
key++;
//索引递增 超出颜色库的索引值时置空
key = key>3?0:key;
localStorage.setItem('key',key)
//基于颜色库实现的换肤功能
document.body.style.background = colorArr[key];
}
}
实现页面数据通信
- 利用localStorage同域下页面共享的特性可以实现页面数据通信,并基于数据来完成跨页面的视图更新
- 流程图为:
- 效果图如下:
- 构造目录如下:
- ./index.js文件的代码
//引入歌单信息的json文件
//通过require可以直接把json数据转换为js对象
const musicData = require("./data/music.json");
router.get("/list", async (ctx, next) => {
//把歌单数据传递给views/list.pug页面上
/*这里是介绍views/list.pug页面如何使用传递过去的musicData数据
each val,key in musicData
ul(class="listContainer "+(key%2==0?"grayBg":"") )
span(class="btnController" onclick="showDetail("+JSON.stringify(val)+")")
*/
await ctx.render("list.pug", {
musicData
});
})
- views/list.pug文件的代码:
- 注意点:关注歌曲数据musicData的使用方式,点击+号按钮触发的是showDetail()方法
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
link(rel='stylesheet', type='text/css', href='css/list.css')
script(src="js/list.js" type="text/javascript")
title Document
body
button.changeSkin 换肤
each val,key in musicData
ul(class="listContainer "+(key%2==0?"grayBg":"") )
li.grayWord #{key+1}
li #{val.songName}
li #{val.album}
li.grayWord #{val.time}
span(class="btnController" onclick="showDetail("+JSON.stringify(val)+")")
a(href='#')
img(src='img/play.png')
img(src='img/add.png')
- views/detail.pug文件的代码:
- 注意点:关注如何基于localStorage同域下跨页面数据共享的特性实现更新视图
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
link(rel='stylesheet', type='text/css', href='css/detail.css')
script(src="js/detail.js" type="text/javascript")
title Document
body
.headContainer
a.btnStyle(href='/list') 返回上一页
a.btnStyle.deleteItem 删除
a.btnStyle.deleteAll 清空列表
.listContainer
ul.myul
input(type='checkbox')
li 歌曲
li 歌手
li 时长
.exchange
- static/js/list.js文件的代码:
//该函数会接收传递过来的要添加的单个歌曲信息
function showDetail(musicData){
//console.log(musicData,'我是表示一条歌曲信息的对象:但此时还是字符串状态');
if(localStorage.getItem("musicData")){
//去重 防止单个歌曲信息被重复添加进localStorage中
let localData = JSON.parse(localStorage.getItem("musicData"));
//该音乐信息是不是被添加过了 有的话不需要再做处理了
if(!localData.find(v=>v.id==musicData.id)){
localData.push(musicData);
localStorage.setItem("musicData",JSON.stringify(localData));
}
}else{
//localStorage的key和value都要是字符串类型
//如果从来没存过的话就初始化存储一次
localStorage.setItem("musicData",JSON.stringify([musicData]));
}
//通过isOpen变量来记录/detail页面是否是已经打开的状态
//如果是打开的状态在点击打开的按钮时不应该再去打开一个新页面
//不需要一直在打开详细页面 用isOpen变量存储是该页面是否是打开的状态
if(!localStorage.getItem("isOpen")){
window.open("/detail");
}
}
- static/js/detail.js文件的代码:
//储存/detail.js的开启状态;
localStorage.setItem("isOpen",true);
//当页面关闭时候清除开启状态;
//也就是再次点击/list中的加号按钮时可以在打开一个新窗口了
window.addEventListener("beforeunload",function(){
localStorage.removeItem("isOpen");
})
//当localStorage的值变更时会触发该条件
//否则每一次都需要手动刷新页面
//监控localStorage是否有变化
window.addEventListener("storage",function(){
//更新视图;
updateView();
})
window.onload = function(){
updateView();
//点击清空所有歌曲的按钮的逻辑
document.querySelector(".deleteAll").onclick = function(){
localStorage.removeItem("musicData");
updateView();
}
//清点击清空空勾选的歌曲的按钮的逻辑
document.querySelector(".deleteItem").onclick = function(){
//拿到所有 check 的复选框
//当该复选框的checked时true代表其勾选了
let inputs =document.querySelectorAll(".exchange input");
let musicData = localStorage.getItem("musicData");
//有可能获取不到 就表示没有可清除的
musicData = JSON.parse(localStorage.getItem("musicData")) || [];
inputs.forEach((v,k)=>{
if(v.checked){
//这里的数据中 input的索引值是和musicData的数据索引是一一对应的
//所以勾选那个就删除相对应的musicData的索引即可
musicData.splice(k,1);
}
})
//基于删除后的数据再次重新写入localStorage
localStorage.setItem("musicData",JSON.stringify(musicData));
//重新写入localStorage后更新视图
updateView();
}
}
function updateView(){
let musicData = localStorage.getItem("musicData");
if(musicData){
//根据localStorage的歌曲数据组装视图
musicData = JSON.parse(musicData);
let innerContent = "";
musicData.forEach(v=>{
let str = `<ul class="myul">
<input type="checkbox" />
<li>${v.songName}</li>
<li>${v.singer}</li>
<li>${v.time}</li>
</ul>`;
innerContent+= str;
})
//替换的节点
document.querySelector(".exchange").innerHTML = innerContent;
}else{
document.querySelector(".exchange").innerHTML = "";
}
}
总结客户端存储的几种方法
- cookie是http协议下,服务端或者脚本可以维护客户端信息的一种方式。
- 客户端操作cookie特点:
-
- 浏览器会主动存储接收到的 set-cookie 头信息的值;
-
- 有时效性;
-
- 可以设置 http-only 属性为 true 来禁止客户端代码(js)修改该值
- localStorage和sessionStorage和cookie共同点:
-
- 同域(同源策略)限制:同源策略:请求与响应的 协议、域名、端口都相同 则时同源,否则为 跨源/跨域
-
- 存储的内容都会转为字符串格式
-
- 都有存储大小限制
- localStorage和sessionStorage共同点:
-
- API相同
-
- 存储大小限制一样基本类似
-
- 无个数限制
- localStorage和sessionStorage和cookie不同点:
-
- localStorage没有有效期,除非删除,否则一直存在,支持同域下页面共享,支持 storage 事件
-
- sessionStorage浏览器关闭,自动销毁,页面数据私有(
/list下设置的sessionStorage数据在 /detail下拿不到的),不支持 storage 事件
- sessionStorage浏览器关闭,自动销毁,页面数据私有(
-
- cookie
-
- 浏览器也会在每次请求的时候主动组织所有域下的cookie到请求头 cookie 中,发送给服务器端;
-
- 浏览器会主动存储接收到的 set-cookie 头信息的值;
-
- 可以设置 http-only 属性为 true 来禁止客户端代码(js)修改该值;
-
- 可以设置有效期 (默认浏览器关闭自动销毁)(不同浏览器有所不同);
-
- 同域下个数有限制,最好不要超过50个(不同浏览器有所不同);
-
- 单个cookie内容大小有限制,最好不要超过4000字节(不同浏览器有所不同)