「JSON」并不是JS对象

$ 什么是JSON ?

  JSON: JS 对象表示法,它是一种数据格式,不是一种编程语言。JSON不支持变量,函数或对象实例,他就是一种表示结构化数据的格式,虽与JS中表示数据的某些语法相同,但它并不局限于JS范畴,JSON并不从属于JS,很多语言都有针对JSON的解析器和序列化器。

$ 用JSON 表示值:

JSON的语法可以表示以下三种类型的值

  1. 简单值: 字符串,数值,布尔值,null
  2. 对象: 对象是一种复杂的数据类型,表示一组无序的键值对,键和值可以是简单值
  3. 数组: 数组也是一种复杂的数据类型,表示一组有序的值的列表

@ JOSN表示简单值

  JSON更多的表示复杂的数据额类型,而简单值只是整个数据结构中的一部分

5   // JSON表示数值5的方式
"Hello world"  // JSON表示字符串的方式

码农注释】JS字符串与JSON表示字符串最大区别在于,后者必须使用双引号,单引号会导致语法错误。

@ JSON表示对象

  JSON表示对象是我们最常用的JSON数据格式之一,JSON中的对象与JS中的对象还是有些不同。

  1. 没有声明变量(JSON中就没有变量的概念)
  2. 末尾没有分号(JS对象不是JS语句,只是数据表示法,不需要分号)
  3. 对象的属性必须加双引号 (!真是个常见的错误)
// JS 字面量对象
var person = {
  name: "Nicholas",
  age: 29
};
// JSON表示对象的方式
{
  "name": "Nicholas",
  "age": 29
}

码农注释】JSON对象键值对本质上是用JSON简单值表示的,简单值规则中就需要加双引号,因此这里也要加。当然JSON对象属性值也可以用JSON对象表示,就是多层嵌套而已,道理相同。

@ JSON表示数组

  JSON数组的数据表示这更加灵活,它与JS数组的区别和JSON对象上的区别类似不赘述,值得注意的是数组中有对象数组这种数据结构,即[{}, {}, {}]形式

// JS数组
var values = [25, "Hi", true];
// JSON数组
[25, "Hi", true]
// 更复杂一些的JSON数组
[
  {
    "title": "JavaScriot",
    "author": ["Nicholas"],
    "edition": 3
  },
  {
    "title": "Ajax",
    "author": ["Nicholas", "Joe", "Jeremy"],
    "edition": 2
  },
]

$ JSON对象 解析与序列化

  JSON流行的原因,并不止因为它类似JS的语法,更重要的是因为可以把JSON数据结构解析为有用的JS对象。早期的JSON解析器基本上就是使用JS的eval()函数。在ES5的规范中为避免eval()执行一些恶意代码,改用使用shim.

  JSON对象有两个方法:stringify()用于将JS对象序列化为JSON字符串,parse()用于把JSON字符串解析为原生JS值

(1)序列化:JSON.stringify()方法

JSON.stringify()方法是将一个JS值(对象或者数组)序列化为一个 JSON字符串,值得注意的是,序列化JS对象时,所有函数及原型成员都会被有意忽略,所有值为undefined的也将被忽略,最终都是值为有效的JSON数据类型的实例属性。

var book = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011,
  master: undefined,
  lastRead: function() {
    return "20180812"
  }
};
var jsonBook = JSON.stringify(book);
console.log(jsonBook);  // 见 码农注释
console.log(typeof(jsonBook));  // string - 这里指JSON字符串

码农注释JSON.stringify()方法将名为book的JS对象序列化成了JSON字符串,并将该字符串存放在变量jsonBook上。jsonBook打印结果为{"title":"JavaScript","authors":["Nichoas C. Zakas"],"edition":3,"year":2011} 可以看到键值对都加了双引号。注意jsonBook中并没有属性lastReadmaster的序列化值,因为它们被故意忽略了。

@ JSON.stringify()序列化选项(参数)

stringify()函数第一个参数是JS对象我们已经清楚,它还能接受另外两个参数,给要序列化的JS对象增加序列化方式。第一个参数是过滤器,第二个参数是 字符串缩进。

