1.0 表单相关
1.1 表单基本操作
-
获取文本框的值(略)
-
获取单选框中的值
-
通过v-model
-
<!-- 1、 两个单选框需要同时通过v-model 双向绑定 一个值 2、 每一个单选框必须要有value属性 且value 值不能一样 3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据gender 的值就是选中的值,我们只需要实时监控他的值就可以了--><input type="radio" id="male" value="1" v-model='gender'><label for="male">男</label><input type="radio" id="female" value="2" v-model='gender'><label for="female">女</label><script>new Vue({data: {// 默认会让当前的 value 值为 2 的单选框选中gender: 2, },})</script>
获取复选框中的值
-
通过v-model
-
和获取单选框中的值一样
-
复选框
checkbox
这种的组合时 data 中的 hobby 我们要定义成数组 否则无法实现多选 -
<!-- 1、 复选框需要同时通过v-model 双向绑定 一个值 2、 每一个复选框必须要有value属性 且value 值不能一样 3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据hobby 的值就是选中的值,我们只需要实时监控他的值就可以了--><div><span>爱好:</span><input type="checkbox" id="ball" value="1" v-model='hobby'><label for="ball">篮球</label><input type="checkbox" id="sing" value="2" v-model='hobby'><label for="sing">唱歌</label><input type="checkbox" id="code" value="3" v-model='hobby'><label for="code">写代码</label></div> <script>new Vue({data: {// 默认会让当前的 value 值为 2 和 3 的复选框选中hobby: ['2', '3'],},}) </script>
获取下拉框和文本框中的值
-
通过v-model
-
<div><span>职业:</span><!--1、 需要给select 通过v-model 双向绑定 一个值 2、 每一个option 必须要有value属性 且value 值不能一样 3、 当某一个option选中的时候 v-model 会将当前的 value值 改变 data 中的 数据occupation 的值就是选中的值,我们只需要实时监控他的值就可以了--><!-- multiple 多选 --><select v-model='occupation' multiple><option value="0">请选择职业...</option><option value="1">教师</option><option value="2">软件工程师</option><option value="3">律师</option></select><!-- textarea 是 一个双标签 不需要绑定value 属性的 --><textarea v-model='desc'></textarea></div> <script>new Vue({data: {// 默认会让当前的 value 值为 2 和 3 的下拉框选中occupation: ['2', '3'],desc: 'nihao'},}) </script>
-
1.2 表单修饰符
-
.number 转换为数值
-
注意点:
-
当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值
-
所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串。
-
-
.trim 自动过滤用户输入的首尾空白字符
-
只能去掉首尾的 不能去除中间的空格
-
-
.lazy 将input事件切换成change事件
-
.lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
-
-
在失去焦点 或者 按下回车键时才更新
-
<!-- 自动将用户的输入值转为数值类型 --> <input v-model.number="age" type="number"><!--自动过滤用户输入的首尾空白字符 --> <input v-model.trim="msg"><!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg" >
2.0 常用特性
2.1 自定义指令
-
内置指令不能满足我们特殊的需求
-
Vue允许我们自定义指令
Vue.directive 注册全局指令
自定义指令创建语法
Vue.directive('focus',{inserted:function(el){//获取元素焦点el.focus();} })
自定义指令用法:
<input type="text" v-focus >
<!-- 使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。
-->
<input type="text" v-focus><script>
// 注意点:
// 1、 在自定义指令中 如果以驼峰命名的方式定义 如 Vue.directive('focusA',function(){})
// 2、 在HTML中使用的时候 只能通过 v-focus-a 来使用 // 注册一个全局自定义指令 v-focusVue.directive('focus',{bind:function(el){// 每当指令绑定到元素上的时候,会立即执行这个bind函数,只执行一次//,注意:在每个函数中 第一个参数永远是el,表示被绑定了指定的那个元素,这个el参数,是一个原生对象,在元素 刚绑定了指令的时候,还没有插入到DOM中去// 这时候,调用focus方法是没有作用的,// 因为一个元素只有插入到Dom之后,才能获取焦点。// el.focus()},inserted:function(el){//inserted表示元素插入到DOM中的时候会执行inserted函数,触发一次el.focus()},updated:function(){//当VNode更新的时候,会执行updated函数,可能会触发多次}}) new Vue({el:'#app'
});
</script>
Vue.directive 注册全局指令 ---带参数
<!-- <input type="text" v-color="'blue'" v-focus> --><input type="text" v-color="msg" v-focus><script type="text/javascript">/*自定义指令-带参数bind - 只调用一次,在指令第一次绑定到元素上时候调用*/// Vue.directive('color', {// bind: function (el) {// //样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有一个内联的样式,// //将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素// el.style.color = "red"// //和样式相关的操作,一般都可以在bind中执行// }// })Vue.directive('color', {bind: function (el,binding) {// console.log(binding.name);// console.log(binding.value);// binding 为自定义的函数形参 通过自定义属性传递过来的值 存在 binding.value 里面// console.log(binding.expression);// el.style.color = binding.valueel.style.color = binding.value.color}})var vm = new Vue({el: '#app',data: {msg: {color: 'blue'}}});</script>
自定义指令局部指令
-
局部指令,需要定义在 directives 的选项 用法和全局用法一样
-
局部指令只能在当前组件里面使用
-
当全局指令和局部指令同名时以局部指令为准
-
<input type="text" v-color='msg'><input type="text" v-focus><script type="text/javascript">/*自定义指令-局部指令*/var vm = new Vue({el: '#app',data: {msg: {color: 'red'}},//局部指令,需要定义在 directives 的选项directives: {color: {bind: function(el, binding){el.style.backgroundColor = binding.value.color;}},focus: {inserted: function(el) {el.focus();}}}});</script>
2.2 使用method监听数据的变化
-
/*使用keyup事件监听数据的变化*/ <!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>Document</title> <style> *{margin:0;padding:0} a {text-decoration:none} ul,li {list-style:none} </style> <script src='./js/vue.js'></script> </head> <body> <div id='app'> <input type="text" v-model="firstname" @keyup="getName">+ <input type="text" v-model="lastname" @keyup="getName">= <input type="text" v-model="fullname"> </div> <script>var vm=new Vue({el:'#app',data:{firstname:'',lastname:'',fullname:''},methods:{getName:function(){ this.fullname = this.firstname+'--'+this.lastname}}}) </script> </body> </html>
-
模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁
2.3 计算属性 computed
-
计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题.
计算属性是根据data中已有的属性,计算得到一个新的属性.
-
computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化
-
/*计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存*/<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>Document</title> <style> *{margin:0;padding:0} a {text-decoration:none} ul,li {list-style:none} </style> <script src='./js/vue.js'></script> </head> <body> <div id='app'> <input type="text" v-model="firstname">+ <input type="text" v-model="lastname">= <input type="text" v-model="fullname"></div> <script>var vm=new Vue({el:'#app',data:{firstname:'',lastname:''},methods:{},computed:{'fullname':function(){ return this.firstname+'--'+this.lastname}//在computed中,可以定义一些属性,叫做计算属性,计算属性的本质就是一个方法,//只不过我们在使用这些计算属性的时候,是把他们的名称直接当做属性来使用,//并不会把计算属性当做方法去调用//注意1.计算属性在引用的时候,一定不要加()去调用,直接把他当做普通属性去使用就可以了//注意2.只要计算属性这个function内部,所用到的任何data数据中的数据发生了变化,就会立即重新计算这个计 算属性的值//注意3.计算属性的求值结果,会被缓存起来,方便下次调用,如果计算属性方法中,所使用的data没有发生任何变化,//则不会对计算属性求值//注意4.computed属性定义和 data以及 methods 平级 //注意5.一定要有return 否则无法拿到结果 }}) </script> </body> </html>
2.4 侦听器 watch
-
使用watch来响应数据的变化
-
watch 中的属性 一定是data 中 已经存在的数据
-
<!DOCTYPE html> <html lang='en'> <head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Document</title><style>* {margin: 0;padding: 0}a {text-decoration: none}ul,li {list-style: none}</style><script src='./js/vue.js'></script> </head><body><div id='app'><input type="text" v-model="firstname">+<input type="text" v-model="lastname">=<input type="text" v-model="fullname"></div><script>var vm = new Vue({el: '#app',data: {firstname: '',lastname: '',fullname: ''},methods: {},watch: {'firstname': function () {this.fullname = this.firstname + "------" + this.lastname},'lastname': function () {this.fullname = this.firstname + "------" + this.lastname}// 'firstname': function (newval, oldval) {// this.fullname = newval +"------"+ this.lastname// },// 'lastname': function (newval, oldval) {// this.fullname = this.firstname +"------"+newval// }}})</script> </body></html>
当然,侦听器有自己的应用场景,它的应用场景就是在执行异步请求或者进行开销比较大的操作的时候,会使用侦听器。
下面我们来看一个异步操作的情况。就是当用户在一个文本框中输入了用户名以后,要将输入的用户名发送到服务端,来检查该用户名是否已经被占用。
-
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Document</title> </head> <body><div id="app"><div><span>用户名:</span><span><input type="text" v-model.lazy='uname'></span><span>{{tip}}</span></div></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/* 侦听器1、采用侦听器监听用户名的变化2、调用后台接口进行验证3、根据验证的结果调整提示信息*/var vm = new Vue({el: '#app',data: {uname: '',tip: ''},methods: {checkName: function(uname) {// 调用接口,但是可以使用定时任务的方式模拟接口调用var that = this;setTimeout(function(){// 模拟接口调用if(uname == 'admin') {that.tip = '用户名已经存在,请更换一个';}else{that.tip = '用户名可以使用';}}, 2000);}},watch: {uname: function(val){// 调用后台接口验证用户名的合法性this.checkName(val);// 修改提示信息this.tip = '正在验证...';}}});</script> </body> </html>
2.5 watch,computed,methods之间的对比
1.methods方法表示一个具体的操作,主要书写业务逻辑。
2.computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算,主要当做属性来使用。
3.watch是一个对象,键是需要观察的表达式,值是对应的回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,可以看做是computed和methods的结合体。
2.6 过滤器
-
Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
-
过滤器可以用在两个地方:双花括号插值和v-bind表达式。
-
过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
-
支持级联操作
-
全局注册时是filter,没有s的。而局部过滤器是filters,是有s的
-
<!DOCTYPE html> <html lang='zh-CN'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>Document</title> <style> </style> <script src='./js/vue.js'></script> </head> <body> <div id='app'><h3>{{msg | format1("帅气") | format2 }}</h3><div :index="abc | upper | lower"></div> </div> <script>Vue.filter('format1',function (val,arg) {return val.replace(/单纯/gi,"英俊"+arg)})Vue.filter('format2',function (val) {return val + "===无敌了!"})const vm = new Vue({el:'#app',data:{msg:"曾经,我也是一个单纯的少年,单纯的我,单纯的问,谁是世界上最单纯的人",abc:'abc'},methods:{},filters: {upper:function (val) {return val.toUpperCase() },lower:function (val) {return val.toLowerCase() }}}) </script> </body> </html>
2.7 过滤时间格式
-
<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>Document</title> <style> /**{margin:0;padding:0} a {text-decoration:none} ul,li {list-style:none}*/ </style> <script src='./js/vue.js'></script> </head> <body> <div id='app'> <p>{{date | format('yyyy-MM-dd hh:mm:ss')}}</p> </div> <script>Vue.filter('format',function (value,arg) {function dateFormat(date, format) {if (typeof date === "string") {var mts = date.match(/(\/Date\((\d+)\)\/)/);if (mts && mts.length >= 3) {date = parseInt(mts[2]);}}date = new Date(date);if (!date || date.toUTCString() == "Invalid Date") {return "";}var map = {"M": date.getMonth() + 1, //月份 "d": date.getDate(), //日 "h": date.getHours(), //小时 "m": date.getMinutes(), //分 "s": date.getSeconds(), //秒 "q": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 };format = format.replace(/([yMdhmsqS])+/g, function(all, t) {var v = map[t];if (v !== undefined) {if (all.length > 1) {v = '0' + v;v = v.substr(v.length - 2);}return v;} else if (t === 'y') {return (date.getFullYear() + '').substr(4 - all.length);}return all;});return format; }return dateFormat(value, arg)})var vm=new Vue({el:'#app',data:{date:new Date("1987/10/20 23:30:5")},methods:{}}) </script> </body> </html>
3.0 生命周期
3.1 概述
-
事物从出生到死亡的过程
-
每个
Vue
实例在被创建时都要经过一系列的初始化过程,例如:需要设置数据的监听,编译模板,将实例挂载到DOM
上,并且在数据变化时更新DOM
等,这些过程统称为Vue
实例的生命周期
。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。 -
常用的钩子函数
-
beforeCreate 在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了 created 在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来 beforeMount 在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已 mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件 beforeUpdate 数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的 updated 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的 beforeDestroy 实例销毁之前调用 destroyed 实例销毁后调用
-
其实
Vue
实例的生命周期,主要分为三个阶段,分别为
-
挂载(初始化相关属性,例如
watch
属性,method
属性)-
beforeCreate
啥也干不了 -
created
创建 初始化数据 method -
beforeMount
虚拟dom -
mounted
挂载 经历到这个阶段,数据才可以显示在页面上
-
-
更新(元素或组件的变更操作)
-
beforeUpdate
-
updated
更新 页面变化立刻,就会被同步到
-
-
销毁(销毁相关属性)
-
beforeDestroy
销毁前 -
destroyed
销毁。 挂了。彻底废了
-
-
前四个阶段 beforeCreate , created, beforeMount , Mounted. 这四个阶段叫做组件(实例)的创建阶段
第五个和第六个 beforeUpdate Updated 组件的运行阶段
第七个和第八个 beforeDestroy destroyed 组件的销毁阶段
3.2 案例演示
-
一个案例让你了解什么是生命周期。
-
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Document</title> </head> <body><div id="app"><div>{{msg}}</div><button @click.number='update(5)'>更新</button><button @click='destroy'>销毁</button></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*Vue实例的生命周期*/var vm = new Vue({el: '#app',data: {msg: 0},template:"<h1>我是模板</h1>",methods: {update: function(num){this.msg += num;},destroy: function(){this.$destroy();}},beforeCreate: function(){console.log('beforeCreate');},created: function(){console.log('created');// console.log(this.$el);},beforeMount: function(){console.log('beforeMount');},mounted: function(){console.log('mounted');},beforeUpdate: function(){console.log('beforeUpdate');},updated: function(){console.log('updated');},beforeDestroy: function(){console.log('beforeDestroy');},destroyed: function(){console.log('destroyed');}});// vm.$mount("#app");//添加了$mount方法</script> </body> </html>
3.3 代码分析
beforeCreate
:Vue
实例初始化之后,以及事件初始化,以及组件的父子关系确定后执行该钩子函数,一般在开发中很少使用。created
: 在调用该方法之前,初始化会被使用到的状态,状态包括props
,methods
,data
,computed
,watch
.而且会实现对
data
中属性的监听,也就是在created
的时候数据已经和data
属性进行了绑定。(放在data
中的属性当值发生改变的时候,视图也会改变)。同时也会对传递到组件中的数据进行校验。所以在执行
created
的时候,所有的状态都初始化完成,我们也完全可以在该阶段发送异步的ajax
请求,获取数据。但是,在
created
方法中,是无法获取到对应的的$el
选项,也就是无法获取Dom
. 所以说上题中选项c
的说法是正确的created
方法执行完毕后,下面会判断对象中有没有el
选项。如果有,继续执行下面的流程,也就是判断是否有template
选项,如果没有el
选项,则停止整个生命周期的流程,直到执行了vm.$mount(el)
后,才会继续向下执行生命周期的流程。我们将上方代码
el
选项去掉了,运行上面的代码后,我们发现执行完created
方法后,整个流程就停止了。现在,我们不添加
el
选项,但是手动执行vm.$mount(el)
,也能够使暂停的生命周期进行下去。我们继续向下看,就是判断在对象中是否有
template
选项。第一:如果
Vue
实例对象中有template
参数选项,则将其作为模板编译成render
函数,来完成渲染。第二:如果没有
template
参数选项,则将外部的HTML作
为模板编译(template
),也就是说,template
参数选项的优先级要比外部的HTML
高第三:如果第一条,第二条件都不具备,则报错。
接下来会触发
beforeMount
这个钩子函数:在执行该钩子函数的时候,虚拟
DOM
已经创建完成,马上就要渲染了,在这里可以更改data
中的数据,不会触发updated
, 其实在created
中也是可以更改数据,但是不会触发updated
函数。下面再执行
mounted
的时候,可以看到真实的数据。同时整个组件内容已经挂载到页面中了,数据以及真实DOM
都已经处理好了,可以在这里操作真实DOM
了,也就是在mounted
的时候,页面已经被渲染完毕了,在这个钩子函数中,我们可以去发送ajax
请求。当整个组件挂载完成后,有可能会进行数据的修改,当
Vue
发现data
中的数据发生了变化,会触发对应组件的重新渲染,先后调用了beforeUpdate
和updated
钩子函数。在
updated
之前beoreUpdate
之后有一个非常重要的操作就是虚拟DOM
会重新构建,也就是新构建的虚拟DOM
与上一次的虚拟DOM
树利用diff
算法进行对比之后重新渲染。而到了
updated
这个方法,就表示数据已经更新完成,dom
也重新render
完成。下面如果我们调用了
vm.$destroy
方法后,就会销毁所有的资源。首先会执行
beforeDestroy
这个钩子函数,这个钩子函数在实例销毁前调用,在这一步,实例仍然可用。在该方法中,可以做一些清理的工作,例如:清除定时器等。
但是执行到
destroyed
钩子函数的时候,Vue
实例已经被销毁,所有的事件监听器会被移除,所有的子实例也会被销毁。最后做一个简单的总结:
-
beforeCreate( )// 该钩子函数执行时,组件实例还未创建.
created()//组件初始化完毕,各种数据可以使用,可以使用ajax发送异步请求获取数据
beforeMounted()// 未执行渲染,更新,虚拟DOM完成,真实DOM未创建
mounted()// 初始化阶段结束,真实DOM已经创建,可以发送异步请求获取数据,也可以访问dom元素
beforeUpdate()//更新前,可用于获取更新前各种状态数据
updated()//更新后执行该钩子函数,所有的状态数据是最新的。
beforeDestroy() // 销毁前执行,可以用于一些定时器的清除。
destroyed()//组件已经销毁,事件监听器被移除,所有的子实例也会被销毁。
4.0 响应式与数组变异方法
4.1 变异方法
-
力有不逮的对象
众所周知,在 Vue
中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变,上代码:
<body><div id="app"><h1 v-for="(val,key) in obj">{{key}}----{{val}}</h1></div><script>var vm = new Vue({el: '#app',data() {return {obj: {a: 1}};}});vm.obj.a = 20;// `vm.obj.a` 现在是响应式的vm.obj.b = 2;// `vm.obj.b` 不是响应式的</script></body>
这是什么原因?
原因在于: Vue
的响应式系统是基于Object.defineProperty
这个方法的,该方法可以监听对象中某个元素的获取或修改,经过了该方法处理的数据,我们称其为响应式数据。但是,该方法有一个很大的缺点,新增属性或者删除属性不会触发监听
原因在于,在 Vue
初始化的时候, Vue
内部会对 data
方法的返回值进行深度响应式处理,使其变为响应式数据,所以, vm.obj.a
是响应式的。但是,之后设置的 vm.obj.b
并没有经过 Vue
初始化时响应式的洗礼,所以,理所应当的不是响应式。
-
更凄惨的数组
-
<body><div id="app"><button @click="change1">索引操作</button><button @click="change2">长度操作</button><p v-for="item in list">{{item }}</p></div><script>const vm = new Vue({el: '#app',data: {list: ['刘德华', '张学友', '黎明', '郭富城']},methods: {change1() {this.list[0] = '郑伊健';},change2() {this.list.length = 0;}}});</script></body>
也就是说,数组连自身元素的修改也无法监听,原因在于,
Vue
对data
方法返回的对象中的元素进行响应式处理时,如果元素是数组时,仅仅对数组本身进行响应式化,而不对数组内部元素进行响应式化。这也就导致如官方文档所写的后果,无法直接修改数组内部元素来触发响应式。那么,有没有破解方法呢?
-
当然有,官方规定了 7 个数组方法,通过这 7 个数组方法,可以很开心地触发数组的响应式,这 7 个数组方法分别是:
-
push()
往数组最后面添加一个元素,成功返回当前数组的长度 pop()
删除数组的最后一个元素,成功返回删除元素的值 shift()
删除数组的第一个元素,成功返回删除元素的值 unshift()
往数组最前面添加一个元素,成功返回当前数组的长度 splice()
有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值 sort()
sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组 reverse()
reverse() 将数组倒序,成功返回倒序后的数组
<!DOCTYPE html>
<html lang='zh-CN'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Document</title><style></style><script src='./js/vue.js'></script>
</head><body><div id='app'><input type="text" v-model="name"><button @click="add">增加</button><button @click="pop">去除</button><li v-for="(item,index) in list" :key="item.id">{{item}}</li></div><script>const vm = new Vue({el: '#app',data: {name: "",list: ["张学友", "郭富城", "黎明", "刘德华"]},methods: {add() {this.list.push(this.name)},pop() {this.list.pop()}}})vm.list[0] = "郑伊健"</script>
</body></html>
可以发现,这 7 个数组方法貌似就是原生的那些数组方法,为什么这 7 个数组方法可以触发应式,触发视图更新呢?
你是不是心里想着:数组方法了不起呀,数组方法就可以为所欲为啊?
Sorry,这 7 个数组方法是真的可以为所欲为的。
因为,它们是变异后的数组方法。什么是变异数组方法?变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展,在 Vue
中这个所谓的功能拓展就是添加响应式功能。
4.2 替换数组
-
不会改变原始数组,但总是返回一个新数组
filter | filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。 |
---|---|
concat | concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组 |
slice | slice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组 |
<!DOCTYPE html>
<html lang='zh-CN'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Document</title><style></style><script src='./js/vue.js'></script>
</head><body><div id='app'><button @click="change">触发</button><li v-for="(item,index) in list" :key="item.id">{{item}}</li></div><script>const vm = new Vue({el: '#app',data: {list: ["张学友", "郭富城", "黎明", "刘德华"]},methods: {change() {this.list.filter(item => {return item.includes("华")})console.log(this.list)}}})</script></body></html>
4.3 动态数组响应式数据(Object.defineProperty()
-
了解完变异方法后,我们回到最初的问题,vm.obj.b = 2; 或者使用索引或length修改数组就真的不能变成响应式的吗?
-
在解决这个问题之前先了解一下 Vue 的响应式原理:
关于响应式,提出3个问题?
第一个问题: 给属性重新赋值成对象,是否是响应式的?
第二个问题: 给`Vue`实例新增一个成员是否是响应式的?
第三个问题: 通过索引和length修改数组是否是响应式的?
-
看一下官方的解决方法
-
深入响应式原理 — Vue.js
当你把一个 JS 对象传给 Vue 实例的 data 属性时,Vue 将遍历此对象的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本的浏览器。
为什么要使用 Vue.set
受限于现代浏览器,Vue 检测不到对象的添加和删除;因为 Vue 在初始化实例时对 data
属性执行 getter/setter
转化操作,所以对象必须在 data
中才能让其响应式。
Vue 不允许在已经创建的实例上动态添加新的根级响应式属性,但是有时我们项目中一开始的对象的属性不确定,你可能需要为已有对象赋予多个新属性,不过可以使用 Vue.set
或者vm.$set 方法将响应式属性添加到嵌套的对象上。代码演示如下
- Vue.set(a,b,c) 让触发视图重新更新一遍,数据动态起来
- a是要更改的数据 、 b是数据的第几项、 c是更改后的数据
<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Document</title>
<style>
</style>
<script src='./js/vue.js'></script>
</head>
<body>
<div id='app'><button @click="change1">问题1</button><h1>{{ obj.message }}</h1><hr><button @click="change2">问题2</button><h1>{{ obj.newProperty }}</h1><hr><button @click="change3">问题3</button><ul><li v-for="(item,index) in list" :key="index">{{ item }}</li></ul>
</div>
<script>const vm = new Vue({el:'#app',data:{obj : {// 第一个问题: 给属性重新赋值成对象,是否是响应式的?message : "默认信息",// 第二个问题: 给`Vue`实例新增一个成员是否是响应式的?}, // 第三个问题: 通过索引和length修改数组是否是响应式的?list:["刘德华","张学友","郭富城","黎明"]},methods:{change1 () {this.obj.message = {name:"Petrel!"}},change2 () {// 直接设置是无法响应式的// this.obj.newProperty = "我是data里面新增加的属性"// Vue.set 可以保证动态添加的新属性是响应式的this.$set(this.obj,"newProperty", "我是data里面新增加的属性")},change3 () {// this.list[0] = "郑伊健" // 1、 使用索引不是响应式 解决方法如下// this.$set(this.list,0,"郑伊健")// this.list.length = 0// 2、vue中使用length改变数组的长度也是不响应的,解决方法是使用this.arr.splice(0)---利用的是数组变异方法。this.list.splice(0)}}})
</script>
</body>
</html>
总结:
vue中使用索引改变数组是不响应的,解决方式是使用Vue.set(a,b,c)
vue中使用length改变数组的长度也是不响应的,解决方法是使用this.arr.splice(0)---利用的是数组变异方法。
vue中如果给一个对象动态的添加了新属性,我们也需要使用Vue.set(a,b,c) 让 触发视图重新更新一遍