JS中原生对象 JSON 知识总结(JSON实现对象的深拷贝)

25 篇文章 0 订阅
1 篇文章 0 订阅

第23章 JSON


本章内容

  • 理解 JSON 语法
  • 解析 JSON
  • JSON 序列化

  2006年 Douglas Crockford 在国际互联网工程任务组制定了 JavaScript 对象简谱( JSON, JavaScript Object Notation)标准。实际上,JSON 早在 2001 年就开始使用了。JSON 是 JavaScript 的严格子集,利用 JavaScript 中的几种模式来表示结构化数据。

  理解 JSON 最关键的一点是要把它当成一种数据格式,而不是编程语言。JSON 不属于 JavaScript,它们只是拥有相同的语法而已。很多语言都有解析和序列化 JSON 的内置能力。

23.1、语法

  JSON 语法支持表示 3 种类型的值。

  • 简单值:字符串、数值、布尔值和 null 可以在 JSON 中出现,就像在 JavaScript 中一样。特殊值 undefined 不可以
  • 对象:对象表示有序键/值对。每个值可以是简单值,也可以是复杂类型。
  • 数组:数组表示可以通过数值索引访问的值的有序列表。数组的值可以是任意类型,包括简单值、对象,甚至其他数组。

23.1.1 简单值

  最简单的 JSON 可以是一个数值,数值和字符串等。

4
“Hello World”

  JavaScript 字符串与 JSON 字符串的主要区别是,JSON 字符串必须使用双引号(单引号会导致语法错误)。

  布尔值和 null 本身也是有效的 JSON 值。不过,实践中更多使用 JSON 表示比较复杂的数据结构,其中会包含简单值。

23.1.2 对象

  对象使用与 JavaScript 对象字面量略为不同的方式表示。JavaScript 中的对象字面量:

let person = {
  name: "Nicholas",
  age: 29
};
let object = {		// js中属性名加上引号,等同于上面的效果
  "name": "Nicholas",
  "age" : 29
};

  但 JSON 中的对象必须使用双引号把属性名包围起来。JSON 表示相同的对象的语法是:

{
  "name": "Nicholas",
  "age": 29
}

  与 JavaScript 对象字面量相比,JSON 主要有三处不同。

  • 没有变量声明( JSON 中没有变量)。
  • 最后没有分号(不需要,因为不是 JavaScript 语句)。
  • 引号将属性名包围起来才是有效的 JSON。

  属性的值可以是简单值或复杂数据类型值,后者可以在对象中再嵌入对象,比如:

{
  "name": "Nicholas",
  "age": 29,
  "school": {
    "name": "Merrimack College",
    "location": "North Andover, MA"
  }
}

23.1.3 数组

  数组在 JSON 中使用 JavaScript 的数组字面量形式表示。例如,以下是一个 JavaScript 数组:

let values = [25, "hi", true];

  在 JSON 中可以使用类似语法表示相同的数组,同样,这里没有变量,也没有分号

[25, "hi", true]

  数组和对象可以组合使用,以表示更加复杂的数据结构。即数组中的元素可以是对象,或对象中的属性值可以为数组。

23.2、解析与序列化

23.2.1 JSON 对象

  ECMAScript 5 增加了 JSON 全局对象,正式引入解析 JSON 的能力。这个对象在所有主流浏览器中都得到了支持。JSON 对象有两个方法:stringify()parse() 。这两个方法分别可以将 JavaScript 序列化 JSON 字符串,以及将 JSON 解析为原生 JavaScript 值

  • stringify() 方法:把一个 JavaScript 对象序列化为一个不包含空格缩进 JSON 字符串。
let book = {
    title: "Professional JavaScript",
    authors: [
      "Nicholas C. Zakas",
      "Matt Frisbie"
       ],
    edition: 4,
    year: 2017
  };
 let jsonText = JSON.stringify(book);
 console.log(jsonText);
