# 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函数什么时候执行?

  1. 初次读取时会执行一次
  2. 当依赖的数据发生改变时会再次调用

优势:与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>

# 过滤器的使用场景

  1. 人民币:¥ 100 元

    <script>
    Vue.filter('RMBformat', val => {
      return "¥ " + Number(val).toFixed(2) + " 元"
    })
    </script>
    
  2. 时间戳: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-prev-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-htmlv-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-ifv-show 都可以设置元素的显示与隐藏,两者的区别在于:

  • v-if 控制元素的显示与销毁,如果值为 true,就显示该元素,如果值为 false就删除该元素

  • v-show 控制元素的显示与隐藏,如果值为 true 就显示该元素,如果值为 false 就隐藏该元素,这里的隐藏就是为该元素设置 style 属性,并设置样式为 display:none;

v-ifv-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 和 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 / setterObject.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中的数据代理

  1. 通过vm对象来代理data对象中的属性的操作(读/写)

  2. Vue中数据代理的好处

    更加方便的操作data中的数据

  3. 基本原理

    通过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
    }
}

# 监听和计算属性的区别

  1. 监听函数无需调用,而计算属性需要调用,并且调用时不加()
  2. 监听函数监听的是属性的变化,拿变化后的属性去做后续的操作
  3. 计算属性是通过它依赖的属性发生变化后得到的值,并且数据的结果会被缓存,且函数中必须通过return返回最终的结果
  4. 使用场景
    • 当一个结果受多个属性影响的时候就需要使用计算属性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
}

# 加载组件

在一个组件中引入另一个组件的行为叫做加载子组件

加载子组件的三部分:

  1. 引入
  2. 注册
  3. 使用

# 路由嵌套

{
	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()