# vue
# Vue中的MVVM
# 什么是MVVM
1.view层---body内的标签
视图层,在前端里面就是我们常说的DOM层,主要作用是给用户展示各种信息
2.Model层---new Vue对象
数据层,数据可能是我们自定义的数据,或者是从网络请求下来的数据
3.ViewModel层---vue.js文件
视图模型层,是view层和model层沟通的桥梁;
一方面它实现了数据绑定,将model的改变实时反应到view中;另一方面它实现了DOM监听,当DOM发生改变可以对应改变数据(Data)
# 实例对象
new Vue({
el:"#app",
data:{
键:值
},
methods:{
方法名:function(){}
方法名(){}
},
computed:{
方法名(){
计算逻辑
return 计算结果;
}
},
filters:{
方法名(形参){
过滤逻辑
return 过滤后的结果
}
},
directives:{
'自定义指令名':{
bind(el,binding){},
update(el,binding){}
}
},
components:{
'组件名':{
template:'html代码或组件的模版id',
data(){
return{
键:值
}
}
}
}
})
el:new Vue对象的操作范围
data:数据
methods:普通方法
computed:计算属性
filters:过滤器
directives:自定义指令
components:组件
# 1. methods 普通方法
如果在方法中需要获取data中的数据,需要使用 this.键
# 2. computed 计算属性
定义:要用的属性不存在,要通过已有属性计算得来
原理:底层借助了 Object.defineProperty方法提供的getter 和 setter方法
get函数什么时候执行?
- 初次读取时会执行一次
- 当依赖的数据发生改变时会再次调用
优势:与methods相比,内部有缓存机制(实现复用效果),效率高,调试方便
备注:
计算属性最终会出现在vm上,直接读取使用即可
如果计算属性要被修改,那必须写set函数进行相应的修改
只要data中的数据发生改变,Vue便会重新解析模版,遇到插值语法一定会再重新调用一下
- 如何让计算属性中的方法多次执行?
- 让返回值发生改变
- 计算属性侧重于得到一个计算的结果,因此方法中必须有return 返回值
- 计算属性中的方法被调用时不需要加()
# methods 和 computed 的区别
调用的时候methods需要使用(),而computed不需要()
methods调用的时候吧,多次调用多次执行
computed如果方法的返回值没有改变就只执行一次,多次调用的结果是在内存中获取数据
# get 和 set
computed:{
方法名(){
计算逻辑
return 计算结果;
}
}
computed:{
自定义属性名:{
get(){
//获取该自定义属性的时候,执行这里的代码
},
set(){
//自定义属性被修改时才来执行这里的代码
}
}
}
# 3. filters 过滤器
- 过滤器的主要作用就是过滤数据,让数据按照我们规定的格式来做展示
- 过滤器要先定义后使用
- 过滤器中的方法必须要有return,如果没有,页面什么都不会渲染。
- return返回的数据就是过滤后的数据,经过返回后,就会在页面上渲染过滤后的数据
- 过滤方法的形参用于接收要过滤的数据,过滤器方法在调用时不需要加()
# 局部过滤器
//过滤器的使用
<div id="app">
{{要过滤的数据 | 过滤器的名字}}
<p :price="要过滤的数据 | 过滤器的名字"></p>
</div>
<script>
new Vue({
el:'#app',
data:{},
//过滤器的定义
filters:{
过滤器名字(形参){
//形参接收 | 前面的要过滤的数据
过滤逻辑
return 过滤结果
}
}
})
</script>
# 全局过滤器
<script>
Vue.filter('过滤器名',(形参)=>{
return 过滤后的结果
})
new Vue({
el:'',
data:{}
})
</script>
# 过滤器的使用场景
人民币:¥ 100 元
<script> Vue.filter('RMBformat', val => { return "¥ " + Number(val).toFixed(2) + " 元" }) </script>
时间戳:new Date().getTime()
<script> Vue.filter('dateFormat',val=>{ let dateObj = new Date(val); let year = dateObj.getFullYear(); let months = dateObj.getMonth()+1; months = months < 10 ? '0' + months : months; let days = dateObj.getDate(); days = days < 10 ? '0' + days : days; let hours = dateObj.getHours(); hours = hours < 10 ? '0' + hours : hours; let minutes = dateObj.getMinutes(); minutes = minutes < 10 ? '0' + minutes : minutes; let seconds = dateObj.getSeconds(); seconds = seconds < 10 ? '0' + seconds : seconds; return `${year}年${months}月${days}日 ${hours}:${minutes}:${seconds}` }) </script>
# 4. directives 自定义指令
自定义指令在定义的时候,不需要使用 v-
# 局部自定义指令
<div id="app">
<元素 v-自定义指令名="指令值"></元素>
</div>
<script>
new Vue({
el:'#app',
data:{},
directives:{
'自定义指令名':{
bind(el,binding){
//当这个自定义指令被执行的时候自动触发该函数(页面一刷新就绑定上了)
//el:绑定该指令的标签
//binding:{name: '自定义指令名', rawName: 'v-自定义指令名', value: '自定义指令的指令值', expression: '指令值的键'}
},
update(el,binding){
//当该自定义指令的指令值发生变化时会自动触发该函数
//el:绑定该指令的标签
//binding:除value的值会变成修改后的新值外,其他参数会和bind的binding一样
}
}
}
})
</script>
bind和update函数都是钩子函数
钩子函数:不需要手动调用,在某种条件满足时会自动触发的函数就是钩子函数
# 全局自定义指令
<script>
Vue.directive('自定义指令名',{
bind(el,binding){
//el:绑定该指令的元素
//binding:是一个对象{},其中value表示当前指令对应的真实值
},
update(el,binding){
}
})
</script>
# 5. components 组件
组件:就是为了封装html代码,可以将重复出现的html代码进行封装,之后在哪里使用,直接使用组件即可
组件分为全局组件 和 局部组件
# 全局组件
- 封装在new Vue对象外的组件,可以在任何对象中使用
- 必须在new Vue对象之前声明
- 组件名如果使用小驼峰命名,那么在使用组件的时候就需要写成<xx-yy />
<body>
<div id="app"> //3.组件的使用
<组件名></组件名>
</div>
</body>
<template id="组件的模版id"> //2.模版的声明:模版就是把要封装的html代码单独存储起来
<唯一根元素>
html代码
</唯一根元素>
</template>
//1.组件的声明
<script>
Vue.component('组件名',{
template:'html代码或组件模板id'
})
new Vue({
el:'',
data:{}
})
</script>
# 局部组件
- 声明在new Vue对象中的组件
- 只能在当前的new Vue对象的操作范围内使用
//3.局部组件的使用
<body>
<div id="app">
<组件名></组件名>
</div>
</body>
//2.模版的声明
<template id="组件模版id">
<div>
{{局部组件中的数据}}
</div>
</template>
//1.声明局部组件
<script>
new Vue({
el:'',
data:{},
components:{
'组件名':{
template:'组件模版id或html代码',
data(){
return {
键:值
}
}
}
}
})
</script>
# 父子组件
- 父子组件就是在组件中还有组件
- 父组件的使用的时候需要在new Vue对象的范围内使用
- 子组件需要在父组件的模版中使用
- 模版在声明的时候不分父子关系,都是同级的
- 组件中除了data(){}外,其他属性的声明与Vue对象中相同
//3.组件的使用
<body>
<div id="app">
<父组件名></父组件名>
</div>
</body>
//2.父子组件模版的声明
<template id="父模版id">
<div>
<子组件名></子组件名>
</div>
</template>
<template id="子模版id">
<div>
{{子组件中的数据}}
</div>
</template>
//1.声明父子组件
<script>
new Vue({
el:'#app',
data:{},
components:{
'父组件名':{
template:'父组件模版id',
data(){},
components:{
'子组件名':{
template:'子组件模版id',
data(){}
}
}
}
}
})
</script>
# 动态组件
动态组件其实可以认为是组件的占位,在使用组件的时候,不直接使用组件名的形式而是使用一个内置的标签
:is
的值是一个变量,这个变量是哪个组件名,该元素所占的位置就加载并渲染哪个组件
<div id="app">
<component :is="变量"></component>
</div>
# 插槽
# 普通插槽
插槽:就是可以让我们封装的组件更灵活,可以让使用组件的人去决定组件中出现的内容,通常把组件的灵活不确定的内容使用slot标签进行占位,在使用组件的时候 在组件标签中间给插槽设置内容
组件:封装html代码
//组件内部
<div id="app">
<父组件名>
<要插入的元素></要插入的元素>
</父组件名>
</div>
//组件模版
<template id="父模版id">
<div>
<slot></slot>
</div>
</template>
就是将父组件内的html标签插入到父模版的slot处
# 具名插槽
如果在组件声明的时候,灵活的内容比较多,我们就可以使用具名插槽,具名插槽就是给插槽命名
//组件内部
<div id="app">
<父组件名>
<要插入的元素 slot="插槽名"></要插入的元素>
</父组件名>
</div>
//组件模版
<template id="父模版id">
<div>
<slot name="插槽名"></slot>
</div>
</template>
# 插槽传值
<div id="app">
<父组件名>
<父组件名 slot-scope="自定义属性" @click="fn(自定义属性)"></父组件名>
</父组件名>
</div>
<template id="父模版id">
<div>
<slot :自定义属性="单一数据或对象"></slot>
</div>
</template>
# 6. 生命周期函数
在vue2中共有8个生命周期函数,都是钩子函数
其中自动触发的有:
- beforeCreate(){}
- created(){}
- beforeMount(){}
- mounted(){}
满足条件后触发的有:
- beforeUpdate(){}
- updated(){}
- beforeDestroy(){}
- destroyed(){}
<script>
new Vue({
el:'#app',
data:{},
methods:{},
computed:{},
filters:{},
directives:{},
components:{},
beforeCreate(){
//创建vue对象之前,此时还没有data和methods
},
created(){
//创建vue对象之后,此时data和methods已经存在,但是模版还未渲染,最适合做数据请求
},
beforeMount(){
//挂载(渲染)之前,此时data和methods已经存在,然后模版已经编译完成,已经发送给页面,但页面还没有渲染
},
mounted(){
//挂载(渲染)之后,数据已经渲染
},
beforeUpdate(){
//修改之前,当数据被修改时触发该函数,此时data中的数据已经被修改,但是页面还未渲染新数据
},
updated(){
//修改之后,当数据修改时触发,此时data中的数据已经被修改,同时修改后的数据也已经重新渲染到页面上了
},
beforeDestory(){
//销毁之前
},
destroyed(){
//销毁之后
}
})
</script>
# 指令
指令分为系统指令和自定义指令两种
# 系统指令
系统指令:vue封装好的指令,我们可以直接使用
系统指令的语法格式 v-指令="指令值"
不是所有的指令都必须设置指令值,如:v-pre
、 v-cloak
# 数据绑定 v-model
数据绑定,只能用于表单,用于让表单的value值和data中的键进行双向绑定,彼此间互相影响。
1.普通的输入型表单
<div id="app">
<input type="text" v-model="val"/>
</div>
<script>
new Vue({
el:'#app',
data:{
val:''
}
})
</script>
绑定关系就是表单中的value值和data中的键绑定,它们的值互相影响,双向绑定
2.单选按钮
<div id="app">
<input type="radio" value="男" v-model="gender" />
</div>
<script>
new Vue({
el:'#app',
data:{
gender:''
}
})
</script>
单选按钮,用户不能直接输入,只能选择,所以将选择的按钮的value值与data中的键绑定
3.多选按钮
//不需要value值,就将选中的状态与data中的键进行绑定
<div id="app">
<input type="checkbox" v-model="bool" />
</div>
<script>
new Vue({
el:'#app',
data:{
bool:''
}
})
</script>
这里的键bool是布尔类型的,如果按钮被选中就是true,没有被选中就是false
//需要value值,
<div id="app">
<input type="checkbox" value="值1" v-model="vals"/>
<input type="checkbox" value="值2" v-model="vals"/>
<input type="checkbox" value="值3" v-model="vals"/>
<input type="checkbox" value="值4" v-model="vals"/>
</div>
<script>
new Vue({
el:'#app',
data:{
vals:[]
}
})
</script>
这里的键vals是数组类型的,按钮被选中后会将其对应的value值与键进行绑定,从而影响数据的变化
4.下拉列表
<div id="app">
<select v-model="item">
<option value="值1">值1的描述</option>
<option value="值2">值2的描述</option>
</select>
</div>
<script>
new Vue({
el:'#app',
data:{
item:''
}
})
</script>
下拉列表属于单选,选择后将对应的option的value值与v-model绑定的键进行绑定
# 插值语法 v-html
v-text
{{}}
v-html
和 v-text
都是插值语法,两者的区别是:
v-html
支持代码的解析v-text
等价于{{}}
,使用它俩渲染数据,默认都不支持代码的解析,可以用来防治XSS攻击- XSS攻击:XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页
{{}}
除了可以进行数据的渲染和方法的调用外,还支持一些书写业务逻辑,复杂的业务逻辑不建议书写在内,影响代码的可读性
<div id="app">
<p v-html="str"></p>
<p v-text="str"></p>
<p>{{bool?"成功":"失败"}}</p>
</div>
<script>
new Vue({
el:'#app',
data:{
str:"<a href="https://www.xiaosutongxue.com">欢迎来到小苏同学的个人站</a>",
bool:true
}
})
</script>
# 原样输出 v-pre
可以让设置该指令的元素不解析胡子语法
<div id="app">
<p v-pre>这是vue的插值语法:{{val}}</p>
</div>
<script>
new Vue({
el:'#app',
data:{
val:'小苏同学'
}
})
</script>
# 元素显示与隐藏 v-if
v-show
v-if
和 v-show
都可以设置元素的显示与隐藏,两者的区别在于:
v-if
控制元素的显示与销毁,如果值为true
,就显示该元素,如果值为false
就删除该元素v-show
控制元素的显示与隐藏,如果值为true
就显示该元素,如果值为false
就隐藏该元素,这里的隐藏就是为该元素设置style
属性,并设置样式为display:none;
v-if
和 v-show
的使用场景:
- 如果频繁操作元素显示不显示,那么就使用
v-show
,如果不频繁显示或不显示元素,就使用v-if
<div id="app">
<p v-if="bool">我是设置v-if的p元素</p>
<p v-show="bool">我是设置v-show的p元素</p>
</div>
<script>
new Vue({
el:'#app',
data:{
bool:false
}
})
</script>
v-if
的扩展
//当v-if的属性值为真时,则v-else不显示,当v-if的值为假时,v-else显示
<p v-if="bool">我是p1</p>
<p v-else>我是p2</p>
//注意v-else不能直接出现,需配合v-if成对使用,两者中间不能插其他数据
//扩展 v-if、v-else-if 和 v-else
<p v-if="type=='a'">我是a</p>
<p v-else-if="type=='b'">我是b</p>
<p v-else-if="type=='c'">我是c</p>
<p v-else>其他</p>
# 斗篷语法 v-cloak
由于 vue.js
文件的引入都写在后边,同时 vue.js
文件在加载的时候,需要时间(特别是引入在线的cdn链接),由于网络原因出现加载过慢的现象,如果加载过慢,就会导致后续代码需要等待,导致页面出现胡子语法。
v-cloak
防止页面初始化闪动问题,在没有加载 vue.js
文件并没有实例化vue对象的时候先隐藏元素,防止出现闪动
<style>
[v-cloak]{
display:none;
}
</style>
<div id="app">
<div v-cloak>
<p>{{name}}</p>
<p>{{age}}</p>
<p>{{gender}}</p>
</div>
</div>
<script>
new Vue({
el:'#app',
data:{
name:'苏东旭',
age:26,
gender:'男'
}
})
</script>
# 事件绑定 v-on
或 @
<div id="app">
<button v-on:click="fn()">按钮1</button>
<button @:click="fn()">按钮2</button>
//使用文本插值的方式调用方法
{{fn()}}
</div>
<script>
new Vue({
el:'#app',
data:{},
methods:{
fn(){
alert('我被触发了')
}
}
})
</script>
# 属性绑定 v-bind
或 :
html标签中的属性,默认是不能解析变量的(默认不能写vue语法)
我们需要解决这个问题就需要进行属性绑定,属性绑定是单向的,数据只能从data内流向页面
属性绑定分为三种:
普通属性绑定
<元素 :属性="键"></元素>
style属性绑定
<元素 :style="{'css属性':键}"></元素>
class属性绑定
<元素 :class="{'类名':布尔值}"></元素>
<元素 :class="[{'css属性':键},'类名',布尔值?'类名1':'类名2']"></元素>
//普通属性绑定
<div id="app">
<a :href="aHref">{{str}}</a>
</div>
<script>
new Vue({
el:'app',
data:{
aHref:'http://www.xiaosutongxue.com',
str:'小苏同学的个人站'
}
})
</script>
//属性绑定给style
<div id="app">
<span :style="{'color':color}">小苏同学</span>
</div>
<script>
new Vue({
el:'#app',
data:{
color:'blue'
}
})
</script>
//属性绑定给class
<div id="app">
<span :class="{'类名':bool}"></span>
<span :class="['类名',bool?'类名1':'类名2',{'类名':bool}]"></span>
</div>
<script>
new Vue({
el:'#app',
data;{
bool:true
}
})
</script>
{'类名':bool}:当布尔值为true时,就让前面的类名生效,此处除了是true或false外,也可以是比较运算符
# 数据循环 v-for
<元素 v-for="(值,键) in 要循环的数据" :key="唯一的标识"></元素>
不加key属性会存在隐患
:key 的值就是浏览器判断是否需要进行DOM更新的依据。
如果这个依据在操作前后没有发生变化,浏览器不会进行DOM更新
如果某些元素的这个依据发生改变,浏览器就会对其进行DOM更新
:key的作用:减少了不必要的DOM操作,提高了更新效率
<div id="app">
<ul>
<li v-for="(item,index) in list" :key="item">{{item}}</li>
</ul>
</div>
<script>
new Vue({
el:'#app',
data:{
list:['钢铁侠','蜘蛛侠','绿巨人']
}
})
</script>
# 自定义指令
- 自定义指令名在定义的时候,不需要使用 v-
- bind和update函数都属于钩子函数
- 钩子函数:不需要手动调用,在某种条件满足时,会自动触发的函数就叫做钩子函数
- bind函数的触发时机:当这个自定义指令被使用时就会自动触发
- update函数的触发时机:当自定义指令的指令值发生改变的时候会自动触发该函数
//1.声明语法格式
<script>
new Vue({
el:'',
data:{},
.......
directives:{
'自定义指令名':{
bind(el,binding){
//el参数:绑定该指令的标签
//binding参数:是一个对象
},
update(el,binding){
//el参数:绑定该指令的标签
//binding参数:除value值外,其它参数与bind的binding一样
}
}
}
})
</script>
//2.使用自定义指令
<元素 v-自定义指令="指令值"></元素>
# reduce方法(js的方法)
reduce方法遍历数组的每一个元素,reduce()调用结果最后返回一个最终值(最后一次return值)
var arr = [{
name: 'Vuejs入门',
price: 99,
count: 3
}, {
name: 'Vuejs底层',
price: 89,
count: 1
}, {
name: 'Vuejs从入门到放弃',
price: 19,
count: 5
}]
//数组名.reduce(回调函数,pre的初始值)
let ret = arr.reduce(function (pre, current) {
//reduce这个方法被调用时,会遍历arr这个数组的每一元素,每遍历一个元素,就执行一次这里的代码
//current表示当前正在遍历的这个元素
//pre是上一次的这个函数的return的值
//!!!因为第一个遍历没有上一个rerurn值,所以交给了第二个参数,设置pre的初始值
console.log(pre, current);
return 10
}, 0)
//ret可以接收到最后一次遍历执行的这个函数的返回值
求所有书籍价格的总和
let total = arr.reduce((pre,current)=>{
return pre+current.price*current.count
},0)
console.log(total)
# 数组的 filter 和 map 方法
# filter
var arr = [1,2,3]
let newArr = arr.filter((item,index,self)=>{
//遍历数组中每一个元素,每遍历一次元素就执行一遍
//item就是当前遍历到的这个元素
//index就是当前遍历到的这个元素的索引
//self就是当前遍历的这个数组 arr
//return 过滤条件(满足需求的条件)
return item>2
})
满足过滤条件的元素会组成新数组
# map
对每个元素进行相同的操作,逐个加工
var arr = [1,2,3]
arr.map(item=>{
//遍历数组中每一个元素,每遍历一次元素就执行一遍
//item就是当前遍历到的这个元素
})
# 数组去重
var arr = [1,2,3,1,6,2,3]
//ES6
console.log([...new Set(arr)])
console.log(Array.from(new Set(arr)))
//古老的方式
let newArr = []
for(var i=0;i<arr.length;i++){
if(newArr.indexOf(arr[i])==-1){
newArr.push(arr[i])
}
}
console.log(newArr)
//filter方法
let newArr = arr.filter((item,index,self)=>{
return self.indexOf(item) == index
})
console.log(newArr)
# 本地存储
# localStorage永久存储
<script>
//存储数据 以键和值的形式来保存
localStorage.setItem('键',值)
//获取数据
localStorage.getItem('键')
//指定删除对应的数据
localStorage.removeItem('键')
//清空所有数据
localStorage.clear()
</script>
# sessionStorage临时会话存储
<script>
//存储数据
sessionStorage.setItem('键',值)
//获取数据
sessionStorage.getItem('键')
//指定删除对应的数据
sessionStorage.removeItem('键')
//清空所有数据
sessionStorage.clear()
</script>
# 二者区别
localStorage除非主动删除,不然是不会自动删除的
sessionStorage关闭浏览器会自动清空数据
一般浏览器的存储大小是5M,5M = 1024 * 5kb
两者存储的值都是字符串类型的数据
注意:只能存储字符串类型的数据,如果是对象格式需要进行转换
<script>
let obj = {
name:'vue',
age:9
}
//存储数据
localStorage.setItem('myobj',JSON.stringify(obj))
//获取数据
JSON.parse(localStorage.getItem('myobj'))
</script>
# cookie
cookie 和 session 都是在服务器端设置的
虽然js能获取cookie信息,但是后端可以禁用不让前端获取cookie
网站中,http请求是无状态的。
也就是第一次登录成功(发送请求),第二次请求服务器依然不知道是哪一个用户。这个时候cookie就是解决这个问题的,第一次登录后服务器返回数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求,浏览器自动会把上次请求存储的cookie数据自动带上给服务器,服务器根据客户端的cookie来判断当前是哪一个用户。cookie存储有大小限制,不同浏览器不一样,一般是4kb,所以cookie只能存储小量数据。
4kb = 4 * 1024 byte = 4 * 1024 * 8 bit
session和cookie有点相似,也是存储用户相关信息。不同的是cookie存储在浏览器,而session存储在服务器
# Object.defineProperty()
object.defineProperty() 用来给对象添加属性
语法:
Object.defineProperty(参数1,参数2,参数3)
参数1: 要添加属性的这个对象
参数2: 添加的这个属性名,以字符串的格式书写
参数3: 配置项(这个属性的值,这个属性可不可以修改删除遍历等)
<script>
let obj = {
name:'Vue'
}
//另一种写法
let obj = {}
Object.defineProperty(obj,'name',{
value:'Vue',
writable:true, //设置这个属性的值可否修改(默认为false)
configurable:true, //设置这个属性可否删除(默认为false)
enumerable:true //设置这个属性可否遍历(默认为false)
})
console.log(obj)
</script>
# get和set方法
<script>
let obj = {}
let val = 'Vue'
Object.difineProperty(obj,'name',{
get(){
console.log('执行了get')
return val
},
set(newVal){
//数据劫持
console.log('执行了set')
val = newVal
}
})
console.log(obj) //点击...获取属性值时触发get方法
console.log(obj.name) //直接触发get方法的执行,get函数中的返回值就是这个属性的值
obj.name = 'VueVue' //被set劫持,set进行相应操作后,再获取name时会返回新值,否则返回原值
console.log(obj.name)
</script>
# 双向数据绑定原理
Vue框架特点:双向数据绑定与组件化开发
Vue最独特的特性之一,是其非侵入式的响应式系统。数据模型仅仅是普通的JavaScript对象。而当你修改它们时,视图会更新。
Vue是怎么做得到的呢?
- 其实就是使用了
Object.defineProperty
把Vue内的属性全部转成getter
/setter
。Object.defineProperty
是ES5中一个无法shim的特性,这也就是Vue不支持IE8以及更低版本浏览器的原因。这句话的意思就是Object.defineProperty
这个特性是无法使用低级浏览器中的方法来实现的,所以Vue不支持IE8以及更低版本的浏览器。- 一个shim是一个库,它将一个新的API引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现。 比如:google和github上都有一段用于兼容ie等低版本浏览器的html标签库 html5shiv,ps: shim有时也叫shiv。
Object.defineProperty
实现了实现了 对象劫持 这个功能
https://github.com/vuejs/vue/blob/1.0/src/observer/index.js
https://github.com/vuejs/vue/blob/2.6/src/core/observer/index.js
Vue实现双向数据绑定的原理
Vue数据双向绑定通过数据劫持结合‘’发布者-订阅者模式“的方式来实现的
借助 Object.defineProperty()
对数据进行劫持,其中会有getter()和setter()方法;当读取属性值时,就会触发getter()方法,在view中如果数据发生了变化,就会通过 Object.defineProperty()
对属性设置一个setter函数,当数据改变了就会来触发这个函数;
参考:https://segmentfault.com/a/1190000014274840
参考:https://zhuanlan.zhihu.com/p/51357583
<body>
<input type="text" id="inp">
<p id="op"></p>
</body>
<script>
let obj = {}
let val = '默认值'
Object.defineProperty(obj, 'inpVal', {
get() {
return val
},
set(newVal) {
console.log(newVal, 'set');
inp.value = newVal
op.innerHTML = newVal
}
})
inp.value = obj.inpVal
op.innerHTML = obj.inpVal
inp.addEventListener('keyup', function (e) {
//修改value值
console.log(e.target.value, 'e.target');
obj.inpVal = e.target.value;
})
</script>
# Vue中的数据代理
通过vm对象来代理data对象中的属性的操作(读/写)
Vue中数据代理的好处
更加方便的操作data中的数据
基本原理
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter / setter
在getter / setter 内部去操作(读 / 写)data中对应的属性
# 组件传值
组件之间是不能通讯的(默认数据是不能互相使用的)
父组件在使用的时候需要在#app的范围内使用
子组件需要在父模版中使用
模版声明时不分父子关系,是同级的
# 父传子
就是在子组件中使用父组件的数据
由于我们可以在父模版中使用父组件的数据,然后子组件又是在父模版中使用的。所以可以为子组件标签设置一个自定义属性,然后将父组件的数据作为该自定义属性的属性值,然后在子组件的props属性中声明该自定义属性用于接收父组件传递的数据。这样在子模版中使用该自定义属性就等价于使用父组件的数据
//1.组件声明
<script>
new Vue({
el:'#app',
data:{},
components:{
'父组件名':{
template:'父模版id',
data(){
return {
父键:值
}
},
components:{
'子组件名':{
template:'子模版id',
props:{
'自定义属性':{
type:String | Array | Object,
default:'' | [] | {}
}
}
}
}
}
}
})
</script>
//2.模版声明
<template id="父组件id">
<div>
<子组件名 :自定义属性="父组件的数据"></子组件名>
</div>
</template>
<template id="子组件id">
<div>
{{自定义属性}}
</div>
</template>
//3.使用
<div id="app">
<父组件名></父组件名>
</div>
# 子传父
就是在父组件中使用子组件的数据
子传父需要一个契机,就是在子模版中为一个元素添加点击事件,事件触发的方法声明在子组件的methods中,在该方法内通过 this.$emit
方法将子组件的数据绑定给自定义事件,然后为父模版中的子组件标签绑定该自定义事件,自定义事件触发的方法不需要加(),同时将该方法声明在父组件的methods中,并将方法的形参赋值给父组件中的数据,然后在父模版中使用该数据就等价于使用子组件中的数据
<script>
new Vue({
el:'#app',
data:{},
components:{
'父组件名':{
template:'父模版id',
data(){
return {
父键:值
}
},
methods:{
receive(v){
this.父键 = v
}
},
components:{
'子组件名':{
template:'子模版id',
data(){
return {
子键:值
}
},
methods:{
send(){
this.$emit('son',this.子键)
}
}
}
}
}
}
})
</script>
//2.模版声明
<template id="父组件id">
<div>
{{父组件中的键}}
<子组件名 @son="receive"></子组件名>
</div>
</template>
<template id="子组件id">
<div>
<button @click="send()">向父组件传值</button>
</div>
</template>
//3.使用
<div id="app">
<父组件名></父组件名>
</div>
# 修饰符
# 事件修饰符
阻止事件冒泡,需要写在事件的后面
.stop
阻止默认行为
.prevent
阻止事件重复触发(不能和.stop一同使用,否则会出现冒泡)
.once
事件的捕获模式
.capture
事件的默认行为立即执行,无需等待事件回调执行完毕(移动端常用)
.passive
注意:
超链接存在默认行为和事件冒泡,可以同时使用.stop.prevent,这样既能阻止默认行为,又能阻止冒泡
# 键盘修饰符
.enter 回车
.tab 换行(配合keydown使用才生效)
.delete 删除(捕获删除和退格键)
.esc 退出
.space 空格
.up 上
.left 左
.........
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
CapsLock caps-lock
系统修饰键(用法特殊):ctrl、alt、shift、meta(win键)
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
- 配合keydown使用:正常触发事件
也可以使用keyCode去指定具体的按键(不推荐)
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
# 表单修饰符
去除两端空白符
.trim
将表单中的内容类型转为数字类型
.number
# 监听 watch
可以监听一个值的变化,如果一个值发生改变,就会触发监听中的方法
# 普通监听
data(){},
watch:{
要监听的数据名(参数1,参数2){
参数1:新值
参数2:老值
}
}
watch:{
要监听的数据名:{
handler(新值,旧值){}
}
}
# 立即监听
immediate:true
设置该属性就表示立即监听,
程序运行时立即监听一次,当数据发生变化时会触发执行
data(){},
watch:{
要监听的数据名:{
handler(新值,旧值){},
immediate:true
}
}
# 深度监听
deep:true
设置该属性就表示深度监听
我们监听的数据不能是对象,如果是对象,那么对象的数据即使发生变化,我们也无法监听,那么如果需要监听对象中的数据变化,就必须使用deep属性,并设置为true
深度监听不推荐使用,因为它监听整个对象,比较消耗性能,所以我们可以指定去监听对象中的指定值,但是需要注意,如果指定了对象中的某一个数据的监听,那么必须写成字符串格式,deep属性可以省略
watch:{
要监听的数据名:{
handler(新值,旧值){},
deep:true
}
}
# 监听和计算属性的区别
- 监听函数无需调用,而计算属性需要调用,并且调用时不加()
- 监听函数监听的是属性的变化,拿变化后的属性去做后续的操作
- 计算属性是通过它依赖的属性发生变化后得到的值,并且数据的结果会被缓存,且函数中必须通过return返回最终的结果
- 使用场景
- 当一个结果受多个属性影响的时候就需要使用计算属性computed
- 当一个数据的变化参与到后续操作的时候需要使用监听watch
# 混入 mixins
可以将公共的数据和方法声明在混入中,如果是全局混入,可以在任何的组件中直接使用混入中的数据或方法,如果是局部混入,哪个组件引入该混入,就可以在哪个组件中使用该混入中的数据和方法
# 全局混入
必须声明在 main.js
中,且在 new Vue 对象之前
Vue.mixin({
data(){
return {
键:值
}
},
methods:{
方法名(){}
}
})
new Vue({
router,
store,
render:h=>h(app)
}).$mount('#app')
# 局部混入
局部混入在src下创建一个mixins目录,里面创建一个index.js文件,在文件内书写局部混入的数据和方法
在任何组件中都可以使用,使用的前提是必须先引入
引入方式有两种:
export
:通过export方式导出,在导入的时候需要加{},且不能换名字(导出啥名,导入就啥名,名字加到{})import {aaa,bbb,ccc导出的时候的名字} from "路径+文件名"
export default
:通过export default方式导出,导入时不需要加{}import 名字 from "路径+文件名"
- 使用场景
- 导出单个值就用
export default
- 导出多个值就用
export
- 导出单个值就用
局部混入
//声明
export const Mixins={
data(){
return {
键:值
}
},
methods:{
方法名(){
方法体
}
}
}
//引入
import {Mixins} from '@/mixins/index.js'
//注册
export default {
//局部混入需要注册
mixins:[Mixins],
data () {
return {
};
},
components: {},
methods: {}
}
//使用
<template>
<div>
{{局部混入的数据}}
{{局部混入的方法()}}
</div>
</template>
# ref 和 $refs
ref类似于标签中的id
ref不但可以绑定给html标签,还可以绑定给组件
我们可以在组件中,通过ref获取组件中的某些数据
//通过ref绑定
<template>
<div>
<组件名 ref="xxx"></组件名>
<button @click="fun()">按钮</button>
</div>
</template>
//通过$refs获取
<script>
import 组件名 from '@/components/组件名.vue'
export default {
data(){},
components:{
组件名
},
methods:{
fun(){
console.log(this.$refs.ref属性值.子组件的键);
this.$refs.ref属性值.子组件方法();
}
}
}
</script>
# keepAlive
keepAlive是vue中的一个内置组件,它可以在组件切换的时候保留状态,防止重复渲染DOM
两个关于路由的对象
- $route 当前活动路由对象
- $router 所有路由对象
可以使用$route获取meta中的keepAlive的值来实现选择性保留状态
{
path:'路由规则'
name:'路由名字'
component:路由对应的组件
meta:{
keepAlive:true
}
}
<template>
<div>
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
</template>
# vuex
vuex是一个专为vue.js应用程序开发的一种状态管理模式,它主要采用的是集中化管理所有组件中的状态
说的直白一些,vuex就是vue.js中管理数据状态的一个库,也就是我们可以将所有组件中的公共数据在vuex中集中管理
我们在安装脚手架项目的时候,选择安装vuex,安装完成之后,可以在项目的目录生成一个store的目录,在目录中会生成一个index.js的文件,这就是书写vuex的数据位置
index.js文件中会有5个属性
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//用于存储数据
},
getters: {
//类似于计算属性,当值发生改变时,自动触发
},
mutations: {
//同步属性,用于修改state中的数据
方法名(state,payload){
//state
//payload:载荷(就是你在调用这个函数的时候传递进来的修改内容)
}
},
actions: {
//异步属性
方法名({commit},payload){
//参数1:是一个对象
//参数2:载荷 调用异步修改方法时传递的第二个参数
}
},
modules: {
//模块管理
}
})
注意:
同步属性不能执行异步操作
state:用于存储数据的,state是状态数据,可以通过this.$store.state来直接获取状态,也可以利用vuex提供的mapState辅助函数将state映射到计算属性(computed)中去。用data接收的值不能及时响应更新,用computed就可以:
getters:类似于计算属性,当值发生改变,会自动触发这个属性中的方法,getters本质上是用来对状态进行加工处理。Getters与State的关系,就像Vue.js的computed与data的关系。getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。可以通过this.$store.getters.valueName对派生出来的状态进行访问。或者直接使用辅助函数mapGetters将其映射到本地计算属性中去。
mapGetters 辅助函数:
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
mutations:同步修改属性,同步属性的方法中异步修改当前数据,当前数据是不会被修改的,只不过是把当前组件中的数据修改而已,vuex中的数据保持不变
actions:异步修改属性
modules:模块管理
# 使用vuex中的数据
如何修改vuex中的数据呢?
思路:点击按钮,触发的是自己组件中的方法,在组件中的方法去执行vuex中的修改方法,在vuex中的修改方法中修改vuex中的数据
//使用vuex中state属性中的数据【可以直接通过胡子语法使用,但是建议书写在computed中】
{{$store.state.键}}
//使用vuex中getters属性中的方法,【可以直接通过胡子语法使用,但是建议书写在computed中,类似计算属性调用时不用加()】
{{$store.getters.方法名}}
//使用vuex中mutations属性中的方法【书写在methods中】
this.$store.commit('参数1','参数2')
参数1:你要调用的方法的方法名
参数2:是载荷值
//使用vuex中actions属性中的方法【书写在methods中】
this.$store.dispatch("参数1","参数2")
// 参数1:异步的方法名 参数2:载荷
# 辅助函数
辅助函数先引入再使用
<script>
import {mapState,mapGetters,mapMutations,mapActions} from "vuex"
export default {
computed:{
//没有拆分模块的写法
...mapState(['键1','键2']),
...mapGetters(['方法名']),
//拆分成模块后的写法
...mapState({
'名':state => state.模块名(目录名).键
'名':function(state){return state.模块名.键}
}),
...mapGetters({
'名':"模块名/方法名"
})
},
methods:{
//没有拆分模块的写法
...mapMutations(['方法名']),
...mapActions(['方法名']),
//拆分成模块后的写法
...mapMutations({
'名':"模块名/方法名"
}),
...mapActions({
'名':"模块名/方法名"
}),
fun(){
this.方法名(载荷值)
}
}
}
</script>
# 命名空间
命名空间,如果命名空间的值为true,那么我们才能把这个目录作为模块去使用
export default {
namespaced:true
}
# router
路由:可以通过路由加载不同的页面
每一个路由中,都有三个部分:
- path 路由规则
- name 路由名字
- component 路由对应的组件
- 普通加载
- 懒加载
# 路由链接
<router-link to="跳转到的路由"></router-link>
# 路由占位
<router-view />
,当前的路由是什么,该位置就加载这个路由对应的组件
# 路由文件
src/router/index.js
这个js文件就是一个路由对象
# 声明路由的对象
const routes = [
{
path:"/路由规则",
name:"路由名字",
component:路由对应的组件
}
]
扩展:
redirect:'/重定向的路由规则'
在加载组件的位置有两种情况
1.在上边使用import 导入组件,在component中使用这个组件
例如:
import 组件名 from "路径+组件文件名"
component: 组件名
2.使用懒加载的方式加载组件 推荐使用
例如:
component: () => import(/*魔法注释*/ '加载的组件名')
# 路由的模式
history
hash 路径带#
# 路由跳转
# 1. 路由链接跳转
<router-link to="/路由规则?参数=值&参数=值"></router-link>
# 2. 通过点击按钮触发方法,在方法中实现跳转
- push()
- replace()
fn(){
this.$router.push('/路由规则?参数=值&参数=值')
this.$router.replace('/路由规则?参数=值&参数=值')
this.$router.push({
path:"路由规则", //或者通过name属性进行跳转路由
query:{
参数:值
......
}
})
}
# 3. 扩展
http://localhost:8080/路由规则/参数的值
如果需要使用这种传参方式,必须修改路由中的内容
{
path: '/路由规则/:id',
name: '路由名字',
component: 路由对应的组件
}
export default = [
methods:{
fun(){
this.$router.push("/路由规则/"+this.键); //键代表参数的值
}
}
]
# 组件中接收传递的参数
created(){
//得到的就是对象 {参数:值,参数:值}
this.$route.query
}
# 加载组件
在一个组件中引入另一个组件的行为叫做加载子组件
加载子组件的三部分:
- 引入
- 注册
- 使用
# 路由嵌套
{
path: '/路由规则/:id',
name: '路由名字',
component: 路由对应的组件,
children:[
{
path:'',
name:'',
component:''
}
]
}
# Axios
axios官网:http://www.axios-js.com/zh-cn/docs/index.html
1.安装
npm i axios
2.引入 在app.vue 的script标签内
import axios from "axios"
3.语法格式
created(){
axios.get("请求路径",{
params:{
键:值
}
}).then(res=>{
}).catch(err=>{
})
}
# 跨域
协议 域名 端口 有一个不一致就会出现跨域
如何解决跨域:
axios解决跨域采用的方式是:写代理
如何设置代理:找到vue.config.js文件
module.export={
devServer:{
//设置端口号 默认端口号是 8080
port:9090,
//设置代理
proxy:{
'代理名':{
target:'要代理的地址'
}
//如果需要自定义代理名,我们需要重写路径
'/代理名':{
target:'代理地址',
pathRewrite:{
"^/api":""
}
}
}
}
}
//修改配置文件必须重启服务
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios其实就是对ajax的封装
# axios的get和post请求
- get请求
axios({
method:"GET",
url:"https://www.xiaosutongxue.com",
}).then().catch()
axios.get('https://www.xiaosutongxue.com').then().catch()
- post请求
axios({
method:"POST",
url:"https://www.xiaosutongxue.com",
data:{
username:"xiaosu",
password:"123"
}
}).then(res=>{
console.log(res)
})
axios.post('https://www.xiaosutongxue.com',{
username:"xiaosu",
password:"123"
}).then(res=>{
console.log(res)
})
# 封装axios
为了便于维护,开发中都会对axios进行封装,建立一个统一的文件去管理它们,另外可以对一些请求进行拦截,发起请求时对一些配置项进行拦截,响应数据时对它进行一个拦截
- request.js
- 这份文件就是用来做拦截的
- api.js
- a
request.js
import axios from 'axios'
//创建一个axios实例
const instance = axios.create({
baseURL:"请求路径",
timeout:超时时间
})
//拦截器 - 请求拦截
instance.interceptors.request.use(config=>{
//在发送请求之前做些什么
//部分接口需要拿到token
let token = localStorage.getItem('token')
if(token){
config.headers.token = token;
}
return config
},err=>{
return Promise.reject(err)
})
//拦截器 - 响应拦截
instance.interceptors.response.use(response=>{
//对响应数据做点什么
return response
},err=>{
return Promise.reject(err)
})
//整体导出
export default instance;
api.js
//将request.js整体导入
import request from './request'
//按需导出每个请求,也就是按需导出每个api
//get请求
export const GetHomeAPI = () => request.get('api')
//post请求
export const PostLoginAPI = (params) => request.post('api',params)
使用
//按需导入
import {GetHomeAPI,PostLoginAPI} from '@/request/api.js'
GetHomeAPI().then(res=>{
console.log(res)
})
PostLoginAPI({
username:"xiaosu",
password:"123"
}).then(res=>{
console.log(res)
})
# 模块化
AMD、CMD,鼻祖级别的,现在半淘汰状态了
模块化目前比较流行的就是 CommonJs规范
和 ES6模块化
# Commonjs
导出是导出,导入是导入两者没有必然关系
只要导出正确,导入使哪种方式都行,但尽量配套使用
- 导出
var num = 10;
var obj = {username:"su"}
function func(){
return 123
}
//整体导出,支持对象简写
module.exports = {
num,
obj,
func
}
//按需导出 不建议这么写,意义不大
module.exports.num = num;
module.exports.obj = obj;
module.exports.func = func;
- 导入
//commonjs
var test = require('@/路径')
//es6
import test from '@/路径'
module.exports导出,本质上是在导出什么?
在导出exports
只写module 或者 module.exports;导出都是一个空对象,因为引入一个js文件,本质上就是在引入一个空对象
{
module:{
exports:{
num,
obj,
func
}
}
}
# ES6
- 导出
var num = 10;
var obj = {username:"su"}
function func(){
return 123
}
//整体导出
export default {
num,
obj,
func
}
//按需导出,
export var num = 10;
- 导入
//es6 默认引入export下的default
import test from '@/路径'
//commonjs 默认引入的是export对象
var test = require('@/路径')
//按需导入
import {num} from '@/路径'
export
:通过export方式导出,在导入的时候需要加{},且不能换名字(导出啥名,导入就啥名,名字加到{})import {aaa,bbb,ccc导出的时候的名字} from "路径+文件名"
export default
:通过export default方式导出,导入时不需要加{}import 名字 from "路径+文件名"
- 使用场景
- 导出单个值就用
export default
- 导出多个值就用
export
- 导出单个值就用
# 使用技巧
# 选项卡
<div id="app">
<button @click="fun(1)">国内新闻</button>
<button @click="fun(2)">国际新闻</button>
<button @click="fun(3)">娱乐新闻</button>
<p v-show="num==1">我是国内新闻</p>
<p v-show="num==2">我是国际新闻</p>
<p v-show="num==3">我是娱乐新闻</p>
</div>
<script>
new Vue({
el:'#app',
data:{
num:1
},
methods:{
fun(v){
this.num=v;
}
}
})
</script>
# get与post的传参差异
//get传参时 多一层对象包裹
axios.get('',{
params:{
username:"xiaosu"
}
}).then().catch()
//post传参时,直接是一个对象
axios.post('',{
username:"xiaosu"
}).then().catch()