1. 增加过滤器(替换:replacer)
可增加两种过滤器
(1)数组形式,包含想序列化的属性名。未出现的直接过滤。
(2)过滤函数。根据函数规则确定结果,未出现的不过滤,显示原值。

// 增加数组过滤器。数字的值为需要序列化的属性名
var book = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011
};
var jsonBook = JSON.stringify(book, ['edition','title']);
console.log(jsonBook)  // {"edition":3,"title":"JavaScript"}

执行结果: {"edition":3,"title":"JavaScript"}
码农解释】序列化结果的属性顺序与数组过滤器的顺序相同,该例中titleedition后出现

// 过滤器为函数
var book = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011,
  nothing: undefined,
  lastRead: function(){
    return "20180814"
  }
};

// 函数不能取到外边定义
var jsonBook = JSON.stringify(book, function(key, value){
  switch (key) {
    case "authors": 
      return value.join(",");
    case "year":
      return 2018; 
    default:
      return value;
    }
});
console.log(jsonBook) 
// {"title":"JavaScript","authors":"Nichoas C. Zakas","edition":3,"year":2018}

码农解释】函数过滤器一定要提供default项,以便其他值都能正常出现在结果中,(其实不加也能保留其他的属性,但可能存在兼容问题)。实际上,第一次调用这个函数过滤器,传入的键是一个空字符串,而值就是book对象。

2.增加字符串缩进
控制结果中的缩进,将堆放的字符串数据改用具有格式的字符串格式显示。缩进办法有两种:
(1)数值,表示缩进空格数。
(2)字符串,用字符串填充缩进。

var book = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011,
  nothing: undefined,
  lastRead: function(){
    return "20180814"
  }
};
var jsonBook = JSON.stringify(book, null, 4);
console.log(jsonBook) 

】执行结果为字符串,只不过因为缩进显示成JSON对象如下(如果第三个参数是字符串,则缩进的填充字符为字符串)

{
    "title": "JavaScript",
    "authors": [
        "Nichoas C. Zakas"
    ],
    "edition": 3,
    "year": 2011
}

3. toJSON(): 序列化的另一个办法
  有时候,JSON.stringify()还是不能满足对某些对象进行自定义序列化的需求,这种情况下我们可以给对象定义toJSON()方法,他表示在执行JSON.stringify时的执行结果。原生Date也有个toJSON()方法,将在下一部分说明。

var book = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011,
  nothing: undefined,
  toJSON: function() {
    return this.title
  }
};
 
var jsonBook = JSON.stringify(book);
console.log(jsonBook);  // "JavaScript"
关于不同序列化办法执行的优先级

(1)如果存在toJSON()方法,而且能通过他获取有效的值,这调用该方法,否则返回对象本身
(2)如果提供了第二个参数,应用这个函数过滤器,传入函数过滤器的值是第(1)步返回的值
(3)对第(2)步的值进行相应的序列化
(4)如果提供了第三个参数,执行相应的格式化

var book = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011,
  nothing: undefined,
  toJSON: function(){
    return this.title;
  }
};
 
var jsonBook = JSON.stringify(book, function(key, value) {
  switch (key) {
    case "title":
      return 'Ajax';
    default:
      return value;
  }
}, 4);
console.log(jsonBook);  
// "JavaScript" 因为优先执行了toJSON(),没有执行第二个参数

(2)解析: JSON.parse() 方法

JSON.parse()是将JSON字符串解析为原生JS值的函数,在上述代码前提下,继续执行以下代码

var bookCopy = JSON.parse(jsonBook);

最终得到的是以下结果

var bookCopy = {
  title: 'JavaScript',
  authors: ["Nichoas C. Zakas"],
  edition: 3,
  year: 2011
}

码农解释】为什么数据发生了变化?虽然JSON.parse()JSON.stringify()在大多数情况下都是相反的过程,但当原始数据存在JSON.stringify()主动忽略的属性时,得到的JSON字符串中已经不存在这部分属性,因此执行parse()得到的原生JS与原数据不同。

注意对比以下情形

var bookCopy = book;

  此时得到的bookCopy的结果与原数据一致。相信很多人能明白这一点,因为JS对象是应用类型,这种方式赋值只是复制了一份原数据的引用到bookCopy上,也就是说,修改book数据的同时也会影响到bookCopy上的值。
  但由JSON.stringify()序列化和JSON.parse()解析后,得到的bookCopybook是没有任何关系的两个变量,他们的值有可能相同,有可能不同,这取决于原数据是否含有主动忽略的属性。

