计算属性
计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。
此处fullname调用了四次,但是1只执行了一次。get的作用:当fullName被读取的时候时,get就会被调用,且返回值就作为fullName的值。
计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。注意,如果某个依赖 (比如非响应式 property) 在该实例范畴之外,则计算属性是不会被更新的。
get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<divid="root"style="margin: 121px 366px;"> 姓氏<inputtype="text"v-model="firstname"><br/> 名<inputtype="text"v-model="nextname"><br/> 姓名:<span>{{fullname}}</span><br/> 姓名:<span>{{fullname}}</span><br/> 姓名:<span>{{fullname}}</span><br/> 姓名:<span>{{fullname}}</span><br/> </div> <script> Vue.config.productionTip= false const vm = new Vue({ el:'#root', data:{ firstname:'张', nextname:'三' }, computed:{ fullname:{ get(){ console.log(1) returnthis.firstname + this.nextname } } } }) </script> |
- 定义:要用的属性不存在,要通过已有属性计算得来。
- 原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
- 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
- 备注:
计算属性最终会出现在vm上,直接读取使用即可。
如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。 - 简写
PLAINTEXT
1 2 3 4 5 |
computed:{ fullname(){ return this.firstname + this.nextname } } |
监视 属性
- 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 监视的属性必须存在,才能进行监视!!
- 监视的两种写法:
- new Vue时传入watch配置
- 通过vm.$watch监视,传两个参数,一个属性,一个方法
('isHot',{handler(){}})
写法一:
JS
1 2 3 4 5 6 7 8 |
watch:{ isHot:{ immediate:true, //初始化时让handler调用一下 handler(newvalue,oldvalue){ console.log("isHot被更改了",newvalue,oldvalue) } } } |
写法二:
JS
1 2 3 4 5 6 7 8 |
vm.$watch('isHot', { immediate:true, handler(newvalue){ console.log("isHot被更改啦",newvalue); } } ) |
深度监视
- Vue中的watch默认不监测对象内部值的改变(一层)。
- 配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。
(3).deep默认为false为了提高开发效率,
JS
1 2 3 4 5 6 7 8 9 |
vm.$watch('number', { immediate:true, handler(newvalue){ console.log("number被更改啦",newvalue); }, deep:true } ) |
简写
简写的话,不能够添加deep,immediate属性。
简写一:
JS
1 2 |
isHot(newValue,oldValue){ console.log('isHot被修改了',newValue,oldValue,this) |
简写二:
JS
1 2 3 |
vm.$watch('isHot',function(newValue,oldValue){ console.log('isHot被修改了',newValue,oldValue,this) }) |
计算属性和监测属性对比
如下代码证明watch能执行异步操作,但是computed不行,setTimeout必须是箭头函数,this指向所定义的作用域内的Vue实例对象,setTimeout下的回调函数不是Vue实例对象调用的,而是V8浏览器的js引擎调用的,所以不使用箭头函数的话,this指向window。
JS
1 2 3 4 5 6 7 8 9 10 11 |
watch:{ firstName(val){ setTimeout(()=>{ console.log(this) this.fullName = val + '-' + this.lastName },1000); }, lastName(val){ this.fullName = this.firstName + '-' + val } } |
computed和watch之间的区别:
- computed能完成的功能,watch都可以完成。
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数这些都是异异步的),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
绑定class样式
写法:class=”xxx” xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
HTML
1 2 3 4 5 6 7 8 |
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 ,比如mood确定但是mood的值是normal,happy,sad不确定--> <divclass="basic":class="mood" @click="changeMood">{{name}}</div><br/><br/> <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 --> <divclass="basic":class="classArr">{{name}}</div><br/><br/> <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 --> <divclass="basic":class="classObj">{{name}}</div><br/><br/> |
绑定style样式
:style=”{fontSize: xxx}”其中xxx是动态值。
:style=”[a,b]”其中a、b是样式对象。
PLAINTEXT
1 2 3 4 5 6 7 8 9 |
data:{ showin:{ backgroundColor:'orange', fontSize: '50px', margin:'auto', textAlign:'center' } }, |
条件渲染
HTML
1 2 3 4 5 6 7 8 9 10 |
<divid="root"> <h2>hello,{{n}}</h2> <button @click="n++">anniu</button> <divv-if="n===1">a</div> <divv-else-if="n===2">b</div> <divv-else-if="n===3"> c</div> <!-- <div v-show="n===1">a</div> <div v-show="n===2">b</div> <div v-show="n===3">c</div> --> </div> |
v-if
写法:
(1).v-if=”表达式”
(2).v-else-if=”表达式”
(3).v-else,else后面无需跟条件。
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
v-show
写法:v-show=”表达式”
适用于:切换频率较高的场景。
特点:不展示的DOM元素不会被移除,vue内部添加style="display :none",
仅仅是使用样式隐藏掉
3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
列表渲染
1.用于展示列表数据
2.语法:v-for=”(item, index) in xxx” :key=”yyy”
遍历数组:p是数组中的每个数据,index是当前索引(用的最多的一种)。
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<divid="root"> <ul> <liv-for="(p,index) in persons":key="p.id"> {{p.name}}-{{p.age}} </li> </ul> </div> <script> new Vue({ el:'#root', data:{ persons:[ {id:'01',name:'张三',age:'18'},{ id:'02',name:'李四',age:'20' },{ id:'03',name:'王五',age:'18' } ] } }) </script> |
遍历对象:value是值,k是属性
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<ul> <liv-for="(value,k) of car":key="k"> {{k}}-{{value}} </li> </ul> <scripttype="text/javascript"> Vue.config.productionTip = false new Vue({ el:'#root', data:{ car:{ name:'奥迪A8', price:'70万', color:'黑色' }, str:'hello' } }) </script> |
Key的作用和原理
- 虚拟DOM中key的作用:
变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: - 对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到到页面。
- 用index作为key可能会引发的问题:
破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低,逆序操作重新生成张三李四王五。
如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
列表过滤
watch监测属性实现
- 创建persons属性,创建一个filterper数组为了避免更改原来的数据。
- 添加watch属性,返回this.filterper的值,因为空字符串也是包含在字符串中,使用
immediate:true
让数据能够全部显示。 - 用
this.filterper= this.persons.filter((p)=>{return p.name.indexOf(val)!== -1})
,即是用arr.filter()的回调判断arr.indexOf()的返回值如果为-1就不存在。
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<divid="root"> <ul> <inputtype="text"v-model="keywords"> <liv-for="(p,index) in filterper":key="p.id"> {{p.name}}-{{p.age}} </li> </ul> </div> <script> new Vue({ el:'#root', data:{ persons:[ {id:'001',name:'马冬梅',age:19,sex:'女'}, {id:'002',name:'周冬雨',age:20,sex:'女'}, {id:'003',name:'周杰伦',age:21,sex:'男'}, {id:'004',name:'温兆伦',age:22,sex:'男'} ], keywords:'', filterper:[] }, watch:{ keywords:{ immediate:true, handler(val){ this.filterper= this.persons.filter((p)=>{ return p.name.indexOf(val)!== -1 }) } } } }) </script> |
computed计算属性实现
- 第一个return是计算属性的返回值,第二个return是filter回调函数的返回值。
JS
1 2 3 4 5 6 7 |
computed:{ filPerons(){ returnthis.persons.filter((p)=>{ return p.name.indexOf(this.keyWord) !== -1 }) } } |
列表排序
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
computed:{ filterpers(){ const arr= this.persons.filter((p)=>{ return p.name.indexOf(this.keywords) !== -1 }) if(this.sorttype){ arr.sort((a,b)=>{ returnthis.sorttype=== 1 ? a.age -b.age :b.age -a.age }) } return arr } } |
Vue监听数据原理
对象
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// 核心数据响应式方法 functiondefineReactive(obj,key,val){ // val可能还是个对象,需要递归一下 objserve(val) Object.defineProperty(obj,key,{ get(){ return val }, set(newVal){ if(newVal !== val){ val = newVal // 如果改变的是个对象,还需要调用一下 objserve(newVal) console.log('set', newVal); // 在这里已经监听到了数据的变化,后续可以做一些更新视图的操作 } } }) } // 如果一个对象有多个属性,循环调用defineReactive,传入每一个值去进行监听 functionobjserve(obj){ // 判断obj类型 if(Object.prototype.toString.call(obj)!=="[object Object]"){ return } Object.keys(obj).forEach(key =>defineReactive(obj,key,obj[key])) } // 如果给对象增加新的属性,是监听不到变化的,那么需要用set方法传入新对象,调用defineReactive手动监听一下 functionset(obj,key,val){ defineReactive(obj,key,val) } const obj = {foo:'foo',baz:{a:1}}; objserve(obj) obj.foo = '2222' obj.baz.a = '3333' obj.baz = {a:10} obj.baz.a = 100 set(obj,"dong",'dong') obj.dong = "dong1" |
数组
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
你可以打开控制台,然后对前面例子的 items
数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })
。
Vue.set()
Vue.set( target, propertyName/index, value )
- 参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
- 返回值:设置的值。
- 用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如this.myObject.newProperty = 'hi'
)
注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
只能在data的对象中添加Vue.set(),而不能在data上直接添加,也就是不能在vm和vm._data上添加。
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const vm = new Vue({ el:'#root', data:{ school:{ name:'尚硅谷', address:'北京', }, student:{ name:'tom', age:{ rAge:40, sAge:29, }, friends:[ {name:'jerry',age:35}, {name:'tony',age:36} ] } }, methods: { addSex(){ // Vue.set(this.student,'sex','男') this.$set(this.student,'sex','男') } } }) |
总结
Vue监视数据的原理:
\1. vue会监视data中所有层次的数据。
\2. 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
\3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
methods: { addSex(){ // Vue.set(this.student,'sex','男') this.$set(this.student,'sex','男') }, addFriend(){ this.student.friends.unshift({name:'jack',age:70}) |