当面试官问起JSON.stringify时,我这样回答后,他说...

2024-01-15 07:20

本文主要是介绍当面试官问起JSON.stringify时,我这样回答后,他说...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

JSON.stringify 作为日常开发中经常使用的方法,你真的能灵活运用它吗?
面试官问起这个时,你只会说我就只会用JSON序列化对象吗?

学习本文之前让大家带着几个问题,一起来深入学习,打开 JSON.stringify 的另一扇门~

  1. JSON.stringify 函数有几个参数,每个参数分别有啥用啊?
  2. JSON.stringify 序列化准则有哪些啊?
  3. 函数序列化中会如何处理?
  4. null、undefined、NaN 等特殊的值又会如何处理?
  5. ES6 后增加的 Symbol 类型、BigInt 序列化过程中会有特别处理吗?
  6. stringify 什么情况下不适合做深拷贝?
  7. 你能想到那些 stringify 的妙用?

整个文章的脉络跟下面思维导图一致,大家可以先留一下印象。
请添加图片描述

三参数

在日常编程中,我们经常 JSON.stringify 方法将某个对象转换成 JSON 字符串形式。

let user = {name: '张三',age: 18
}
// 输出结果: {"name":"张三","age":18}
console.log(JSON.stringify(user))

stringify 真的就这么简单吗?我们先来看一下 MDN 中对 stringify 的定义。

MDN 中指出: JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。
看完定义,stringfy 不止一个参数吗?当然了,stringify 有三个参数。

咱们来看一下 stringify 语法和参数介绍:

JSON.stringify(value[, replacer [, space]])

  • value: 将要序列后成 JSON 字符串的值。
  • replacer (可选)
    1. 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    2. 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中
    3. 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
  • space (可选): 指定缩进用的空白字符串,用于美化输出
    1. 如果参数是个数字,它代表有多少的空格。上限为10。
    2. 该值若小于1,则意味着没有空格
    3. 如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格
    4. 如果该参数没有提供(或者为 null),将没有空格

replacer 的用法

用法一:replacer 作为函数

replacer 作为函数,它有两个参数,键(key) 和 值(value),并且两个参数都会被序列化。

在开始时,replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。理解这点很重要,replacer 函数并非是上来就把对象解析成键值对形式,而是先传入了待序列化对象。随后每个对象或数组上的属性会被依次传入。 如果函数返回值为undefined或者函数时,该属性值会被过滤掉,其余按照返回规则。

// repalcer 接受两个参数 key value 分别为对象的每个键值对
// 根据 undefined 的返回进行过滤 测试
function replacer(key, value) {if (typeof value === "string") {return undefined;}return value;
}
// 据 function 的返回进行过滤 测试
function replacerFunc(key, value) {if (typeof value === "string") {return () => {};}return value;
}
let user = { name: '张三', age: 18, sex: '男', friends: ['小明', '小花', 007], isMarried: false };
JSON.stringify(user, replacer);      //  输出结果: {"age":18,"friends":[null,null,7],"isMarried":false}'
JSON.stringify(user, replacerFunc);  // 同上

注意📢:从结果发现 friends 数组按之前说的为啥不直接返回数组,相反连里面每一个值都好像遍历了一次 replacer 函数,且出现了null

解答 >>>>>>>
这也是 stringify 处理数组的一个特殊知识点; 如果序列化的是数组 stringify会对里面每一项进行无差别对待,也就是说数组的每一项会重新走一次replacer 函数,但是呢此时经过 replace 函数 → 返回的 undefined 或者函数,当前值不会被忽略,而将会被 null 取代噢~

用法二:replacer 作为数

作为数组比较好理解,过滤数组中出现的键值。

let params = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
// 输出结果: {"week":45,"month":7}
JSON.stringify(params, ['week', 'month']);  

JSON 序列化结果为 {"week":45,"month":7} , 只保留 weekmonth 属性值。

九特性

特性一: undefined、函数、Symbol值
  1. 出现在非数组对象属性值中: undefined、任意函数、Symbol 值在序列化过程中将会被忽略
  2. 出现在数组中: undefined、任意函数、Symbol 值会被转化为 null
  3. 单独转换时: 会返回 undefined
// 1. 对象属性值中存在这三种值会被忽略
let user1 = {name: '张三',age: 18,sayHello() {console.log('hello world')},gir: undefined,id: Symbol(007)
}
// 输出结果: {"name":"张三","age":18}
console.log(JSON.stringify(user1))// 2. 数组中这三种值会被转化为 null
let user2 = ['李四', 20, function sayHello() {console.log('hello world')}, undefined,Symbol(123)
]// 输出结果: ["李四",20,null,null,null]
console.log(JSON.stringify(user2))// 3. 这三种值单独转化将会返回 undefined
console.log(JSON.stringify(undefined))
console.log(JSON.stringify(Symbol(007)))
console.log(JSON.stringify(function sayHello() { console.log('hello world')
}))
特性二: toJSON() 方法