JOSN.parse()不允许用逗号作为结尾

// both throw a SyntaxError
JSON.parse("[1, 2, 3, 4, ]"); 
JSON.parse('{"foo" : 1, }');


1. 解析选项:
JSON.parse()也可以接受另一个参数,该参数是个函数,将在每个键值对上调用,为区别序列化的第二个参数,该函数被称为还原函数(reviver)。但实际上他们的签名是相同的,接受两个参数,keyvalue,而且都需要返回一个值。

  需要注意的是,如果还原函数返回undefined,则表示要从结果中删除相应的键,如果返回其他值,则将该值插入到结果中。

$ JSON的几个使用场景

  JSON格式的数据在一些场景的使用中,如果直接传输会出现异常,此时就需要我们先对数据做一次序列化,否则很可能在访问的时候出现[object,object]

(1) JSON 在 localStorage中的使用

  一些时候,你想存储用户创建的一个对象,并且,即使在浏览器被关闭后仍能恢复该对象。

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});
// setItem前要先使用 JSON.stringify 转换为 JSON 字符串
localStorage.setItem('session', JSON.stringify(session));
// 然后通过JSON.parse()取数据
var restoredSession = JSON.parse(localStorage.getItem('session'));

(2) Date.prototype.toJSON()

toJSON() 方法返回Date对象的字符串形式。

var event = new Date('August 19, 1975 23:15:30 UTC');

var jsonDate = event.toJSON();

console.log(jsonDate);
// expected output: 1975-08-19T23:15:30.000Z

console.log(new Date(jsonDate).toUTCString());
// expected output: Tue, 19 Aug 1975 23:15:30 GMT

$ JSON的深拷贝【难点】

  先明白两个概念: 什么是深拷贝(深复制),什么是浅拷贝(浅复制)?对于值类型,深拷贝与浅拷贝没什么区别,关键是对于引用类型的数据结构

浅复制(Shallow Copy) —- 对于引用类型,只复制了指向堆内存中的地址引用,并没有开辟新的栈空间。对于浅复制的两个变量,任何一个的值发生了改变,另一个也会随着改变。

深复制(Deep Copy) —- 开辟了一个新的栈空间用来创建一个原始字段的内容相同的对象。拷贝前后的两个变量完全相互独立,修改其中一个对象的属性值不会影响另一个对象的属性值。

@ 深拷贝的实现办法

1. 通过递归问解析解决 (推荐)
//深复制,要想达到深复制就需要用递归
function deepCopy(o,c){
  var c = c || {}
  for (var i in o) {
    if (typeof o[i] === 'object') {
      //要考虑深复制问题了
      if (o[i].constructor === Array) {
        //这是数组
        c[i] =[]
      } else {
        //这是对象
        c[i] = {}
      }
      deepCopy(o[i],c[i])
    } else {
      c[i] = o[i]
    }
  }
  return c
}

// 测试代码
var result = {name:'result'}
var test= {
  nation : '中国',
  skincolr :'yellow',
  deep: {
    childDeep: {
      names: ['judy', 'cici']
    }
  },
  func: function() {
    return "func return";
  }
}
result = deepCopy(test, result)
console.dir(result)
  1. 通过JSON的解析解决 (只能使用在不存在被主动忽略属性的对象中)
var test= {
  nation : '中国',
  skincolr :'yellow',
  deep: {
    childDeep: {
      names: ['judy', 'cici']
    }
  }
}

var result = JSON.parse(JSON.stringify(test));
console.log(result);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,093评论 0 3
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,211评论 0 4
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 昨天我终于止步于中区比赛啦!是该高兴呢?还是悲哀呢?都不是,我感觉自己可以长长地舒口气了!终于可以美美地睡一...
    batheny阅读 435评论 6 2
  • 最近看楚乔传,感慨下原来中国封建王朝几千年,朝代更替,门阀争权,历史就有,当今社会,亦然如此。为人处世,要聪明,有...
    Nick_k哥阅读 269评论 0 0