ccforward / cc

Code & Blog
1.59k stars 193 forks source link

64.深度使用 JSON.stringify() #69

Open ccforward opened 7 years ago

ccforward commented 7 years ago

深度使用 JSON.stringify()

按照 JSON 的规范,使用 JSON.stringify() 做对象序列化时,如果一个属性为函数,那这个属性就会被忽略。

const data1 = {
  a: 'aaa',
  fn: function() {
    return true
  }
}
JSON.stringify(data)

// 结果是  "{"a":"aaa"}"

还有一种情况,一个属性的值为 undefined

const data2 = {
  a: 'abc',
  b: undefined
}
JSON.stringify(data2)

// 结果是  "{"a":"abc"}"

如果属性为 null 则可以正常序列化这个属性:

const data3 = {
  a: 'abc',
  b: null
}
JSON.stringify(data3)

// 结果是  "{"a":"abc","b":null}"

因为 null 可表示已经赋值,而 undefined 表示未定义、未赋值,所以执行 JSON.stringify 不会处理。

stringify 函数

stringify 函数的定义为 JSON.stringify(value [, replacer [, space]])

后面还带有我不常用两个可选参数 replacer 和 space

value 参数不多解释,replacer 其实就是一个自定义函数,可以改变 JSON.stringify 的行为,space 就是格式化输出,最大值为 10,非整数时取值为 1

stringify 输出 Function

本质上无论怎么改,stringify 还是不会输出 Function,但是 Function 可以调用 toString() 方法的,所以思路就很明了了。

const data1 = {
  a: 'aaa',
  fn: function() {
    return true
  }
}

const replace = function(k ,v) {
  if(typeof v === 'function') {
    return Function.prototype.toString.call(v)
  }
  return v
}

JSON.stringify(data1, replace)

// 结果  "{"a":"aaa","fn":"function () {\n    return true\n  }"}"

同理可证 undefined 也能输出了

const replace = function(k ,v) {
  if(v === undefined){
    return 'undefined'
  }
  return v
}

stringify 格式化输出

JSON.stringify 的第三个参数很简单,相当于我们编辑器的 tab_size

const data4 = {
  a: 'abc',
  b: null,
  c: {
    x: 'xxx',
    y: 'yyy',
    z: 2046
  },
  d: 9527
}

JSON.stringify(data4, null, 2);

// 输出结果

/*
"{
  "a": "abc",
  "b": null,
  "c": {
    "x": "xxx",
    "y": "yyy",
    "z": 2046
  },
  "d": 9527
}"
*/

toJSON 方法

toJSON 是个覆盖函数,尽量少用,看了代码就懂了:

const data5 = {
  a: 'abc',
  b: null,
  c: {
    x: 'xxx',
    y: 'yyy',
    z: 2046
  },
  d: 9527,
  toJSON: function() {
    return 'WTF'
  }
}

JSON.stringify(data5, null, 2);

// 结果返回  "WTF"

直接覆盖掉所有的序列化处理,返回了 "WTF"

下面的评论中 @yingyuk 给出了详细的 stringify转换demo

jjeejj commented 7 years ago

推荐看我的这篇文章:JSON

xwHoward commented 7 years ago

使用JSON.stringify()和JSON.parse()做对象的深拷贝:

var data = {
  a: 'abc',
  c: {
    x: {
      foo: 'xxx',
      bar: 'yyy'
    },
    y: 'yyy'
  }
};

var copy = JSON.stringify(data);
var copyObj = JSON.parse(copy);
honpery commented 7 years ago

@xwHoward 所提到的深拷贝方法会忽略undefined和function,所以需要采用文章的方法再次封装 - -

bouquetrender commented 7 years ago

@xwHoward 这种深拷贝方式有什么副作用吗

keizure commented 7 years ago

直接用扩展运算符

lulusir commented 7 years ago

@Sakuyakun 副作用就是文中提到的undefined和function会忽略咯,还有就是不支持JSON的浏览器就用不了

refanbanzhang commented 7 years ago

比较有意思

xxyj commented 7 years ago

@lulusir 还有如果字符串中含有'\n',JSON.parse解析是会报错的

xwHoward commented 7 years ago

@honpery @Sakuyakun @lulusir @xxyj 确实这种方法有一定局限性,但在对数据结构明了、性能要求比较高(相比用递归进行对象复制)的时候可以用这种方式快速进行对象深拷贝,算是一种trick啦

yunchangyue commented 7 years ago

很好

qianlongo commented 7 years ago

赞 可以的

zhengsk commented 7 years ago

@xwHoward 按我的了解,该方法复制对象的性能比直接使用传统方式复制对象更慢。 https://jsperf.com/deep-copy-vs-json-stringify-json-parse/10

确实这种方法有一定局限性,但在对数据结构明了、性能要求比较高(相比用递归进行对象复制)的时候可以用这种方式快速进行对象深拷贝,算是一种trick啦

chinghanho commented 7 years ago

@lulusir 瀏覽器支援不是問題了。

screen shot 2017-07-31 at 10 16 22 am

yingyuk commented 7 years ago
var myMap = new Map();
myMap.set(0, 'zero');

var mySet = new Set();
mySet.add(1);

class test {
  constructor(opt) {
    this.opt = opt;
  }
}

JSON.stringify({
  String: 'string',
  Boolean: true,
  Number: 123,
  NaN: NaN, // null,
  Infinity: Infinity, // null
  null: null, // null
  undefined: undefined, // 没有 key
  Array: [1, 2, 3],
  Object: {
    foo: 'bar',
  },
  Symbol: Symbol('foo'), // {}
  Map: myMap, // {}
  Set: mySet, // {}
  Promise: new Promise(function(resolve, reject) {}), // {}
  Proxy: new Proxy({}, {}), // {}
  Class: test, //  没有 key
  ClassA: new test(123), // { "opt": 123 },
  Math: Math, // {}
  // Buffer: new Buffer('123'), // nodeJs  '{"Buffer":{"type":"Buffer","data":[49,50,51]}}'
  Error: new Error('error'), // {}
  Function: function() { // 没有 key
    console.info('hi');
  },
  Date: new Date(), // "2017-08-01T13:52:48.628Z",
  RegExp: /RegExp/, // {}
}, null, 2);

/*
"{
  "String": "string",
  "Boolean": true,
  "Number": 123,
  "NaN": null,
  "Infinity": null,
  "null": null,
  "Array": [
    1,
    2,
    3
  ],
  "Object": {
    "foo": "bar"
  },
  "Map": {},
  "Set": {},
  "Promise": {},
  "Proxy": {},
  "ClassA": {
    "opt": 123
  },
  "Math": {},
  "Error": {},
  "Date": "2017-08-01T14:04:58.522Z",
  "RegExp": {}
}"
 */