gmfe / Think

观麦前端团队的官方博客
68 stars 3 forks source link

处理JSON方法异常引发的思考 #66

Open Lotus1717 opened 5 years ago

Lotus1717 commented 5 years ago

背景

mgm库的Storage组件对localStorage的存取方法没有对数据做异常处理导致报错:

set (key, value) {
    window.localStorage.setItem(prefix + key,JSON.stringify(value))
  },

get (key) {
    const v = window.localStorage.getItem(prefix + key)
    return v ? JSON.parse(v) : v
 }

报错场景:

  1. JSON.parse解析undefined值
  2. JSON.parse解析的值不符合 JSON规范

解决思路

  1. 防止localStorage存入undefined值
  2. 利用try catch控制程序流,捕捉代码异常

如何处理localStorage

localStorage的键值对总是以字符串的形式存储。对于不是string类型的值,会自动执行toString()方法。然而,对于对象或数组而言,这个转换会丢失它原本的类型信息,导致值在取出的时候不能变为原来的格式。举例: 【例1】localStorage.setItem('a', [1, 2, 3]) 结果:localStorage的键值对依次是‘a'、'1,2,3'

【例2】localStorage.setItem('a', {a: 1}) 结果:ocalStorage的键值对依次是‘a'、'[object, object]'

所以,为了避免在数据转换过程中类型丢失,当我们要存入对象,数组的时候,要以某种编码方式把值做处理,转化为字符串再存进localStorage。这里用到的是JSON数据交换格式。值在存入localStorage前用JSON.stringify方法转化为符合JSON格式的值,从localStorage取出后用JSON.parse方法解析为原来的值。

关于JSON

JSON具有以下形式:

  1. object
  2. array
  3. value: 可以是双引号括起来的字符串(string)、数值(number)、true、false、null、object或者array。这些结构可以嵌套。

MDN上关于循环引用的对象不可序列化的限制是错的,实际验证了并没有(一开始没有验证,差点信了,还是要多动手试试)。stringify方法内部已经把这个循环引用给过滤了。举例: 【例3】

var A = {a: B, c: 1}, B = {b: A}; 
JSON.stringify(A);

结果:”{“a”:{“b”:{“c”:1}},”c”:1}”

需要注意的是,对于symbol类型值和undefined,序列化的结果是undefined。但是undefined值在解析的时候会报错,所以在把数据存入localStorage的时候把undefined值过滤掉,避免报错。

关于‘undefined'的序列化处理 JSON.stringify用法: JSON.stringify(value[, replacer [, space]])

第二个参数是一个函数,可以在序列化过程中处理被序列化的值的属性。一开始我是想着用这个replacer函数j将’undefined’转换为undefined,这样子序列化的时候自然会把undefined忽略掉。

这也是一开始跟雅堂有争议的地方,‘undefined’这个字符串是不是有意义的,需不需要在序列化的时候过滤掉。我考虑的是值在运行时候的不确定性产生的’undefined‘并非喜闻乐见(想了想,好像自己也很少碰见程序莫名其妙跑出个’undefined‘值),索性把它干掉。雅堂考虑的是从字符串本身出发,它是有意义的。想了想,干预过多并非好事,就像用户的行为由他本身解释,字符串本身也应该自己解释自己才对。而且,程序的值如果是只是单纯的读写的话,更不要随意的手动更改。

try catch

不符合JSON规范的值解析会报异常,但传进JSON方法的值是不能在编写代码时确定的,因为JavaScript是一门动态类型语言,变量只有在运行过程中才能确定值的类型。因此,在程序运行过程中JSON.parse方法很可能传入一个非JSON规范的值。所以对于这种可能发生的程序异常,需要使用异常处理机制来防止代码报错,提升程序的容错能力。这里程序的异常处理用到了try catch异常处理机制。序列化和解析数据的代码放在try代码块中,如果有异常数据导致方法报错的话,在catch内部便可以捕捉到程序异常,并打印出警告。

关于console

console对象有很多方法属性可以辅助开发的,我经常用console.log方法打印日志,以致于信息不区分轻重全部log,这样容易忽略掉一些值得注意的程序异常。这里是数据异常引发方法运行出错,应该用console.warn方法输出警告信息,提醒开发者报错。

最终代码

set (key, value) {
    try {
      value && window.localStorage.setItem(prefix + key, JSON.stringify(value))
    } catch (e) {
      console.warn(e)
    }
  },
  get (key) {
    try {
      const v = window.localStorage.getItem(prefix + key)
      return v ? JSON.parse(v) : v
    } catch (e) {
      console.warn(e)
    }
  }