解决Vue 使用Watch侦听器监听对象如何取得旧值问题
Created by @一个前端er 2020/08/25
最近在项目中因为需要深度监听数组对象的变化,其实是一组表格数据,数组中的每一项都是一个对象,或者说都是一行数据,监听它的改变就必须使用Watch
侦听器来完成了,并且由于对象(数组)不止一层引用,需要在Watch中开始深度监听deep:true
,基本语法如下:
watch:{
someVal:{
handler(newVal,oldVal){
//处理逻辑
console.log(newVal.oldVal)
},
deep:true
}
}
当数据改变之,确实是执行了handler里面的代码,但是在控制台查看新值和老值,发现它们两都是改变之后的数据,这就让我有点纳闷了,以为是哪里出现问题了,然后我就判断了它们两是不是同一个对象:
console.log(newVal === oldVal) //true
结果返回的是true,果然它们两指向的是同一个引用,这说明 Watch
只是侦听到它改变了,并没有把之前的老值给缓存下来,引用类型直接指向了引用地址。后来在网上搜了一下解决方案,基本都是类似的:使用Computed
计算属性缓存 深拷贝后的 data
或 props
里面的数据,然后再使用watch
进行侦听,这样前前后后指向的是不同的两个对象,从而获得改变之前的旧数:
computed:{
cacheSomeVal(){
return deepClone(this.someVal) //JSON.parse(JSON.stringify(this.someVal)
}
},
watch:{
cacheSomeVal:{
handler(newVal,oldVal){
//处理逻辑
console.log(newVal.oldVal)
},
deep:true
}
}
但是网上基本上使用的是JSON的深拷贝,通过序列化和反序列化来进行深拷贝,但是基本不建议使用这种方式来进行深拷贝,容易带来性能问题,而且也会丢失原有的原型链,并且碰到一些特殊值如 Undefined
会报错,会NaN
序列化成null
等等问题,在这里推荐大家使用深拷贝算法进行深拷贝,如果是不涉及原型链、Map 或者Set等特殊的类型,只是Array|普通Object|基本数据类型(不包含Symbol)
等普通对象,完全可以用以下算法进行深拷贝:
转载自25行实现 javascript 深拷贝算法(纯手写)
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
function deepClone(source) {
let target = null;
if (!isArray(source) && !isObject(source)) {
target = source;
}
if (isArray(source)) {
target = [];
for (let i = 0; i < source.length; i++) {
target[i] = deepClone(source[i]);
}
}
if (isObject(source)) {
target = {
};
for (let key in source) {
target[key] = deepClone(source[key]);
}
}
return target;
}
使用JSON的Api进行深拷贝基本要比深拷贝算法慢不少
当然大家也可以用其他的深拷贝算法,在这里我简单使用了知乎一位老哥的算法,当涉及到原型链这种比较复杂的拷贝的时候,还是推荐大家找一下全面的深拷贝算法。
关于JSON序列化与反序列的性能问题,大家可以参考这几篇文章:
为何不推荐使用JSON.stringify做深拷贝
如何提升JSON.stringify()的性能?
贴上比较的代码:
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
function deepClone(source) {
let target = null;
if (!isArray(source) && !isObject(source)) {
target = source;
}
if (isArray(source)) {
target = [];
for (let i = 0; i < source.length; i++) {
target[i] = deepClone(source[i]);
}
}
if (isObject(source)) {
target = {
};
for (let key in source) {
target[key] = deepClone(source[key]);
}
}
return target;
}
const a = {
name:'a front-end developer',
info:{
sex:'male',
handsome:true
}
}
console.time();
for (let index = 0; index < 100000; index++) {
deepClone(a)
}
console.timeEnd();
console.time();
for (let index = 0; index < 100000; index++) {
JSON.parse(JSON.stringify(a))
}
console.timeEnd();