添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
行走的剪刀  ·  Mocking localstorage ...·  3 天前    · 
强悍的松鼠  ·  Why localStorage ...·  3 天前    · 
不羁的苦瓜  ·  Solved: HubSpot ...·  2 月前    · 
坚韧的皮蛋  ·  BMC Community·  2 月前    · 
瘦瘦的小熊猫  ·  openpty(3) - OpenBSD ...·  5 月前    · 

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

就我目前3年(实习了1年,965了1年,996了2年,算3年感觉少了,说是4年老司机也不为过吧。)的工作经验来看,JSON.stringify一般有以下用途:

  • 深拷贝:深拷贝引用类型的数据
  • 序列化:服务端存储重依赖前端的数据,localStorage/sessionStorage存储的数据
  • 但其实除了上面两种常用的用法之外,JSON.stringify还有许多更加强大的特性,值得系统性学习。
    关键是这些特性可能是你开发过程中 经常用到的,只是你不知道而已

    也许你觉得这是一篇枯燥的长篇大论的讲用法的博文,那么我就说几个有趣的场景吧:

  • 为什么这个对象明明有foo属性,序列化后在数据库持久化存储之后,这个属性咋就没了呢?
  • 为什么axios发送post请求时,如果req的body包含undefined值的参数,在发给服务端的请求中会消失?
  • 打印出的JSON字符串就这么不易读么?出了store成一个variable然后parse之外,能不能直接parse就清晰看到它的数据结构呢?
  • 为什么序列化一个对象,它还报错呢?报的还是那种自己引用自己的错误?
  • 如果这几个问题看了之后是一脸懵逼,那么一定要细细阅读一下这篇博文,答案就在文中。聪明的你一定会找到答案的。

  • 工作中常用JSON.stringify()
  • 深拷贝:深拷贝引用类型的数据(JSON.parse(JSON.stringify(obj/arr)))
  • 序列化:服务端存储重依赖前端的数据, localStorage/sessionStorage存储的数据(例如fabric.js的canvas模板数据,vue-amap的svg路径信息等等
  • 存储重前端功能的数据
  • localStorage/sessionStorage存储的数据
  • 查询JSON数据做解析
  • JSON.stringify()序列化具有相同属性相同值但属性顺序不同的对象,结果相等吗?
  • JOSN.stringify()与localStorage的使用示例
  • 回答一下文章开头的问题
  • 工作中常用JSON.stringify()

    深拷贝:深拷贝引用类型的数据(JSON.parse(JSON.stringify(obj/arr)))

    const obj = {
        foo: 'hi',
        bar: {
            name: 'bar',
            age: 3
        baz: ['hello','javascript']
    const arr = ['hi', { name: 'bar', age: 3 }, ['hello','javascript']];
    const deepCopy = JSON.parse(JSON.stringify(obj/arr))

    深拷贝之后,deepCopy会生成一个内存独立的obj或者arr。
    也就是说obj/arr与deepCopy存储在不同的堆内存,修改obj/arr不会影响deepCopy,修改deepCopy也不会影响obj或者arr。

    若是对于深浅拷贝不理解,建议先找资料系统性学习一下。

    序列化:服务端存储重依赖前端的数据,localStorage/sessionStorage存储的数据

    服务端存储重依赖前端的数据:例如fabric.js的canvas模板数据,vue-amap的svg路径信息等等。
    localStorage/sessionStorage存储的数据: LocalStorage/SessionStorage The keys and the values are always strings。

    存储重前端功能的数据

    例如Canvas,SVG信息,服务端做持久化。

    const complexFabricJSCanvasTemplate = { ... };
    const complexVueAMapSVGObject = { ... };
    const JSONStr = JSON.stringify(complexFabricJSCanvasTemplate/complexVueAMapSVGObject);
    axios.post('/create', {
        name: 'fabric.js', // "vue-amap"
        data: JSONStr,
      .then((res)=>{
        console.log(res);
      .catch((err)=>{
        console.log(err);
      });
    localStorage/sessionStorage存储的数据
    const testObj = {foo: 1, bar: 'hi', baz: { name: 'frankkai', age: 25 }}
    localStorage.setItem('testObj', JSON.stringify(testObj ));

    若不转化,也不会报错,会导致存储失效: localStorage.getItem('testObj');// "[object Object]"

    查询JSON数据做解析

    从服务端接口查询到存储好的Canvas或SVG数据,做解析后传入到fabric.js,vue-amap等进行绘制。

    return new Promise((resolve)=>{
        axios.get('/retrive', {
             name: 'fabric.js', // "vue-amap"
        .then((res)=>{
          const complexFabricJSCanvasTemplate = JSON.parse(res.result);
          const complexVueAMapSVGObject = JSON.parse(res.result);
          resolve(complexFabricJSCanvasTemplate/complexVueAMapSVGObject);
        .catch((err)=>{
          console.log(err);
        });
    
    let arr = [];
    let lenCode = `${JSON.stringify(arr)}.length` // '[].length'

    假如不使用JSON.stringify,会返回'.length'。

    let arr = [];
    let lenCode = `${arr}.length` // '.length'

    再来看一个模拟可选链的例子:

    可选链的执行过程。如果不能有左侧变量赋值,如何模拟?

    let data = [{name: 'foo'}, {name: 'bar'}];
    data?.map((item) => item?.name)
    let data = [{name: 'foo'}, {name: 'bar'}];
    function optionalChain(src, operation) {
        let temp = src;
        if(temp === null || temp === undefined){
            return undefined;
        return eval(`${JSON.stringify(temp)}${operation}`); 
    optionalChain(data, '.map((item) => item?.name)');

    注意:假如直接使用eval(${temp}${operation}),由于temp是数组,会执行[].toString() => "",导致标识数组的代码丢失。使用JSON.stringify序列化之后,就没有任何问题了,会生成代码字符串"[].map((item) => item?.name)"

    初识JSON.stringify()

  • 序列化数据 JSON.stringify()方法将一个js对象或者值转换为JSON string类型。
  • 过滤数据 如果指定replacer为数组或函数,可以对数据进行过滤。
  • 格式化数据 如果指定space可以对JSON的string进行格式化。
  • 序列化数据

    console.log(JSON.stringify({ x: 5, y: 6 }));
    // expected output: "{"x":5,"y":6}"
    console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)]));
    // expected output: "[3,"false",false]"
    console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }));
    // expected output: "{"x":[10,null,null,null]}"
    console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)));
    // expected output: ""2006-01-02T15:04:05.000Z""
    var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7};
    // replacer为数组
    JSON.stringify(foo, ['week', 'month']);  // '{"week":45,"month":7}', 只返回week"和"month"
    // replacer为函数
    function replacer(key, value) {
      if (typeof value === 'string') return undefined;
      return value;
    JSON.stringify(foo, replacer); // '{"week":45,"month":7}',返回callback不为undefined的。

    格式化数据

    首行缩进两个空格。

  • “ ” 两个空格
  • JSON.stringify({ a: 2 }, null, '  ');
    // JSON.stringify({a:2}, null, 2)
    "a": 2

    JSON.stringify(value, [ ,replacer[ ,space]]);语法

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

    转化为JSON string的值。

    replacer

    过滤数据。

    函数:replacer可以是函数,返回undefined时不输出数据,非undefined的数据被输出。
    字符串数组:replacer可以是数组,一般是string,也可以是number。指定输出JSON的属性白名单。['week', 'month']
    null或不写:replacer为null或者不写时,所有属性都被输出。null的话一般用于不过滤数据仅设置space的情况。

    疑惑:replacer的数组中是数字?[1,2,3,4,5] ?

    const arr = { 999: 'hi', foo: 'js', bar:' java' };
    JSON.stringify(arr, [999, 'foo']); // 打印出"{"999":"hi","foo":"js"}"。 这里的999是number类型。
    space

    增强可读性的缩进空格或填充字符串。

  • 空格锁进数 string或number 指明需要插入几个空格。
  • number最大最小值 空白数。number最大值为10,大于10时也取10。小于1时则代表不需要缩进。
  • string最大最小值 空白数。string的前10个字符,大于10只取前10个。
  • null或不写时不缩进 值为null或不写,则默认不锁进。
  • JSON string。

    TypeError “cyclic object value”

    序列化自引用的对象时会报这个错。高德地图的segments就是自引用对象。如何序列化自引用的对象可见下文。

    TypeError “BigInt value can't be serialized in JSON”

    包含突破Number存储上线的BitInt类型的obj,不能被序列化。

    JSON.stringify({foo: 1n}) // TypeError “BigInt value can't be serialized in JSON”

    JSON.stringify()描述

    常见使用须知

    如果想更好的使用JSON.stringify(),下面这些使用须知一定要掌握。

  • toJSON方法 如果被序列化的value有toJSON()方法,可以在其中定义数据是如何序列化的。
  • 数据格式保持 序列化后,Boolean,Number,String的数据类型会保留,这种转换是符合传统语义的。
  • 不支持转化undefined,function,Symbol 这些类型不是有效的JSON值。如果是普通的,直接被忽略;如果在数组中找到时,直接被转成null。 JSON.stringify(function(){}) 或JSON.stringify(undefined)返回undefined。JSON.stringify({ foo: Symbol('foo') });// "{}"
  • Symbol类型作为key时,会被完全忽略。 即使在replacer中明确指明。JSON.stringify({ [Symbol('foo')]: 'foo' });// '{}' JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')]);// '{}'
  • Date类型作为值时。 Date的toJSON方法等同于date.toISOString()。
  • 特殊的类型:Infinity,NaN, undefined, null Infinity,NaN, undefined, null都被当作null。
  • 特殊的类型:Map,Set, WeakMap,WeakSet 所有其他的Object实例(包括Map,Set,WeakMap,WeakSet)都仅有enumerable属性能被序列化。这些值默认是enumerable为false的。var foo = [['foo',1]];var mp = new Map(foo);JSON.stringify(mp); // "{}"
  • 数组的string类型属性是不可被enumerable(列举)的。 let a = ['foo', 'bar']; a['baz'] = 'quux'; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ]; JSON.stringify(a); // '["foo","bar"]'
  • TypedArray类型可以序列化。 JSON.stringify([new Float32Array([1]), new Float64Array([1])]);// '[{"0":1},{"0":1}]'
  • 究极使用示例:

    JSON.stringify({});                    // '{}'
    JSON.stringify(true);                  // 'true'
    JSON.stringify('foo');                 // '"foo"'
    JSON.stringify([1, 'false', false]);   // '[1,"false",false]'
    JSON.stringify([NaN, null, Infinity]); // '[null,null,null]'
    JSON.stringify({ x: 5 });              // '{"x":5}'
    JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)) 
    // '"2006-01-02T15:04:05.000Z"'
    JSON.stringify({ x: 5, y: 6 });
    // '{"x":5,"y":6}'
    JSON.stringify([new Number(3), new String('false'), new Boolean(false)]);
    // '[3,"false",false]'
    // String-keyed array elements are not enumerable and make no sense in JSON
    let a = ['foo', 'bar'];
    a['baz'] = 'quux';      // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ]
    JSON.stringify(a); 
    // '["foo","bar"]'
    JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }); 
    // '{"x":[10,null,null,null]}' 
    // Standard data structures
    JSON.stringify([new Set([1]), new Map([[1, 2]]), new WeakSet([{a: 1}]), new WeakMap([[{a: 1}, 2]])]);
    // '[{},{},{},{}]'
    // TypedArray
    JSON.stringify([new Int8Array([1]), new Int16Array([1]), new Int32Array([1])]);
    // '[{"0":1},{"0":1},{"0":1}]'
    JSON.stringify([new Uint8Array([1]), new Uint8ClampedArray([1]), new Uint16Array([1]), new Uint32Array([1])]);
    // '[{"0":1},{"0":1},{"0":1},{"0":1}]'
    JSON.stringify([new Float32Array([1]), new Float64Array([1])]);
    // '[{"0":1},{"0":1}]'
    // toJSON()
    JSON.stringify({ x: 5, y: 6, toJSON(){ return this.x + this.y; } });
    // '11'
    // Symbols:
    JSON.stringify({ x: undefined, y: Object, z: Symbol('') });
    // '{}'
    JSON.stringify({ [Symbol('foo')]: 'foo' });
    // '{}'
    JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')]);
    // '{}'
    JSON.stringify({ [Symbol.for('foo')]: 'foo' }, function(k, v) {
      if (typeof k === 'symbol') {
        return 'a symbol';
    });
    // undefined
    // Non-enumerable properties:
    JSON.stringify( Object.create(null, { x: { value: 'x', enumerable: false }, y: { value: 'y', enumerable: true } }) );
    // '{"y":"y"}'
    // BigInt values throw
    JSON.stringify({x: 2n});
    // TypeError: BigInt value can't be serialized in JSON

    replacer 参数

    replacer可以是function,也可以是array。

    function
    function replacer(key="", value) {
        return value;
    

    作为function,有两个参数key 和 value。
    默认情况下replacer函数的key为“”,对于对象中的每个值,都会call一次这个函数。

    `function replacer(key,value){ 
        return value;
    JSON.stringify({foo: false,bar:123},replacer);`

    返回值有以下几种情况:

  • number number相关联的string属性被添加。 return 123; // "123"
  • string 相关联的string属性被添加。return "foo"; // "foo"
  • boolean “true”或”false“被添加。return true; // "true"
  • null "null"被添加。 return null; //"null"
  • value 如果是正常的value对象,那么会递归它的属性序列化成JSON string。// return value;"{"foo":false,"bar":123}"
  • undefined 不返回这个属性。// JSON.stringify({foo: false,bar:undefined},replacer);"{"foo":false}"。
  • 使用强大的undefined是需要注意:

  • undefined 用过axios的同学这里破案了:当我们的post请求的reqBody包含以undefined为值的参数时,根本不会发到服务端的接口,而null可以。这就是JSON.stringify()的功劳。
    v = JSON.stringify(v)时,会将undefined类型的值自动过滤掉。
  • // axios源码 buildURL.js 37~56行:
      utils.forEach(params, function serialize(val, key) {
          if (utils.isArray(val)) {
            key = key + '[]';
          } else {
            val = [val];
          utils.forEach(val, function parseValue(v) {
            if (utils.isDate(v)) {
              v = v.toISOString();
            } else if (utils.isObject(v)) {
              v = JSON.stringify(v); // 注意这里
            parts.push(encode(key) + '=' + encode(v));
          });
        });
  • 不能用replacer去移除array的值。若返回undefined,会返回null。
  • function replacer(key,value){
        if(typeof value ==='boolean'){return undefined}
        return value;
    JSON.stringify([1, 'false', false],replacer);
    // "[1,"false",null]"
    function replacer(key, value) {
      // 返回undefined过滤属性
      if (typeof value === 'string') {
        return undefined;
      return value;
    var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7};
    JSON.stringify(foo, replacer); // '{"week":45,"month":7}'
    JSON.stringify(foo, ['week', 'month']);  // '{"week":45,"month":7}', 

    space 参数

  • 可以是number 最大10,10个缩进
  • 可以是string 最长取前10个,10个char缩进
  • JSON.stringify({ a: 2 }, null, ' ');
    // '{
    //  "a": 2
    JSON.stringify({ uno: 1, dos: 2 }, null, '\t');
    // returns the string:
    // '{
    //     "uno": 1,
    //     "dos": 2
    // }'

    toJSON()的表现

    如果要被字符串化的对象具有一个名为toJSON的属性,其值是一个函数,则该toJSON()方法将自定义JSON字符串化行为:代替被序列化的对象,该toJSON()方法返回的值将被序列化,而不是被序列化的对象。JSON.stringify()调用toJSON一个参数:

  • 如果此对象是属性值,则返回属性名称
  • 如果它在数组中,则返回数组中的索引(作为字符串)
  • 如果JSON.stringify()直接在此对象上调用,则返回一个空字符串
  • var obj = {
        data: 'data',
        toJSON (key) {
            return key;
    JSON.stringify(obj);
    // '""""
    JSON.stringify({ obj })
    // '{"obj":"'obj'"}'
    JSON.stringify([ obj ])
    // '["'0'"]'

    JSON.stringify()序列化循环引用时的问题

    自引用对象抛出TypeError

    TypeError: Converting circular structure to JSON

    const circularReference = {};
    circularReference.myself = circularReference;
    // Serializing circular references throws "TypeError: cyclic object value"
    JSON.stringify(circularReference);

    JSON.stringify高德地图vue-amap中的自引用对象

    路径规划plans的路径SVG信息segments对象。

    需求是这样的,前端需要将路线的信息传递给后端,其中包括经纬度数组和SVG数组。
    用JSON.stringify()序列化经纬度数组是ok的,但是序列化的SVG数组是自引用的,会报错。(是后来才知道这个SVG数组可以不往服务端存的,不过当时序列化报错是真的懵逼了)

    如何序列化自引用对象?

    使用Douglas Crockford的cycle.js

    它在全局JSON对象上新增了2个方法:

  • JSON.decycle(将自引用的属性myself:obj 替换为myself:{$ref: "$"}
  • JSON.retrocycle
  • 使用示例:

    var circularReference = {};
    circularReference.myself = circularReference;
    JSON.decycle(circularReference);
    // { "$ref": "$" }

    JSON.stringify()序列化具有相同属性相同值但属性顺序不同的对象,结果相等吗?

    var a = JSON.stringify({ foo: "bar", baz: "quux" })
    //'{"foo":"bar","baz":"quux"}'
    var b = JSON.stringify({ baz: "quux", foo: "bar" })
    //'{"baz":"quux","foo":"bar"}'
    console.log(a === b) // false

    JOSN.stringify()与localStorage的使用示例

    localStorage只能存储string类型的数据,因此需要使用JSON.stringify()将对象序列化为JSON string。

    var session = {
      'screens': [],
      'state': true
    session.screens.push({ 'name': 'screenA', 'width': 450, 'height': 250 });
    session.screens.push({ 'name': 'screenB', 'width': 650, 'height': 350 });
    session.screens.push({ 'name': 'screenC', 'width': 750, 'height': 120 });
    localStorage.setItem('session', JSON.stringify(session));
    var restoredSession = JSON.parse(localStorage.getItem('session'));
    console.log(restoredSession);

    回答一下文章开头的问题

    为什么这个对象明明有foo属性,序列化后在数据库持久化存储之后,这个属性咋就没了呢?

    值为undefined?
    replacer对它做过滤了?
    toJSON方法中做过滤了?
    属性的enumerable值为false?
    Symbol?
    Map?Set?WeakMap?WeakSet?
    找原因吧。

    为什么axios发送post请求时,如果req的body包含undefined值的参数,在发给服务端的请求中会消失?

    v = JSON.stringify(v); // v = {foo: undefined} =>"{}"

    源码在buildURL.js 37~56行

    打印出的JSON字符串就这么不易读么?出了store成一个variable然后parse之外,能不能直接parse就清晰看到它的数据结构呢?

    const testObj = {foo: 1, bar: 'hi', baz: { name: 'frankkai', age: 25 }}
    JSON.stringify(testObj, null, 4);
    "foo": 1, "bar": "hi", "baz": { "name": "frankkai", "age": 25

    为什么序列化一个对象,它还报错呢?报的还是那种自己引用自己的错误?这种对象不能序列化了吗?

    这是因为自引用之后,会限制死循环。
    引擎应该是做了特殊的处理,发现这种无限循环时自动抛出异常。
    这种对象不能序列化了吗?可以使用cycle.js的decycle(序列化。

    var circularReference = {};
    circularReference.myself = circularReference;
    JSON.decycle(circularReference);
    // { "$ref": "$" }

    参考资料:

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
  • https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
  • https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
  • https://github.com/axios/axios/blob/master/lib/helpers/buildURL.js
  •