转换值如果有 toJSON() 方法,toJSON() 方法返回什么值,序列化结果就返回什么值,其余值会被忽略,也就是只处理toJSON 方法里面返回的元素

let user = {name: '李四',age: 20,toJSON(){return ['张三', Symbol(007), undefined, 18, true]}
}
// 输出结果: ["张三",null,null,18,true]
console.log(JSON.stringify(user));
特性三: 布尔值、数字、字符串的包装对象

布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值

// 输出结果: ["张三",18,true]
JSON.stringify([new String("张三"), new Number(18), new Boolean(true)])
特性四: NaN Infinity null

特性四主要针对 JavaScript 里面的特殊值,例如 Number 类型里的 NaNInfinitynull 。此三种数值序列化过程中都会被当做 null

// 特性三讲过布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
// 隐式类型转换就会调用包装类,因此会先调用 Number => NaN => null
// 1/0 => Infinity => null
// 输出结果: [null,null,null,null]
JSON.stringify([Number('123a'), +'123a', 1/0, null])
特性五: Date对象

Date 对象上部署了 toJSON 方法(同 Date.toISOString() )将其转换为字符串,因此 JSON.stringify() 将会序列化 Date 的值为时间格式字符串

// 输出结果: "2021-02-28T03:55:02.929Z"
JSON.stringify(new Date())
特性六: Symbol

特性一提到,Symbol 类型当作值来使用时,对象、数组、单独使用分别会被忽略、转换为 null 、转化为 undefined

同样的,所有以 Symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

let user = {name: '张三',age: 18,[Symbol('sex')]: '男'
}
function replacer(key, value) {if (typeof key === 'symbol') {return value;}
}// 输出结果: undefined
JSON.stringify(user, replacer)

通过上面案例,我们可以看出,虽然我们通过 replacer 强行指定了返回 Symbol 类型值,但最终还是会被忽略掉。

特性七: BigInt

JSON.stringify 规定: 尝试去转换 BigInt 类型的值会抛出 TypeError

let bigNumber = BigInt(1)
// 输出结果: Uncaught TypeError: Do not know how to serialize a BigInt
console.log(JSON.stringify(bigNumber))
特性八: 循环引用

对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误

日常开发中深拷贝最简单暴力的方式就是使用 JSON.parse(JSON.stringify(obj)),但此方法下的深拷贝存在巨坑,关键问题就在于 stringify 无法处理循环引用问题。

let obj = {name: '张三',age: 18
}let loopObj = {obj
}
// 形成循环引用
obj.loopObj = loopObj;
/* Uncaught TypeError: Converting circular structure to JSON--> starting at object with constructor 'Object'|     property 'loopObj' -> object with constructor 'Object'--- property 'obj' closes the circleat JSON.stringify (<anonymous>)at <anonymous>:10:6
*/
JSON.stringify(obj)
特性九: 可枚举属性

对于对象(包括 Map / Set / WeakMap / WeakSet)的序列化,stringify 也明确规定,仅会序列化可枚举的属性

// 不可枚举的属性默认会被忽略
let obj1 = new Set([1,2,3,4,5])
let obj2 = Object.create(null,{name: { value: '张三', enumerable: false },age: { value: 18, enumerable: true }}
)JSON.stringify(obj1) // 输出结果: {}
JSON.stringify(obj2) // 输出结果: {"age":18}

【拓展芝士🧀】
不可枚举的对象,同样在for…in循环 , Object.keys方法 中获取不了

六妙用

妙用一:localStorage

localStorage 对象用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。通常我们以对象形式进行存储。

let user = {name: '王五',age: 30,sex: '男'
}
/* ---- 单纯调用 localStorage.setItem() ---- */
localStorage.setItem('userInfo', user);// 最终返回结果是 [object Object]
// 可见单纯调用localStorage是失败的
console.log(localStorage.getItem('userInfo'))/* ---- 配合 JSON.stringify 方法 ---- */
localStorage.setItem('userInfo1', JSON.stringify(user))
// 最终返回结果是 { name: '王五', age: 30, sex: '男' }
console.log(JSON.parse(localStorage.getItem('userInfo1')))
妙用二:属性过滤

来假设这样一个场景,后端返回了一个很长的对象,对象里面属性很多,而我们只需要其中几个属性,并且这几个属性我们要存储到 localStorage

1.方案一: 解构赋值+ stringify

// 我们只需要 a,e,f 属性
let res = { a:1, b:2, c:3, d:4, e:5, f:6, g:7 }
// 解构赋值
const {a,e,f} = res
// 存储到localStorage
localStorage.setItem('res', JSON.stringify({a,e,f}))
// {"a":1,"e":5,"f":6}
console.log(localStorage.getItem('res'))

2.使用 stringify 的 replacer 参数