//{"title":"Professional JavaScript","authors":["Nicholas C. Zakas","Matt Frisbie"],"edition":4,"year":2017}

  在序列化 JavaScript 对象时,所有函数原型成员都会有意地在结果中省略。此外,值为 undefined 的任何属性也会被跳过。最终得到的就是所有实例属性均为有效 JSON 数据类型的表示。

  • parse() 方法:将 JSON 字符串转化为相应的 JavaScript 值。
let bookCopy = JSON.parse(jsonText);

  book 和 bookCopy 是两个完全不同的对象,没有任何关系。但是它们拥有相同的属性和值。如果给 JSON.parse() 传入的 JSON 字符串无效,则会导致抛出错误

23.2.2 序列化选项

  JSON.stringify() 方法除接收序列化的对象参数外,还可以接收两个参数。用于指定其序列化 JavaScript 对象的方式。

  • 第一个参数:序列化的对象(必输

  • 第二个参数:过滤器结果,可以是一个数组或函数(可选

  • 第三个参数:数字或字符串,控制输出字符串缩进和空格(可选

01. 过滤结果
  • 第二个参数是一个数组:那么 JSON.stringify() 返回的结果只会包含该数组中列出的对象属性。
let book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    edition: 4,
    year: 2017
};
let jsonText = JSON.stringify(book, ["title", "edition"]);
console.log(jsonText);  // {"title":"Professional JavaScript","edition":4}
  • 第二个参数是一个函数(称之为替代函数(replacer)):提供的函数接收两个参数:属性名(key)属性值(value)。可以根据这个 key 决定要对相应属性执行什么操作。返回的值就是相应 key 应该包含的结果。注意,返回 undefined 会导致属性被忽略。
let book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    edition: 4,
    year: 2017
};
let jsonText = JSON.stringify(book, (key, value) => {
    switch (key) {
        case "authors":
            return value.join(",")  // 用 ,分隔 authors 属性值
        case "year":
            return 5000;            // year 属性值改为 5000
        case "edition":
            return undefined;       // edition 属性值改为 undefined 所以不显示
        default:
            return value;           // 其他属性直接返回结果
    }
});
console.log(jsonText);
// {"title":"Professional JavaScript","authors":"Nicholas C. Zakas,Matt Frisbie","year":5000}

  【注意📢】第一次调用这个函数实际上会传入空字符串 key,值是 book 对象。函数过滤器会应用到要序列化的对象所包含的所有对象(例如下面实例中的a属性),会递归处理内部的每一个对象。可通过下面的实例调试看到每一步执行细节。

02. 字符串缩进
  • 第三个参数是一个数值:表示每一级缩进的空格数。
let book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    edition: 4,
    year: 2017
};
let jsonText = JSON.stringify(book, null, 4);		// 每级缩进4个空格
console.log(jsonText);
// console.log(jsonText) 输出结果
{
    "title": "Professional JavaScript",
    "authors": [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    "edition": 4,
    "year": 2017
}

  除了缩进,JSON.stringify() 方法还为方便阅读插入了换行符(有缩进就强行换行)。最大缩进值为10,大于10的值会自动设置为10。

  • 第三个参数是一个字符串:JSON 字符串中就会使用这个字符串而不是空格来缩进。
let jsonText = JSON.stringify(book, null, "--" );
// console.log(jsonText) 输出结果
{
--"title": "Professional JavaScript",
--"authors": [
----"Nicholas C. Zakas",
----"Matt Frisbie"
--],
--"edition": 4,
--"year": 2017
}

  使用字符串时同样有 10 个字符的长度限制。如果字符串长度超过 10,则会在第 10 个字符处截断。

03. toJSON() 方法

  如果对象需要在 JSON.stringify() 之上自定义 JSON 序列化,可以在要序列化的对象中添加 toJSON() 方法。如果对象有 toJSON 方法,JSON.stringify() 就会调用对象的 toJSON() 方法,以 toJSON() 方法返回的值为序列化值 。

let book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    edition: 4,
    year: 2017,
    toJSON: function () {		// toJSON 方法
        return this.title;
    }
};
let jsonText = JSON.stringify(book);
console.log(jsonText);	// "Professional JavaScript"

  toJSON() 函数是 JavaScript 构建类时重要的工具。通过这种方式,您可以控制 JavaScript 如何将你的类实例序列化为 json 字符串。

  箭头函数不能用来定义 toJSON() 方法。主要原因是箭头函数的词法作用域是全局作用域,在这种情况下不合适。

  toJSON() 方法可以与过滤函数一起使用,把对象传给 JSON.stringify() 时会执行如下步骤:

  1. 对象toJSON() 方法调用 toJSON() 方法获取实际的值, 否则使用默认的序列化。
  2. 如果提供了第二个参数,则应用过滤。传入过滤函数的值就是第 1 步返回的值。
  3. 第 2 步返回的每个值都会相应地进行序列化。
  4. 如果提供了第三个参数,则相应地进行缩进。

  如下例子结合了三个参数去序列化一个对象。

