1、数据代理概括
数据代理过程相当于是进行了 vm 代理 vm_data中的属性,vm._data 是与 我们vue文件中写的 data是全等的
//创建Vue实例let data = { //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。name:'atguigu',address:'北京'}new Vue({el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。data})Object.defineProperty(vm, 'name', {get(){return vm._data.name},set(value){vm._data.name = value}})
2、v-on:事件
只有data里面的东西才会做数据劫持,代理,方法其实也可以写在data,但是会使vue实例变得冗余,因为对方法做了数据代理及劫持,这是没意义的操作
事件的基本使用:
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参;
2.1、事件修饰符
Vue中的事件修饰符:
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式,让事件在捕获阶段执行(默认事件是在冒泡阶段执行的);
5.self:只有event.target是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
2.2、键盘事件
1.Vue中常用的按键别名:
回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合keydown去使用)
上 => up
下 => down
左 => left
右 => right
2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2).配合keydown使用:正常触发事件。
4.也可以使用keyCode去指定具体的按键(不推荐)
5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
3、列表渲染 key
最终效果图
操作步骤:
复制张三-18到其对应的右侧输入框
李四-19、王五-20同上操作
然后点击添加老刘就会得到上述的效果,具体原因在 下面 3.1 有解释
demo代码
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>key的原理</title><script type="text/javascript" src="../js/vue.js"></script></head><body><!-- 准备好一个容器--><div id="root"><!-- 遍历数组 --><h2>人员列表(遍历数组)</h2><button @click.once="add">添加一个老刘</button><ul><!-- 分别通过 index 与 id作为 key --><li v-for="(p,index) of persons" :key="index">{{p.name}}-{{p.age}}<input type="text"></li></ul></div><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{persons:[{id:'001',name:'张三',age:18},{id:'002',name:'李四',age:19},{id:'003',name:'王五',age:20}]},methods: {add(){const p = {id:'004',name:'老刘',age:40}this.persons.unshift(p)}},})</script>
</html>
3.1、index作为key
解释:
- 当index作为key的时候(没设置key时,默认也是以index作为key的)
- 我们一开始渲染的是左侧的三条数据,会生成三个虚拟dom节点,key 分别为0、1、2,
- 然后vue根据这三虚拟dom生成真正的三个dom节点,渲染到页面上去
- 当我们通过 unshift 逆序添加一个元素到列表时,破坏了原列表的顺序,生成四个虚拟节点
- vue会通过diff算法 将左侧(原本的)和右侧(新生成)的虚拟节点进行对比,主要是通过key,去对比
- 发现右侧key = 0的地方 文本节点与左侧key = 0的地方文本节点不一样,则在生成真实dom节点的时候,就会重新生成dom节点,对比input虚拟节点的时候,发现是一样的,则会复用已有的dom节点而此时,由于复用的是左侧key = 0 节点下的input,所以老刘对应的输入框本应是无值的变成了 张三-18,以此类推最终就会得到 右图所示的效果,出现数据错乱
- 而且如果是类似上述情况,涉及逆序破坏列表顺序的情况,使用idnex作为key,会造成效率问题,让本该复用的节点,得不到复用,重复创建dom节点,所以当是类似上述情况,使用唯一标识很重要,下面解释为啥使用唯一标识重要
3.2、唯一标识作为key
解释:
- 当唯一标识(这里是id)作为key的时候
- 我们一开始渲染的是左侧的三条数据,会生成三个虚拟dom节点,key 分别为001、002、003,
- 然后vue根据这三虚拟dom生成真正的三个dom节点,渲染到页面上去
- 当我们通过 unshift 逆序添加一个元素到列表时,破坏了原列表的顺序,生成四个虚拟节点
- vue会通过diff算法 将左侧(原本的)和右侧(新生成)的虚拟节点进行对比,主要是通过key,去对比
- 发现右侧key = 001的地方 文本节点与左侧key = 001的地方文本节点与input节点是一样的,则会复用已有的dom节点,此时就能正确显示,以此类推 002、003对应的节点杜能正确配对显示,所以就能得到我们想要的效果
4、vue监测数据改变的原理
引出问题
demo代码,注意要引入 vue.js (vue 2.xx.xx版本)
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>更新时的一个问题</title><script type="text/javascript" src="../js/vue.js"></script></head><body><!-- 准备好一个容器--><div id="root"><h2>人员列表</h2><button @click="updateMei">更新马冬梅的信息</button><ul><li v-for="(p,index) of persons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',data:{persons:[{id:'001',name:'马冬梅',age:30,sex:'女'},{id:'002',name:'周冬雨',age:31,sex:'女'},{id:'003',name:'周杰伦',age:18,sex:'男'},{id:'004',name:'温兆伦',age:19,sex:'男'}]},methods: {updateMei(){// this.persons[0].name = '马老师' //奏效// this.persons[0].age = 50 //奏效// this.persons[0].sex = '男' //奏效this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效// this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'}) // 奏效}}}) </script>
</html>
一个属性一个属性的更改是可以检测到的,但是当是数组的整个对象更新的时候,就不行
临时添加的对象属性,也不具有响应式(因为没有通过Object.defineProperty 进行劫持),除非使用$set添加的 ($set就相当于是重新使用 Object.defineProperty 进行数据劫持)
数组通过索引更改值,vue也检测不到,页面也不会实时更新(当然也可以使用set,就可以检测到了),必须通过数组的七个方法更新数组(push、pop、unshift、shift、splice、sort、reverse),vue才能检测到,因为vue对上述方法进行了重写
$set局限性不能vue实例/根数据对象身上使用 set, 会报错:避免添加响应式属性到xxx
5、其他
5.1、v-model 相关的三个修饰符
v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
5.2、过滤器
过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据
5.3、内置指令
v-text指令:
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html(慎用)
因为有了可能会在指令后面的内容中,插入一些获取 cookie的操作(document.cookie),这样就会导致xss攻击,也就是类似仿冒的操作,如果服务器返回的cookie 没有设置成 httpOnly ,则可以通过document.cookie 获取到,攻击者就可以获取到cookie,v-html 就有可能会带有,类似获取cookie,然后发送到攻击者的服务器的操作,所以如果要使用v-html 一定要确保是可信的,并进行相应的校验
v-once 只加载一次,后续相关值改变, 此处值不作变更
v-once指令:
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre
v-pre指令:
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
5.4、自定义指令
自定义指令总结:
一、定义语法:
(1).局部指令:
new Vue({
directives{指令名:回调函数}
})
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。
三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
指令可以分为函数式写法与对象式写法
new Vue({el:'#root',data:{name:'尚硅谷',n:1},directives:{big(element,binding){console.log('big',this) //注意此处的this是window// console.log('big')element.innerText = binding.value * 10},fbind:{//指令与元素成功绑定时(一上来)bind(element,binding){element.value = binding.value},//指令所在元素被插入页面时inserted(element,binding){element.focus()},//指令所在的模板被重新解析时update(element,binding){element.value = binding.value}}}})
函数式写法就相当于只写了bind 与 update, 向上面这样bind、inserted、update都写,一些小细节的东西才能处理
5.5、Vm 与 Vc
关于this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
Vm是指Vue实例, Vc是指VueComponent实例,一般Vm中的$children 会有相应的Vc实例,如果是嵌套组件,则其下也会有$children 也会有Vc实例, Vm 与 Vc基本长一个样 ,且Vm创建的时候又el配置项用于指定服务于那个容器,而Vc则没有
1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
5.6、插件
写法 plugins.js
export default {install(Vue,x,y,z){console.log(x,y,z)//全局过滤器Vue.filter('mySlice',function(value){return value.slice(0,4)})//定义全局指令Vue.directive('fbind',{//指令与元素成功绑定时(一上来)bind(element,binding){element.value = binding.value},//指令所在元素被插入页面时inserted(element,binding){element.focus()},//指令所在的模板被重新解析时update(element,binding){element.value = binding.value}})//定义混入Vue.mixin({data() {return {x:100,y:200}},})//给Vue原型上添加一个方法(vm和vc就都能用了),但是一般不这么做,需要慎重考虑Vue.prototype.hello = ()=>{alert('你好啊')}}
}
在项目入口文件中引入安装一下
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false//应用(使用)插件
Vue.use(plugins,1,2,3)
//创建vm
new Vue({el:'#app',render: h => h(App)
})
经过上面操作,这些定义好的过滤器、指令、mixin就可以全局使用了
5.7、npm 查看依赖版本
5.8、解决跨域方法
- 服务端设置cors
- jsonp,需要前后端配置,且只能处理get请求
- 脚手架/打包工具 代理
- nginx代理
6、生命周期
6.1、原理图
常用的生命周期钩子:
1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
7、事件总线
1、Vue原型对象上包含事件处理的方法
1)$on(eventName,listener):绑定自定义事件监听
2)$emit(eventName,data):分发自定义事件
3)$off(eventName):解绑自定义事件监听4)
$once(eventName,listener):绑定事件监听,但只能处理一次
2、所有组件实例对象的原型对象的原型对象就是Vue的原型对象
1)所有组件对象都能看到Vue原型对象上的属性和方法
2)Vue.prototype.$bus=newVue(),所有的组件对象都能看到$bus这个属性对象
事件总线其实大概就是这样一个东西
new Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线},
})
8、Vuex
8.1、基础使用
vuex官网 都是使用
但是需要注意是,vue开发者工具只能检测到mutation 的动作
mapState、mapGetters使用
computed:{//靠程序员自己亲自去写计算属性/* sum(){return this.$store.state.sum},school(){return this.$store.state.school},subject(){return this.$store.state.subject}, *///借助mapState生成计算属性,从state中读取数据。(对象写法)// ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),//借助mapState生成计算属性,从state中读取数据。(数组写法)...mapState(['sum','school','subject']),/* ******************************************************************** *//* bigSum(){return this.$store.getters.bigSum}, *///借助mapGetters生成计算属性,从getters中读取数据。(对象写法)// ...mapGetters({bigSum:'bigSum'})//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)...mapGetters(['bigSum'])},
mapMutations、mapActions
methods: {//程序员亲自写方法/* increment(){this.$store.commit('JIA',this.n)},decrement(){this.$store.commit('JIAN',this.n)}, *///借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)...mapMutations({increment:'JIA',decrement:'JIAN'}),//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)// ...mapMutations(['JIA','JIAN']),/* ************************************************* *///程序员亲自写方法/* incrementOdd(){this.$store.dispatch('jiaOdd',this.n)},incrementWait(){this.$store.dispatch('jiaWait',this.n)}, *///借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)// ...mapActions(['jiaOdd','jiaWait'])},
模块化开发
模块化的作用,顾名思义就是模块化,减少在同事间在协作的过程中可能会出现的冲突,代码结构更清晰,方便维护
9、vue-router
官网
9.1、params参数
携带 params 参数进行路由跳转,必须使用 name,不能使用path
若路由定义了 props: true 则会将params 作为组件的props,可以在组件里面通过prosp获取
若props 直接返回对象也可以,也可以写成函数形式
路由跳转方式:声明式导航 router-link、编程式导航,router.push/replace
9.2、路由守卫
全局路由守卫
//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{console.log('前置路由守卫',to,from)if(to.meta.isAuth){ //判断是否需要鉴权if(localStorage.getItem('school')==='atguigu'){next()}else{alert('学校名不对,无权限查看!')}}else{next()}
})//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{console.log('后置路由守卫',to,from)document.title = to.meta.title || '管理系统'
})
路由独享守卫
{name:'zhuye',path:'/home',component:Home,meta:{title:'主页'},children:[{name:'xinwen',path:'news',component:News,meta:{isAuth:true,title:'新闻'},beforeEnter: (to, from, next) => {console.log('独享路由守卫',to,from)if(to.meta.isAuth){ //判断是否需要鉴权if(localStorage.getItem('school')==='xxxx'){next()}else{alert('学校名不对,无权限查看!')}}else{next()}}}
}
组件内路由守卫
export default {name:'About',/* beforeDestroy() {console.log('About组件即将被销毁了')},*//* mounted() {console.log('About组件挂载完毕了',this)window.aboutRoute = this.$routewindow.aboutRouter = this.$router}, */mounted() {// console.log('%%%',this.$route)},//通过路由规则,进入该组件时被调用beforeRouteEnter (to, from, next) {console.log('About--beforeRouteEnter',to,from)if(to.meta.isAuth){ //判断是否需要鉴权if(localStorage.getItem('school')==='xxxxx'){next()}else{alert('学校名不对,无权限查看!')}}else{next()}},//通过路由规则,离开该组件时被调用beforeRouteLeave (to, from, next) {console.log('About--beforeRouteLeave',to,from)next()}}
路由模式
hash 与 history
history 会将路由地址,当做请求资源的地址,所以会出现404问题,需要后端去做适配,区别那些前段路由,那些是后端路由才行 ,而hash就不会出现这个问题