// 借助 replacer 作为数组形式进行过滤
localStorage.setItem('res', JSON.stringify(res, ['a','e','f']))
// {"a":1,"e":5,"f":6}
console.log(localStorage.getItem('res'))

replacer 是数组时,可以简单的过滤出我们所需的属性,是一个不错的小技巧。

妙用三:三思而后行之深拷贝

使用 JSON.parse(JSON.stringify) 是实现对象的深拷贝最简单暴力的方法之一。但也正如标题所言,使用该种方法的深拷贝要深思熟虑

  1. 循环引用问题,stringify 会报错
  2. 函数、undefined、Symbol 会被忽略
  3. NaN、Infinity 和 -Infinity 会被序列化成 null

因此在使用 JSON.parse(JSON.stringify) 做深拷贝时,一定要深思熟虑。如果没有上述隐患,JSON.parse(JSON.stringify) 是一个可行的深拷贝方案。

【小提示】
当面试官,问你深拷贝的时候,是不是可以说一下除了什么情况下,JSON.parse(JSON.stringify) 也是可以深拷贝的,这样面试官应该会很满意的~

妙用四:对象的 map 函数

在使用数组进行编程时,我们会经常使用到 map 函数。有了 replacer 参数后,我们就可以借助此参数,实现对象的 map 函数

// 例如下面给 obj 对象的属性值乘以2
let obj = {a: 1,b: 2,c: 3
}
const fn = (key, val) => {if (typeof val === "number") {return val * 2;}return val;
}
// 输出结果: {a: 2, b: 4, c: 6}
console.log(JSON.parse(JSON.stringify(obj, fn)))

很多同学有可能会很奇怪,为什么里面还需要多加一部判断,直接 return value * 2 不可吗?

解答>>>>>>>>
上文讲过,replacer 函数首先传入的是待序列化对象,对象 * 2 => NaN => toJSON(NaN) => undefined => 被忽略,就没有后续的键值对解析了。

妙用五:删除对象属性

借助 replacer 函数,我们还可以删除对象的某些属性

let user = {name: '张三',age: 18
}
// 输出结果: {"age":18}
JSON.stringify(user, (key, val) => {// 返回值为 undefined时,该属性会被忽略 if (key === 'name') {return undefined;}return val;
})
妙用六:对象判断

JSON.stringify 可以将对象序列化为字符串,因此我们可以借助字符串的方法来实现简单的对象相等判断

//判断数组是否包含某对象
let people = [{name:'张三'},{name:'李四'},{name:'王五'},
];
let people1 = {name:'张三'};
// true
JSON.stringify(people).includes(JSON.stringify(people1))// 判断对象是否相等
let p1 = {name:'张三', age: 18, sex: '男', firends: ['李四','王五']}
let p2 = {name:'张三', age: 18, sex: '男', firends: ['李四','王五']}// true
JSON.stringify(p1) === JSON.stringify(p2);

参考链接

  • 你不知道的 JSON.stringify() 的威力
  • 就因为JSON.stringify,我的年终奖差点打水漂了

这篇关于当面试官问起JSON.stringify时,我这样回答后,他说...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/608083

相关文章

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

Springboot3+将ID转为JSON字符串的详细配置方案

《Springboot3+将ID转为JSON字符串的详细配置方案》:本文主要介绍纯后端实现Long/BigIntegerID转为JSON字符串的详细配置方案,s基于SpringBoot3+和Spr... 目录1. 添加依赖2. 全局 Jackson 配置3. 精准控制(可选)4. OpenAPI (Spri

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

使用Java将实体类转换为JSON并输出到控制台的完整过程

《使用Java将实体类转换为JSON并输出到控制台的完整过程》在软件开发的过程中,Java是一种广泛使用的编程语言,而在众多应用中,数据的传输和存储经常需要使用JSON格式,用Java将实体类转换为J... 在软件开发的过程中,Java是一种广泛使用的编程语言,而在众多应用中,数据的传输和存储经常需要使用j

MySQL 中的 JSON 查询案例详解

《MySQL中的JSON查询案例详解》:本文主要介绍MySQL的JSON查询的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 的 jsON 路径格式基本结构路径组件详解特殊语法元素实际示例简单路径复杂路径简写操作符注意MySQL 的 J

Spring Boot中JSON数值溢出问题从报错到优雅解决办法

《SpringBoot中JSON数值溢出问题从报错到优雅解决办法》:本文主要介绍SpringBoot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和... 目录一、问题背景:为什么我的接口突然报错了?二、为什么会发生这个错误?1. Java 数据类型的“容量”限制

Spring 请求之传递 JSON 数据的操作方法

《Spring请求之传递JSON数据的操作方法》JSON就是一种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串,主要负责在不同的语言中数据传递和交换,这... 目录jsON 概念JSON 语法JSON 的语法JSON 的两种结构JSON 字符串和 Java 对象互转