浏览器的本地存储:
可以在浏览器的开发者工具看到:
(这就是浏览器本地存储,点x号可以删除当前选中行。)
对于上面的Object,Object,我们可以通过JSON.stringify()方法来字符串化:
<html>
<head>
<meta charset="utf-8">
<title>localStoragetitle>
head>
<body>
<h2>localStorage的演示h2>
<button onclick="saveData()">点我保存一个数据button>
<button onclick="readData()">点我读取一个数据button>
<button onclick="deleteData()">点我删除一个数据button>
<button onclick="deleteAllData()">清空localStoragebutton>
<script type="text/javascript">
function saveData(){
let p = {
name:"张三",
age:18
};
//可以window可以简写忽略
//需要注意的是这里的两个参数都必须是字符串。
window.localStorage.setItem('msg','hello,world');
//666会转成字符串形式
localStorage.setItem('msg2',666);
//如果传入的是对象,它就会显示Object纯字符串。
localStorage.setItem('person',JSON.stringify(p));
}
function readData(){
var s1 = localStorage.getItem("msg");
var s2 = localStorage.getItem("msg2");
var s3 = localStorage.getItem("person");
console.log(s1);
console.log(s2);
console.log(JSON.parse(s3));
}
function deleteData(){
localStorage.removeItem('msg2');
}
function deleteAllData(){
localStorage.clear();
}
script>
body>
html>
localStorage存储最大的特点就是关闭浏览器,再打开浏览器它的数据依然会存在。
localStorage中的数据什么时候会消失:
sessionStorage中存储数据的生命周期是一个会话时期。
功能方法也都和localStorage的操作相同,唯一要注意的就是这个数据的存活时长。
localStorage和sessionStorage统称为webStorage。
在我们上个todoLists案例中,加上浏览器本地存储的效果。
App.vue文件:
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<Header1 :receive="receive">Header1>
<List1
:todoList="todoList"
:checkTodoObj="checkTodoObj"
:deleteTodoObj="deleteTodoObj"
>List1>
<Footer1 :todoList="todoList" :checkAllTodoObj="checkAllTodoObj" :clearAllTodoObj="clearAllTodoObj">Footer1>
div>
div>
div>
template>
<script>
import Header1 from "./components/MyHeader.vue"
import Footer1 from "./components/MyFooter.vue"
import List1 from "./components/MyList.vue"
export default{
name:'App',
components:{
Header1,
List1,
Footer1,
},
data(){
return {
//获取当前页面已经存储的localStorage数据。
//如果localStorage.getItem('todoList')没有数据返回就返回了null,并且JSON解析出来也是null,这样就报错了!
//所以我们要加一个||或,前面赋值报错就只用后面的[]空数组。
todoList:JSON.parse(localStorage.getItem('todoList')) || []
}
},
methods:{
//从MyHeader中获取数据,添加todoObj对象
receive(todoObj){
//这样将值传给添加到todoList末尾中。
this.todoList.unshift(todoObj);
},
//勾选或者取消勾选一个todo
checkTodoObj(id){
this.todoList.forEach((todoObj)=>{
if(todoObj.id == id)
todoObj.done = !todoObj.done
})
},
//删除一个TodoObj
deleteTodoObj(id){
//注意:过滤出来的是一个新数组,并不是改变了data中的todoList。
//因此,要重新赋值一下。
// console.log(id)
this.todoList = this.todoList.filter((todoObj)=>{
return todoObj.id !== id
})
},
//全选or取消全选
checkAllTodoObj(done){
this.todoList.forEach((todo)=>{
todo.done = done
})
},
//清除所有已经完成的todoObj
clearAllTodoObj(){
this.todoList = this.todoList.filter((todo)=>{
return !todo.done
})
}
}
,
//实现本地存储
watch:{
todoList:{
//开启深度监视,确保对象中的内容被修改了也能重新定义localStorage中的todoList。
deep:true,
handler(newValue,oldValue){
localStorage.setItem('todoList',JSON.stringify(newValue));
}
}
}
}
script>
<style>
body{
background-color: #fff;
}
.btn{
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px rgba(0,0,0,0.05);
border-radius: 4px;
}
.btn-danger{
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover{
color: #fff;
background-color: #BD362F;
}
.btn:focus{
outline: none;
}
.todo-container{
width:600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
style>
MyHeader.vue文件:
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车确认" @keyup.enter="add">
div>
template>
<script>
import {nanoid} from 'nanoid'
export default {
name:"MyHeader",
methods:{
add(e){
//判断是否为空
if(!e.target.value.trim())
return alert('输入不能为空!')
//获取用户输入信息
console.log(e.target.value)
//包装用户信息,id使用Nanoid来操作
const todoObj = {
id:nanoid(),
title:e.target.value,
done:false
}
//当前vc拿到receive方法,直接传值就可以了。
this.receive(todoObj)
e.target.value = ''
}
},
props:['receive']
}
script>
<style scoped>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82,168,236,0.8);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}
style>
MyList.vue文件:
<template>
<ul class="todo-main">
<Item1
v-for="todoObj in todoList"
:key="todoObj.id"
:itemPro="todoObj"
:checkTodoObj="checkTodoObj"
:deleteTodoObj="deleteTodoObj"
>Item1>
ul>
template>
<script>
import Item1 from './MyItem.vue'
export default {
name:"MyList",
components:{Item1},
props:['todoList','checkTodoObj','deleteTodoObj']
}
script>
<style scoped>
.todo-main{
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty{
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
style>
MyItem.vue文件:
<template>
<li>
<label>
<input type="checkbox" :checked="itemPro.done" @click="handleCheck(itemPro.id)"/>
<span>{{itemPro.title}}span>
label>
<button class="btn btn-danger" @click="handleDelete(itemPro.id)">删除button>
li>
template>
<script>
export default {
name:"MyItem",
//声明接受todo对象
props:['itemPro','checkTodoObj','deleteTodoObj'],
mounted(){
// console.log(this.itemPro);
},
methods:{
//勾选or取消
handleCheck(id){
//通知App组件将对应的itemPro对象的done值取反
this.checkTodoObj(id)
},
//删除
handleDelete(id){
//根据用户
if(confirm('确定删除吗?')){
this.deleteTodoObj(id)
}
}
}
}
script>
<style scoped>
li{
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button{
float: right;
display: none;
margin-top: 3px;
}
li:before{
content: initial;
}
li:last-child{
border-bottom: none;
}
li:hover{
background-color: #ddd;
}
li:hover button{
display: block;
}
style>
MyFooter.vue文件:
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
label>
<span>
<span>已经完成{{doneTotal}} / 全部:{{total}}span>
span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务button>
div>
template>
<script>
export default {
name:"MyFooter",
props:['todoList','checkAllTodoObj','clearAllTodoObj'],
computed:{
total(){
return this.todoList.length
},
doneTotal(){
//reduce的使用:
//第一个参数是函数,当前todoList的数组长度为多少,就调用多少次。
//第二个参数是开始的时候的pre的起始值。
const x = this.todoList.reduce((pre,current)=>{
//这里的pre参数是上一次执行的返回W值。起始索引是0。
// console.log('pre参数:',pre)
//这里的current参数是这次执行的对象。
// console.log('current参数:',current)
return pre + (current.done ? 1:0)
},0)
// console.log("reduce的最终返回:",x)
return x;
},
isAll(){
return this.doneTotal == this.total && this.total > 0
}
},
methods:{
checkAll(e){
// console.log(e.target.checked)
this.checkAllTodoObj(e.target.checked)
},
clearAll(){
if(confirm("确定清除全部任务吗?"))
this.clearAllTodoObj()
}
}
}
script>
<style scoped>
.todo-footer{
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label{
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input{
position: relative;
top:-1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button{
float: right;
margin-top: 5px;
}
style>
注意:这里是自定义事件,并不是自定义指令,不要搞混了。
vue的组件自定义事件是通过v-on来指定或者@来指定。
常见情况:
this.$refs.student.$on(‘事件名’,function(args){ }) :开发中我们使用这种方式来绑定事件。后面的函数我们都叫它回调函数。
this.$emit(‘itholmes’,this.name,1,2,3) 触发当前this组件的itholmes事件,并给它传递多个参数。
$on和$emit一般配合使用的:
第一种方式:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据。(使用@或v-on)
第二种方式:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据。(使用ref)
App.vue文件:
<template>
<div class="app">
<h1>{{msg}}h1>
<Student v-on:itholmes.once="getStudentName">Student>
<Student ref="student">Student>
div>
template>
<script>
import School from "./components/School.vue"
import Student from "./components/Student.vue"
export default{
name:'App',
components:{Student,School},
data(){
return {
msg:"你好啊!"
}
},
methods:{
getStudentName(name,...params){
console.log("App收到了学生名:",name,params)
}
},
mounted(){
//这种方式相比较上面两种更加灵活一些。
// this.$refs.student.$on('itholmes',this.getStudentName)//绑定自定义事件
//也可以用once来操作
this.$refs.student.$once('itholmes',this.getStudentName)//绑定自定义事件一次性。
}
}
script>
<style>
.app{
background-color: gray;
padding: 5px;
}
style>
Student.vue文件:
<template>
<div class="student">
<h2>学生姓名: {{name}}h2>
<h2>学生性别: {{sex}}h2>
<button @click="seneStudentName">把学生姓名给Appbutton>
div>
template>
<script>
export default{
name:"Student",
data(){
return {
name:"张三",
sex:"男"
}
},
methods:{
seneStudentName(){
// emit英文直译:发射,排放。
//$emit来触发Student组件实例身上的itholmes事件,并且将this.name传给itholmes事件中的getStudentName函数。
this.$emit('itholmes',this.name,1,2,3)
}
}
}
script>
<style>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
style>
解绑事件的操作如下:
<template>
<div class="student">
<h2>学生姓名: {{name}}h2>
<h2>学生性别: {{sex}}h2>
<button @click="seneStudentName">把学生姓名给Appbutton>
<button @click="unbind">解绑itholmes事件button>
div>
template>
<script>
export default{
name:"Student",
data(){
return {
name:"张三",
sex:"男"
}
},
methods:{
seneStudentName(){
this.$emit('itholmes',this.name,1,2,3)
//再指定一个demo事件,来触发
this.$emit('demo')
},
unbind(){
//解绑单个事件
// this.$off('itholmes')
//解绑多个事件
// this.$off(['itholmes','demo'])
//解绑所有的自定义事件
this.$off();
}
}
}
script>
<style>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
style>
注意:使用this$refs中拿到的子组件,调用$on或者其他方法中,函数的this指向的是子组件,并不是父组件!
<template>
<div class="app">
<h1>{{msg}},学生姓名:{{studentName}}h1>
<Student ref="student">Student>
div>
template>
<script>
import School from "./components/School.vue"
import Student from "./components/Student.vue"
export default{
name:'App',
components:{Student,School},
data(){
return {
msg:"你好啊!",
studentName:""
}
},
methods:{
getSchoolName(name){
console.log('App收到了学校名:',name)
},
m1(){
console.log("demo事件被触发了。")
}
},
mounted(){
this.$refs.student.$on('itholmes',function(name,...params){
console.log("App收到了学生名:",name,params)
console.log(this)
//这样做是不可以的!因为这里的this指向的不是当前App组件,而是我们$refs中student组件。
this.studentName = name
//因此我们在这里设置name,是设置的student子组件的,并不是App组件!!
//这个坑要注意!
})
}
}
script>
<style>
.app{
background-color: gray;
padding: 5px;
}
style>
这种问题可以通过箭头函数来解决。
也可以通过在父组件创建methods函数来解决。
如果我们想给组件添加原生的一些事件,例如:click事件。组件依然会将它认为是自定义事件!
这个时候就可以使用native修饰符来修饰原生事件,让这整个组件都添加上这个原生事件。
组件的自定义组件事件是一种组件的通信方式,适用于:子组件 向 父组件发送。
Vue与VueComponent的一个重要内置关系:
全局的事件总线就是基于这个规则的。
事件常用的$on,$emit,$off都是在vue的原型对象上面,就是因为组件和vue原型对象存在一个内置的原型链关系,所以组件才能调用这些方法。
就是通过下面的vc组件的实例对象d实现的。
main.js文件:
import Vue from "vue"
import App from "./App.vue"
Vue.config.productionTip = false;
//这里如果设置了一个Object类型,Object的原型链上面是没有$emit,$on这些东西的。
// Vue.prototype.x = {a:1,b:2}
const Demo = Vue.extend({})
//这里的d就是vc组件的实例对象。
const d = new Demo();
//因此,这里我们想要实现事件总线就必须得设置一个vue实例对象或者vc组件对象
Vue.prototype.x = d;
new Vue({
el:'#app',
render:h=>h(App)
})
School.vue组件:
<template>
<div class="school">
<h2>学校姓名: {{name}}h2>
<h2>学校地址: {{address}}h2>
div>
template>
<script>
export default{
name:"School",
data(){
return {
name:"清华大学",
address:"北京"
}
},
mounted(){
// console.log('School',this)
this.x.$on('hello',(data)=>{
console.log("我是School组件,收到数据",data)
})
}
}
script>
<style>
.school{
background-color: skyblue;
}
style>
Student.vue文件:
<template>
<div class="student">
<h2>学生姓名: {{name}}h2>
<h2>学生性别: {{sex}}h2>
<button @click="sendStudentName">把学生名给School组件button>
div>
template>
<script>
export default{
name:"Student",
data(){
return {
name:"张三",
sex:"男"
}
},
methods:{
sendStudentName(){
this.x.$emit("hello",this.name)
}
}
}
script>
<style>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
style>
我们可以通过钩子函数beforeCreate来操作:
import Vue from "vue"
import App from "./App.vue"
Vue.config.productionTip = false;
new Vue({
el:'#app',
render:h=>h(App),
beforeCreate() {
//这里的this就是指向的当前vm对象。
Vue.prototype.x = this; //安装全局事件总线
}
})
一般安装全局事件总线的名字叫做$bus。
完整代码如下:
import Vue from "vue"
import App from "./App.vue"
Vue.config.productionTip = false;
new Vue({
el:'#app',
render:h=>h(App),
beforeCreate() {
Vue.prototype.$bus = this;
}
})
Student.vue文件:
<template>
<div class="student">
<h2>学生姓名: {{name}}h2>
<h2>学生性别: {{sex}}h2>
<button @click="sendStudentName">把学生名给School组件button>
div>
template>
<script>
export default{
name:"Student",
data(){
return {
name:"张三",
sex:"男"
}
},
methods:{
sendStudentName(){
this.$bus.$emit("hello",this.name)
}
}
}
script>
<style>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
style>
School.vue文件:
<template>
<div class="school">
<h2>学校姓名: {{name}}h2>
<h2>学校地址: {{address}}h2>
div>
template>
<script>
export default{
name:"School",
data(){
return {
name:"清华大学",
address:"北京"
}
},
mounted(){
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log("我是School组件,收到数据",data)
})
},
//记得在组件销毁之前,因为我们的这个$bus是一直全局都在的!需要用的就用,不需要的就销毁!
beforeDestroy(){
this.$bus.$off("hello")
}
}
script>
<style>
.school{
background-color: skyblue;
}
style>
main.js文件:
import Vue from "vue"
import App from "./App.vue"
Vue.config.productionTip = false;
new Vue({
el:'#app',
render:h=>h(App),
beforeCreate() {
//安装事件总线
Vue.prototype.$bus = this;
}
})
MyHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车确认" @keyup.enter="add">
div>
template>
<script>
import {nanoid} from 'nanoid'
export default {
name:"MyHeader",
methods:{
add(e){
//判断是否为空
if(!e.target.value.trim())
return alert('输入不能为空!')
//获取用户输入信息
console.log(e.target.value)
//包装用户信息,id使用Nanoid来操作
const todoObj = {
id:nanoid(),
title:e.target.value,
done:false
}
this.$emit("receive",todoObj)
//输入完成后清空
e.target.value = ''
}
},
}
script>
<style scoped>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82,168,236,0.8);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}
style>
MyList.vue文件:
<template>
<ul class="todo-main">
<Item1
v-for="todoObj in todoList"
:key="todoObj.id"
:itemPro="todoObj"
>Item1>
ul>
template>
<script>
import Item1 from './MyItem.vue'
export default {
name:"MyList",
components:{Item1},
props:['todoList']
}
script>
<style scoped>
.todo-main{
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty{
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
style>
MyItem.vue文件:
<template>
<li>
<label>
<input type="checkbox" :checked="itemPro.done" @click="handleCheck(itemPro.id)"/>
<span>{{itemPro.title}}span>
label>
<button class="btn btn-danger" @click="handleDelete(itemPro.id)">删除button>
li>
template>
<script>
export default {
name:"MyItem",
//声明接受todo对象
props:['itemPro'],
mounted(){
// console.log(this.itemPro);
},
methods:{
//勾选or取消
handleCheck(id){
//通知App组件将对应的itemPro对象的done值取反
this.$bus.$emit('checkTodoObj',id)
},
//删除
handleDelete(id){
//根据用户
if(confirm('确定删除吗?')){
this.$bus.$emit("deleteTodoObj",id)
}
}
}
}
script>
<style scoped>
li{
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button{
float: right;
display: none;
margin-top: 3px;
}
li:before{
content: initial;
}
li:last-child{
border-bottom: none;
}
li:hover{
background-color: #ddd;
}
li:hover button{
display: block;
}
style>
MyFooter.vue文件:
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
label>
<span>
<span>已经完成{{doneTotal}} / 全部:{{total}}span>
span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务button>
div>
template>
<script>
export default {
name:"MyFooter",
props:['todoList'],
computed:{
total(){
return this.todoList.length
},
doneTotal(){
//reduce的使用:
//第一个参数是函数,当前todoList的数组长度为多少,就调用多少次。
//第二个参数是开始的时候的pre的起始值。
const x = this.todoList.reduce((pre,current)=>{
//这里的pre参数是上一次执行的返回W值。起始索引是0。
// console.log('pre参数:',pre)
//这里的current参数是这次执行的对象。
// console.log('current参数:',current)
return pre + (current.done ? 1:0)
},0)
// console.log("reduce的最终返回:",x)
return x;
},
isAll(){
return this.doneTotal == this.total && this.total > 0
}
},
methods:{
checkAll(e){
// console.log(e.target.checked)
this.$emit("checkAllTodoObj",e.target.checked)
},
clearAll(){
if(confirm("确定清除全部任务吗?"))
this.$emit("clearAllTodoObj")
}
}
}
script>
<style scoped>
.todo-footer{
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label{
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input{
position: relative;
top:-1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button{
float: right;
margin-top: 5px;
}
style>