JSON.stringify与replacer异常行为:不同谓词为何结果不同?
问题场景
以下代码中,两次调用仅谓词函数不同,但第一个调用输出不符合预期,第二个却正常工作,需要解释原因。
原始代码
'use strict'; function stringify(value,p) { function replacer(k,v) { if (!Array.isArray(v)) return p(k) ? v : undefined; return v } return JSON.stringify(value,replacer); } console.log(stringify([{ city: 'Milano', air_quality: 'red', temperature: 10 }, { air_quality: 'yellow', 'temperature': 20, 'sea_conditions': 3, city: 'Genova' }], k => k.match(/^[a-z]+$/))); console.log(stringify([{ city: 'Milano', air_quality: 'red', temperature: 10 }, { air_quality: 'yellow', 'temperature': 20, 'sea_conditions': 3, city: 'Genova' }], k => k.length < 5));
预期输出
[{"city":"Milano","temperature":10},{"temperature":20,"city":"Genova"}] [{"city":"Milano"},{"city":"Genova"}]
实际输出
[null,null] [{"city":"Milano"},{"city":"Genova"}]
可行的替换函数
已知使用以下replacer可得到预期结果:
function replacer(k,v){ if (typeof v === 'object' && !Array.isArray(v)) { let result = {} for (let idx in v) { if(p(idx)) { result[idx] = v[idx] } } return result } return v }
原因解析
问题根源在于JSON.stringify替换函数的执行逻辑和第一版replacer的处理逻辑不匹配:
替换函数的调用时机:JSON.stringify会递归遍历数据结构,处理数组中的对象时,会先调用一次replacer——此时参数
k是该对象在数组中的索引(如"0"、"1"),v是对象本身;之后才会遍历对象的每个属性,此时k是属性名(如"city"),v是属性值。第一个谓词的致命问题:第一个谓词
k => k.match(/^[a-z]+$/)要求k全为小写字母,但数组索引是数字字符串,完全不满足匹配条件。replacer处理对象本身时直接返回undefined,而JSON.stringify遇到返回undefined的情况,会把当前值(整个对象)从结果中剔除,数组元素因此变成null。第二个谓词正常工作的原因:第二个谓词
k => k.length <5对数组索引"0"、"1"来说,长度为1,满足条件,所以replacer返回对象本身,后续会继续遍历对象属性。处理属性时,k是属性名,比如"city"长度4符合要求被保留,"air_quality"长度11不符合被剔除,最终结果正确。正确replacer的逻辑优势:你后来写的replacer遇到非数组对象时,直接遍历对象自身属性,对每个属性名应用谓词并构建新对象返回。这跳过了对对象父级键(数组索引)的判断,直接处理内部属性,自然不会出现整个对象被剔除的问题。
内容的提问来源于stack exchange,提问作者1.1

