Vue的computed和watch的细节全面分析
作者:
秒速五厘米
是一个计算属性,类似于过滤器,对绑定到view的数据进行处理
data: { firstName: 'Foo', lastName: 'Bar' }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }
fullName不可在data里面定义,
如果定义会报如下图片的错误,因为对应的computed作为计算属性定义fullName并返回对应的结果给这个变量,变量不可被重复定义和赋值
data: { firstName: 'Foo', lastName: 'Bar' }, computed: { fullName:{ get(){//回调函数 当需要读取当前属性值是执行,根据相关数据计算并返回当前属性的值 return this.firstName + ' ' + this.lastName }, set(val){//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据 //val就是fullName的最新属性值 console.log(val) const names = val.split(' '); console.log(names) this.firstName = names[0]; this.lastName = names[1]; } } }
watch是一个观察的动作
data: { firstName: 'Foo', lastName: 'Bar', fullName: 'Foo Bar' }, watch: { firstName: function (val) { this.fullName = val + ' ' + this.lastName }, lastName: function (val) { this.fullName = this.firstName + ' ' + val } }
上面是监听firstName和lastName的变化,但是仅限简单数据类型
data(){ return{ 'first':2 } }, watch:{ first(){ console.log(this.first) } },
1.监听复杂数据类型需用深度监听
data(){ return{ 'first':{ second:0 } } }, watch:{ secondChange:{ handler(oldVal,newVal){ console.log(oldVal) console.log(newVal) }, deep:true } },
2.console.log打印的结果,发现oldVal和newVal值是一样的,所以深度监听虽然可以监听到对象的变化,但是无法监听到具体对象里面那个属性的变化
3.oldVal和newVal值一样的原因是它们索引同一个对象/数组。Vue 不会保留修改之前值的副本
vm.$watch的深度监听
4.深度监听对应的函数名必须为handler,否则无效果,因为watcher里面对应的是对handler的调用
方法一:可以直接对用对象.属性的方法拿到属性
data(){ return{ 'first':{ second:0 } } }, watch:{ first.second:function(newVal,oldVal){ console.log(newVal,oldVal); } },
方法二:watch如果想要监听对象的单个属性的变化,必须用computed作为中间件转化,因为computed可以取到对应的属性值
data(){ return{ 'first':{ second:0 } } }, computed:{ secondChange(){ return this.first.second } }, watch:{ secondChange(){ console.log('second属性值变化了') } },
1.是计算值,
2.应用:就是简化tempalte里面{{}}计算和处理props或$emit的传值
3.具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数
1.是观察的动作,
2.应用:监听props,$emit或本组件的值执行异步操作
3.无缓存性,页面重新渲染时值不变化也会执行
传入的值想作为局部变量来使用,直接使用会
props:['listShop'], data(){ return{} }, created(){ this.listShop=30 }
报错
这个错误是说的避免直接修改父组件传入的值,因为会改变父组件的值,贴上官网介绍
简单数据类型解决方案:
所以可以在data中重新定义一个变量,改变指向,但是也只是针对简单数据类型,因为复杂数据类型栈存贮的是指针,
props:['listShop'], data(){ return{ listShopChild:this.listShop } }, created(){ this.listShopChild=30 }
这样就可以愉快的更改传入的简单数据类型的数据啦!不会有任何报错,也不会影响父组件!
复杂数据类型在栈中存贮的是指针,所以赋值给新的变量也会改变原始的变量值.那么应该咋整呢?
1.可以手动深度克隆一个复杂的数据出来,循环或者递归都行
数组深度克隆:
var x = [1,2,3]; var y = []; for (var i = 0; i < x.length; i++) { y[i]=x[i]; } console.log(y); //[1,2,3] y.push(4); console.log(y); //[1,2,3,4] console.log(x); //[1,2,3]
对象深度克隆:
var x = {a:1,b:2}; var y = {}; for(var i in x){ y[i] = x[i]; } console.log(y); //Object {a: 1, b: 2} y.c = 3; console.log(y); //Object {a: 1, b: 2, c: 3} console.log(x); //Object {a: 1, b: 2}
函数深度克隆
var x = function(){console.log(1);}; var y = x; y = function(){console.log(2);}; x(); //1 y(); //2
为什么函数可以直接赋值克隆?
由于函数对象克隆之后的对象会单独复制一次并存储实际数据,因此并不会影响克隆之前的对象。所以采用简单的复制“=”即可完成克隆。
2.Object.assign
只会对只是一级属性复制,比浅拷贝多深拷贝了一层而已,所以还是无法达到深度克隆的目的.
详请请戳
3.强大的JSON.stringify和JSON.parse
const obj1 = JSON.parse(JSON.stringify(obj));
这是ES5新出来的API,先将对象转化为字符串,就是简单数据类型赋值,再用JSON.parse转化
直接用computed改变
computed:{ listShopChild(){ return this.listShop } }
注意:此时用computed时,如果是数组this.$set(arr,1,true)对应的值耶不更新,
这个很坑,这个bug我找个很久
如果传入的值只是在data定义,并未在methods或生命周期钩子更改,直接改变也会报错
所以还是可以先用局部变量接收,再修改,这个坑比较多
监听本组件计算和监听
计算或监听父传子的props值
分为简单数据类型和复杂数据类型监听,监听方法如上watch的使用
监听vuex的state或者getters值的变化
computed:{ stateDemo(){ return this.$store.state.demoState; } } watch:{ stateDemo(){ console.log('vuex变化啦') } }
很开心小伙伴们能看到这里,接下来给大家简单罗列下他们的原理!
分为三个过程:实例化Vue、调用$watch方法、属性变化,触发回调
Vue的数据依赖实现原理简析
vue中$watch源码阅读笔记
公共类
function defineReactive(data, key, val, fn) { let subs = [] // 新增 Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function() { // 新增 if (data.$target) { subs.push(data.$target) } return val }, set: function(newVal) { if (newVal === val) return fn && fn(newVal) // 新增 if (subs.length) { // 用 setTimeout 因为此时 this.data 还没更新 setTimeout(() => { subs.forEach(sub => sub()) }, 0) } val = newVal }, }) }
function computed(ctx, obj) { let keys = Object.keys(obj) let dataKeys = Object.keys(ctx.data) dataKeys.forEach(dataKey => { defineReactive(ctx.data, dataKey, ctx.data[dataKey]) }) let firstComputedObj = keys.reduce((prev, next) => { ctx.data.$target = function() { ctx.setData({ [next]: obj[next].call(ctx) }) } prev[next] = obj[next].call(ctx) ctx.data.$target = null return prev }, {}) ctx.setData(firstComputedObj) }
function watch(ctx, obj) { Object.keys(obj).forEach(key => { defineReactive(ctx.data, key, ctx.data[key], function(value) { obj[key].call(ctx, value) }) }) }
https://segmentfault.com/a/1190000012948175?utm_source=tag-newest