let book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    edition: 4,
    year: 2017,
    a: {
        b: 11,
        toJSON: function () {		// toJSON 方法
            return {
                rea: 99,
                reb: "nihao",
                rec: {
                    reca: 3,
                }
            }
        }
    }

};
let jsonText = JSON.stringify(book, (key, value) => {
    switch (key) {
        default:
            console.log("key:" + key + " # value:" + value);
            return value;
    }
}, "--");
console.log(jsonText);

输出结果:

23.2.3 解析选项

  JSON.parse() 方法除了接受必输的 JSON 字符串外,还可选接受第二个函数参数,这个函数会针对每个键/ 值对都调用一次。称为还原函数(reviver)。该函数也接收两个参数,属性名(key)和属性值 (value),也需要返回值。

  如果还原函数返回 undefined,则结果中就会删除相应的键。如果返回了其他任何值,则该值就会成为相应键的值插入到结果中。还原函数经常被用于把日期字符串转换为 Date 对象。

let book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas",
        "Matt Frisbie"
    ],
    edition: 4,
    year: 2017,
    releaseDate: new Date(2017, 11, 1)
};
let jsonText = JSON.stringify(book);
console.log(jsonText);	// {"title":"Professional JavaScript","authors":["Nicholas C. Zakas","Matt Frisbie"],"edition":4,"year":2017,"releaseDate":"2017-11-30T16:00:00.000Z"}
let bookCopy = JSON.parse(jsonText,
    (key, value) => key == "releaseDate" ? new Date(value) : value);
console.log(bookCopy.releaseDate.getFullYear());		// 2017

23.3、小结

  JSON 是一种轻量级数据格式,可以方便地表示复杂数据结构。这个格式使用 JavaScript 语法的一个子集表示对象、数组、字符串、数值、布尔值和 null。虽然 XML 也能胜任同样的角色,但 JSON 更简洁,JavaScript 支持也更好。更重要的是,所有浏览器都已经原生支持全局 JSON 对象

  ECMAScript 5 定义了原生 JSON 对象,用于将 JavaScript 对象序列化为 JSON 字符串,以及将 JSON 数组解析为 JavaScript 对象。JSON.stringify()JSON.parse() 方法分别用于实现这两种操作。 这两个方法都有一些选项可以用来改变默认的行为,以实现过滤或修改流程。

23.3.1 原生 JSON 对象常用户对象的深拷贝

对象的深浅拷贝可以参考:JS 中对象的深浅拷贝

let obj1 = {
    a: "a",
    b: [
        "b1",
        "b2"
    ],
    c: true,
    d: 2017,
    e: {
        e1:1,
        e2:{
            e2a:3
        }
    }
};
let obj2 = JSON.parse(JSON.stringify(obj1))
console.log("obj1 === obj2?",obj1 === obj2);
console.log("obj1:",JSON.stringify(obj1));
console.log("obj2:",JSON.stringify(obj2));
obj1.b[0]="b99";
obj2.e.e2.e2a = 99;
console.log("obj1修改了b[0]:",JSON.stringify(obj1));
console.log("obj2修改了e.e2.e2a:",JSON.stringify(obj2));
JSON实现对象深拷贝
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ItDaChuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值