因为不了解 JSON 拷贝的缺陷,导致我查错查了一下午😅🤡
背景
事情是这样的,客户端首页窗口是一个合约信息查询模块,有两个列对称的 Table 展示合约信息。大概长这样:
开发时我想着,既然列都一样,只是 正序 和 倒序 的区别,那本着能用一组数据就绝不用两组数据的原则,我将列名等信息 抽离 出来。
最终结构如下:
// columns.ts
import { ColumnProps } from 'ant-design-vue/lib/table/interface';
type CustomColumnType = (ColumnProps & { resizable?: boolean, sortIndex?: number });
const contract: CustomColumnType[] = {
{
title: '合约名称',
key: 'stockName',
dataIndex: 'stockName',
resizable: true,
width: 180,
align: 'center',
sortIndex: contractSortIndexMap['stockName']
},
{
title: '合约代码',
key: 'stockCode',
dataIndex: 'stockCode',
resizable: true,
width: 120,
align: 'center',
sortIndex: contractSortIndexMap['stockCode'],
customRender: ({ text }: any) => {
return h('span', { style: { color: '#4A7EFF' } }, text);
}
},
...
};
export {
contract
};
首页通过 import
进行引入:
import { contract } from './config/columns';
const leftContractColumns = ref(JSON.parse(JSON.stringify(contract.sort((a: any, b: any) => a.sortIndex - b.sortIndex))));
const rightContractColumns = ref(JSON.parse(JSON.stringify(contract.sort((a: any, b: any) => b.sortIndex - a.sortIndex))));
然后用 JSON.parse + JSON.stringify
实现简单的拷贝,分别赋值给对应变量,大功告成,洒洒水嘛。
随后测试了一番,发现列的渲染没问题(正序和倒序),但是在列的配置数组里,所有含有 customRender
属性定义的函数 全部 没有生效。
{
title: '合约代码',
key: 'stockCode',
...,
customRender: ({ text }: any) => { // customRender 失效
return h('span', { style: { color: '#4A7EFF' } }, text);
}
}
这是为什么呢?
我猜有可能是哪个数据一直在刷新,导致函数配置项没有反应过来(说出来我自己都不信)。
于是开启了摸瞎胡猜的排错之路,结果显而易见的,又是和兰陵王做斗争。
那没办法了,摇人是不可能摇人的,边摸鱼边想吧。
也许是摸鱼带给了我几分清明,也许是我的脑袋宕机恢复了,我突然灵光一闪:会不会是 JSON.stringfy + JSON.parse
实现拷贝有什么弊端?来做个实验试试!
锁定问题
说干就干,我们准备一个数组,往里塞个拥有两个属性和一个方法的对象:
let arr = [{
name: 'CatWatermelon',
age: 18,
talk: function(){
console.log(`${this.name} is handsome`);
}
}]
arr[0].talk() // CatWatermelon is handsome
测试一下正常输出,毛闷台。
通过 JSON
两兄弟拷贝一下看看:
嗯。嗯?talk
方法怎么不见了?
难道!莫非?对,还真是它俩搞的鬼!通过 JSON
两兄弟拷贝对象数组,对象里的函数直接 被删掉了 !
那我不用 JSON
坑比两兄弟不就完事了。
import { contract } from './config/columns';
const leftContractColumns = ref([...contract.sort((a: any, b: any) => a.sortIndex - b.sortIndex)]);
const rightContractColumns = ref([...contract.sort((a: any, b: any) => b.sortIndex - a.sortIndex)]);
渲染效果没问题,喜滋滋。
你以为这就完了吗?点到为止可不是俺们打工人的作风,接下来我们一探究竟。
拨开迷雾
首先,通过上面的分析我们已经知道是 JSON
坑比二兄弟导致我们拷贝后对象的方法丢失,那么到底是在 JSON.stringify
序列化后丢失的,还是在 JSON.parse
解析完才丢失的?
话不多说,直接动手测试一下:
一目了然,talk
方法在 JSON.stringify
序列化后就已经丢失了。这说明 JSON.stringify
应该是有自己的一套转换规则的,什么能搞里头,什么不能搞里头,搞了会怎么样,都是规定好了的。
JSON.stringify 的转换规则
那么 JSON.stringify
的转换规则到底是啥呢?话不多说,我们直接往下看:
- 转换值如果有
toJSON()
方法,该方法定义什么值将被序列化。 - 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
undefined
、任意的函数以及symbol
值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null
(出现在数组中时)。函数、undefined
被单独转换时,会返回undefined
,如JSON.stringify(function(){})
orJSON.stringify(undefined)
.- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
- 所有以
symbol
为属性键的属性都会被完全忽略掉,即便replacer
参数中强制指定包含了它们。 Date
日期调用了toJSON()
将其转换为了 string 字符串(同Date.toISOString()
),因此会被当做字符串处理。NaN
和Infinity
格式的数值及null
都会被当做null
。- 其他类型的对象,包括
Map/Set/WeakMap/WeakSet
,仅会序列化可枚举的属性。
小小方法,门道还不少,一不小心就掉进坑里了,不过这也说明了对 api 的掌握程度还不够,以至于发生问题没有第一时间怀疑到这个点。不过踩坑不要紧,填平了那就是经验了,下次出了问题多了一个思考方向,美滋滋。
结束语
本文通过踩坑 JSON
实现拷贝讲解了该方法需要注意的点,大家应该根据自己的实际情况,来选择合适的拷贝方式,而不是一招鲜,希望大家阅读本文都能有所收获。
转载自:https://juejin.cn/post/7149210196689043469