解构赋值
概述
JavaScript
中最常用的两种数据结构是Object
和Array
- 对象是一种根据键存储数据的实体
- 数组是一种直接存储数据的有序列表
- 但是,当我们把它们传递给函数时,函数可能不需要整个对象/数组,而只需要其中一部分
- 解构赋值 是一种特殊的语法,它使我们可以将数组或对象“拆包”至一系列变量中
- 解构操作对那些具有很多参数和默认值等的函数也很奏效
数组解构
- 将数组解构到变量中的例子:
1 2 3 4 5 6 7 8 9 10 |
// 我们有一个存放了名字和姓氏的数组 let arr = ["John", "Smith"] // 解构赋值 // 设置 firstName = arr[0] // 以及 surname = arr[1] let [firstName, surname] = arr; alert(firstName); // John alert(surname); // Smith |
- 与
split
函数(或其他返回值为数组的函数)结合使用时,看起来更优雅:
1 2 3 |
let [firstName, surname] = "John Smith".split(' '); alert(firstName); // John alert(surname); // Smith |
注意
- “解构”并不意味着“破坏”
- 这种语法被叫做“解构赋值”,是因为它“拆开”了数组或对象,将其中的各元素复制给一些变量
- 原来的数组或对象自身没有被修改
- 换句话说,解构赋值只是写起来简洁一点。以下两种写法是等价的:
1 2 3 |
// let [firstName, surname] = arr; let firstName = arr[0]; let surname = arr[1]; |
- 忽略使用逗号的元素
- 可以通过添加额外的逗号来丢弃数组中不想要的元素:
- 代码中,数组的第二个元素被跳过了,第三个元素被赋值给了
title
变量 - 数组中剩下的元素也都被跳过了(因为在这没有对应给它们的变量)
1 2 3 4 |
// 不需要第二个元素 let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert( title ); // Consul |
- 等号右侧可以是任何可迭代对象
- 实际上,我们可以将其与任何可迭代对象一起使用,而不仅限于数组:
- 这种情况下解构赋值是通过迭代右侧的值来完成工作的
- 这是一种用于对在
=
右侧的值上调用for..of
并进行赋值的操作的语法糖
1 2 |
let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); |
- 赋值给等号左侧的任何内容
- 可以在等号左侧使用任何“可以被赋值的”东西
- 例如,一个对象的属性:
1 2 3 4 5 |
let user = {}; [user.name, user.surname] = "John Smith".split(' '); alert(user.name); // John alert(user.surname); // Smith |
- 与
.entries()
方法进行循环操作- 可以将
.entries()
方法与解构语法一同使用,来遍历一个对象的“键—值”对:
- 可以将
1 2 3 4 5 6 7 8 9 |
let user = { name: "John", age: 30 }; // 使用循环遍历键—值对 for (let [key, value] of Object.entries(user)) { alert(`${key}:${value}`); // name:John, then age:30 } |
1 2 3 4 5 6 7 8 9 10 |
// 用于 `Map` 的类似代码更简单,因为 `Map` 是可迭代的: let user = new Map(); user.set("name", "John"); user.set("age", "30"); // Map 是以 [key, value] 对的形式进行迭代的,非常便于解构 for (let [key, value] of user) { alert(`${key}:${value}`); // name:John, then age:30 } |
- 交换变量值的技巧
- 使用解构赋值来交换两个变量的值是一个著名的技巧:
- 创建了一个由两个变量组成的临时数组,并且立即以颠倒的顺序对其进行了解构赋值
1 2 3 4 5 6 7 |
let guest = "Jane"; let admin = "Pete"; // 让我们来交换变量的值:使得 guest = Pete,admin = Jane [guest, admin] = [admin, guest]; alert(`${guest} ${admin}`); // Pete Jane(成功交换!) |
其余的 ‘…’
- 通常,如果数组比左边的列表长,那么“其余”的数组项会被省略
- 例如,这里只取了两项,其余的就被忽略了:
1 2 3 4 5 |
let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar // 其余数组项未被分配到任何地方 |
- 如果我们还想收集其余的数组项 —— 我们可以使用三个点
"..."
来再加一个参数以获取其余数组项:rest
的值就是数组中剩下的元素组成的数组
1 2 3 4 5 6 |
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; // rest 是包含从第三项开始的其余数组项的数组 alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 |
1 2 |
let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; // 现在 titles = ["Consul", "of the Roman Republic"] |
默认值
- 如果数组比左边的变量列表短,这里不会出现报错。缺少对应值的变量都会被赋
undefined
:
1 2 3 4 |
let [firstName, surname] = []; alert(firstName); // undefined alert(surname); // undefined |
- 如果我们想要一个“默认”值给未赋值的变量,我们可以使用
=
来提供:
1 2 3 4 5 |
// 默认值 let [name = "Guest", surname = "Anonymous"] = ["Julius"]; alert(name); // Julius(来自数组的值) alert(surname); // Anonymous(默认值被使用了) |
- 默认值可以是更加复杂的表达式,甚至可以是函数调用
- 不过,这些表达式或函数只会在这个变量未被赋值的时候才会被计算
- 注意:
prompt
将仅针对缺失值(surname
)运行
1 2 3 4 5 |
// 只会提示输入姓氏 let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; alert(name); // Julius(来自数组) alert(surname); // 你输入的值 |
对象解构
- 解构赋值同样适用于对象
- 在等号右侧是一个已经存在的对象,我们想把它拆分到变量中
- 等号左侧包含了对象相应属性的一个类对象“模式(
pattern
)” - 在最简单的情况下,等号左侧的就是
{...}
中的变量名列表
1 |
let {var1, var2} = {var1:…, var2:…} |
1 2 3 4 5 6 7 8 9 10 11 |
let options = { title: "Menu", width: 100, height: 200 }; let {title, width, height} = options; alert(title); // Menu alert(width); // 100 alert(height); // 200 |
1 2 3 4 |
// 变量的顺序并不重要,下面这个代码也是等价的: // 改变 let {...} 中元素的顺序 let {height, width, title} = { title: "Menu", height: 200, width: 100 } |
- 等号左侧的模式(
pattern
)可以更加复杂,指定属性和变量之间的映射关系- 如果我们想把一个属性赋值给另一个名字的变量,比如把
options.width
属性赋值给名为w
的变量,那么我们可以使用冒号来设置变量名称: - 冒号的语法是“从对象中什么属性的值:赋值给哪个变量”
- 例子中,属性
width
被赋值给了w
,属性height
被赋值给了h
,属性title
被赋值给了同名变量
- 如果我们想把一个属性赋值给另一个名字的变量,比如把
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
let options = { title: "Menu", width: 100, height: 200 }; // { sourceProperty: targetVariable } let {width: w, height: h, title} = options; // width -> w // height -> h // title -> title alert(title); // Menu alert(w); // 100 alert(h); // 200 |
- 对于可能缺失的属性,我们可以使用
"="
设置默认值,如下所示:- 就像数组或函数参数一样,默认值可以是任意表达式甚至可以是函数调用
- 它们只会在未提供对应的值时才会被计算/调用
1 2 3 4 5 6 7 8 9 |
let options = { title: "Menu" }; let {width = 100, height = 200, title} = options; alert(title); // Menu alert(width); // 100 alert(height); // 200 |
1 2 3 4 5 6 7 8 |
let options = { title: "Menu" }; let {width = prompt("width?"), title = prompt("title?")} = options; alert(title); // Menu alert(width); // (prompt 的返回值) |
- 还可以将冒号和等号结合起来:
1 2 3 4 5 6 7 8 9 |
let options = { title: "Menu" }; let {width: w = 100, height: h = 200, title} = options; alert(title); // Menu alert(w); // 100 alert(h); // 200 |
- 如果我们有一个具有很多属性的复杂对象,那么我们可以只提取所需的内容:
1 2 3 4 5 6 7 8 9 10 |
let options = { title: "Menu", width: 100, height: 200 }; // 仅提取 title 作为变量 let { title } = options; alert(title); // Menu |
剩余模式pattern
- 如果对象拥有的属性数量比我们提供的变量数量还多,该怎么办?
- 可以只取其中的某一些属性,然后把“剩余的”赋值到其他地方吗?
- 可以使用剩余模式(
pattern
),与数组类似 - 一些较旧的浏览器不支持此功能(例如
IE
,可以使用Babel
对其进行polyfill
),但可以在现代浏览器中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let options = { title: "Menu", height: 200, width: 100 }; // title = 名为 title 的属性 // rest = 存有剩余属性的对象 let {title, ...rest} = options; // 现在 title="Menu", rest={height: 200, width: 100} alert(rest.height); // 200 alert(rest.width); // 100 |
注意
- 不使用
let
时的陷阱- 上面的示例中,变量都是在赋值中通过正确方式声明的:
let {…} = {…}
- 当然,我们也可以使用已有的变量,而不用
let
,但这里有一个陷阱
- 上面的示例中,变量都是在赋值中通过正确方式声明的:
- 以下代码无法正常运行:
- 问题在于
JavaScript
把主代码流(即不在其他表达式中)的{...}
当做一个代码块
- 问题在于
1 2 3 4 |
let title, width, height; // 这一行发生了错误 {title, width, height} = {title: "Menu", width: 200, height: 100}; |
1 2 3 4 5 6 |
{ // 一个代码块 let message = "Hello"; // ... alert( message ); } |
- 这样的代码块可以用于对语句分组,如下所示:
- 因此,这里
JavaScript
假定我们有一个代码块,这就是报错的原因。我们需要解构它 - 为了告诉
JavaScript
这不是一个代码块,我们可以把整个赋值表达式用括号(...)
包起来:
- 因此,这里
1 2 3 4 5 6 |
let title, width, height; // 现在就可以了 ({title, width, height} = {title: "Menu", width: 200, height: 100}); alert( title ); // Menu |
嵌套解构
- 如果一个对象或数组嵌套了其他的对象和数组,我们可以在等号左侧使用更复杂的模式(
pattern
)来提取更深层的数据 - 下面的代码中,
options
的属性size
是另一个对象,属性items
是另一个数组- 赋值语句中等号左侧的模式(
pattern
)具有相同的结构以从中提取值:
- 赋值语句中等号左侧的模式(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
let options = { size: { width: 100, height: 200 }, items: ["Cake", "Donut"], extra: true }; // 为了清晰起见,解构赋值语句被写成多行的形式 let { size: { // 把 size 赋值到这里 width, height }, items: [item1, item2], // 把 items 赋值到这里 title = "Menu" // 在对象中不存在(使用默认值) } = options; alert(title); // Menu alert(width); // 100 alert(height); // 200 alert(item1); // Cake alert(item2); // Donut |
智能函数参数
- 有时,一个函数有很多参数,其中大部分的参数都是可选的
- 对用户界面来说更是如此。想象一个创建菜单的函数。它可能具有宽度参数,高度参数,标题参数和项目列表等
- 下面是实现这种函数的一个很不好的写法:
1 2 3 |
function showMenu(title = "Untitled", width = 200, height = 100, items = []) { // ... } |
- 实际开发中,记忆如此多的参数的位置是一个很大的负担
- 常集成开发环境(
IDE
)会尽力帮助我们,特别是当代码有良好的文档注释的时候 - 但是…… 另一个问题就是,在大部分的参数只需采用默认值的情况下,调用这个函数时会需要写大量的
undefined
- 这太难看了。而且,当我们处理更多参数的时候可读性会变得很差
- 常集成开发环境(
1 2 |
// 在采用默认值就可以的位置设置 undefined showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) |
- 解构赋值可以解决这些问题
- 可以用一个对象来传递所有参数,而函数负责把这个对象解构成各个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 我们传递一个对象给函数 let options = { title: "My menu", items: ["Item1", "Item2"] }; // ……然后函数马上把对象解构成变量 function showMenu({title = "Untitled", width = 200, height = 100, items = []}) { // title, items – 提取于 options, // width, height – 使用默认值 alert( `${title} ${width} ${height}` ); // My Menu 200 100 alert( items ); // Item1, Item2 } showMenu(options); |
- 也可以使用带有嵌套对象和冒号映射的更加复杂的解构:
- 完整语法和解构赋值是一样的:
- 对于参数对象,属性
incomingProperty
对应的变量是varName
,默认值是defaultValue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let options = { title: "My menu", items: ["Item1", "Item2"] }; function showMenu({ title = "Untitled", width: w = 100, // width goes to w height: h = 200, // height goes to h items: [item1, item2] // items first element goes to item1, second to item2 }) { alert( `${title} ${w} ${h}` ); // My Menu 100 200 alert( item1 ); // Item1 alert( item2 ); // Item2 } showMenu(options); |
1 2 3 4 |
function({ incomingProperty: varName = defaultValue ... }) |
- 注意,这种解构假定了
showMenu()
函数确实存在参数- 如果我们想让所有的参数都使用默认值,那我们应该传递一个空对象:
- 也可以通过指定空对象
{}
为整个参数对象的默认值来解决这个问题:
1 2 3 |
showMenu({}); // 不错,所有值都取默认值 showMenu(); // 这样会导致错误 |
1 2 3 4 5 |
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) { alert( `${title} ${width} ${height}` ); } showMenu(); // Menu 100 200 |
日期和时间
Date
- 该对象存储日期和时间,并提供了日期/时间的管理方法
创建
- 调用
new Date()
来创建一个新的Date
对象
1 2 |
let now = new Date(); alert( now ); // 显示当前的日期/时间 |
- 在调用时可以带有一些参数,如下所示:
- 创建一个
Date
对象,其时间等于1970
年1
月1
日UTC+0
之后经过的毫秒数(1/1000
秒) - 传入的整数参数代表的是自
1970-01-01 00:00:00
以来经过的毫秒数,该整数被称为 时间戳
- 创建一个
1 2 3 4 5 6 7 |
// 0 表示 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); // 现在增加 24 小时,得到 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 ); |
- 在
01.01.1970
之前的日期带有负的时间戳,例如:
1 2 3 |
// 31 Dec 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 ); |
- 如果只有一个参数,并且是字符串,那么它会被自动解析
- 算法与
Date.parse
所使用的算法相同
- 算法与
1 2 3 4 5 6 7 8 |
let date = new Date("2017-01-26"); alert(date); // 未指定具体时间,所以假定时间为格林尼治标准时间(GMT)的午夜零点 // 并根据运行代码时的用户的时区进行调整 // 因此,结果可能是 // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) // 或 // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) |
new Date(year, month, date, hours, minutes, seconds, ms)
year
应该是四位数。为了兼容性,也接受2
位数,并将其视为19xx
,例如98
与1998
相同,但强烈建议始终使用4
位数month
计数从0
(一月)开始,到11
(十二月)结束date
是当月的具体某一天,如果缺失,则为默认值1
- 如果
hours/minutes/seconds/ms
缺失,则均为默认值0
- 时间度量最大精确到 1 毫秒(1/1000 秒):
1 2 |
new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00 new Date(2011, 0, 1); // 同样,时分秒等均为默认值 0 |
1 2 |
let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567 |
getFullYear
- 获取年份(
4
位数)
getMonth
- 月份,
0
到11
getDate
- 获取当月的具体日期,从
1
到31
,这个方法名称可能看起来有些令人疑惑
getHours,getMinutes,getSeconds,getMilliseconds
- 获取相应的时间组件
getDay
- 获取一周中的第几天,从
0
(星期日)到6
(星期六) - 第一天始终是星期日,在某些国家可能不是这样的习惯
getUTCFullYear,getUTCMonth,getUTCDay
- 如果你当地时区相对于
UTC
有偏移,那么下面代码会显示不同的小时数:
1 2 3 4 5 6 7 8 |
// 当前日期 let date = new Date(); // 当地时区的小时数 alert( date.getHours() ); // 在 UTC+0 时区的小时数(非夏令时的伦敦时间) alert( date.getUTCHours() ); |
getTime
- 返回日期的时间戳 —— 从
1970-1-1 00:00:00 UTC+0
开始到现在所经过的毫秒数
getTimezoneOffset
- 返回
UTC
与本地时区之间的时差,以分钟为单位:
1 2 3 |
// 如果你在时区 UTC-1,输出 60 // 如果你在时区 UTC+3,输出 -180 alert( new Date().getTimezoneOffset() ); |
注意
- 不是
getYear()
,而是getFullYear()
设置日期
setFullYear(year, [month\], [date])
setMonth(month, [date\])
setDate(date)
setHours(hour, [min\], [sec], [ms])
setMinutes(min, [sec\], [ms])
setSeconds(sec, [ms\])
setMilliseconds(ms)
setTime(milliseconds)
(使用自1970-01-01 00:00:00 UTC+0
以来的毫秒数来设置整个日期)
1 2 3 4 5 6 7 |
let today = new Date(); today.setHours(0); alert(today); // 日期依然是今天,但是小时数被改为了 0 today.setHours(0, 0, 0, 0); alert(today); // 日期依然是今天,时间为 00:00:00。 |
自动校准(Autocorrection
)
- 自动校准 是
Date
对象的一个非常方便的特性。我们可以设置超范围的数值,它会自动校准
1 2 |
let date = new Date(2013, 0, 32); // 32 Jan 2013 ?!? alert(date); // ……是 1st Feb 2013! |
- 超出范围的日期组件将会被自动分配
- 假设我们要在日期 “
28 Feb 2016
” 上加2
天 - 结果可能是 “
2 Mar
” 或 “1 Mar
”,因为存在闰年 - 但是我们不需要考虑这些,只需要直接加
2
天,剩下的Date
对象会帮我们处理:
- 假设我们要在日期 “
1 2 3 4 |
let date = new Date(2016, 1, 28); date.setDate(date.getDate() + 2); alert( date ); // 1 Mar 2016 |
- 这个特性经常被用来获取给定时间段后的日期。例如,我们想获取“现在
70
秒后”的日期:
1 2 3 4 |
let date = new Date(); date.setSeconds(date.getSeconds() + 70); alert( date ); // 显示正确的日期信息 |
- 还可以设置 0 甚至可以设置负值。例如:
1 2 3 4 5 6 7 |
let date = new Date(2016, 0, 2); // 2016 年 1 月 2 日 date.setDate(1); // 设置为当月的第一天 alert( date ); date.setDate(0); // 天数最小可以设置为 1,所以这里设置的是上一月的最后一天 alert( date ); // 31 Dec 2015 |
日期转数字,日期差值
- 当
Date
对象被转化为数字时,得到的是对应的时间戳,与使用date.getTime()
的结果相同:
1 2 |
let date = new Date(); alert(+date); // 以毫秒为单位的数值,与使用 date.getTime() 的结果相同 |
- 有一个重要的副作用:日期可以相减,相减的结果是以毫秒为单位时间差
- 这个作用可以用于时间测量:
1 2 3 4 5 6 7 8 9 10 |
let start = new Date(); // 开始测量时间 // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } let end = new Date(); // 结束测量时间 alert( `The loop took ${end - start} ms` ); |
Date.now
- 如果我们仅仅想要测量时间间隔,我们不需要
Date
对象- 有一个特殊的方法
Date.now()
,它会返回当前的时间戳 - 相当于
new Date().getTime()
,但它不会创建中间的Date
对象
因此它更快,而且不会对垃圾回收造成额外的压力
- 有一个特殊的方法
1 2 3 4 5 6 7 8 9 10 |
let start = Date.now(); // 从 1 Jan 1970 至今的时间戳 // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } let end = Date.now(); // 完成 alert( `The loop took ${end - start} ms` ); // 相减的是时间戳,而不是日期 |
基准测试(Benchmarking
)
- 在对一个很耗
CPU
性能的函数进行可靠的基准测试(Benchmarking
)时,我们需要谨慎一点- 例如,我们想判断以下两个计算日期差值的函数:哪个更快?
- 这种性能测量通常称为“基准测试(
benchmark
)
1 2 3 4 5 6 7 8 9 |
// 我们有 date1 和 date2,哪个函数会更快地返回两者的时间差? function diffSubtract(date1, date2) { return date2 - date1; } // or function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } |
- 首先想到的方法可能是连续运行两者很多次,并计算所消耗的时间之差
- 就这个例子而言,函数过于简单,所以我们必须执行至少
100000
次 - 看起来使用
getTime()
这种方式快得多,这是因为它没有进行类型转换,对引擎优化来说更加简单
- 就这个例子而言,函数过于简单,所以我们必须执行至少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function diffSubtract(date1, date2) { return date2 - date1; } function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } function bench(f) { let date1 = new Date(0); let date2 = new Date(); let start = Date.now(); for (let i = 0; i < 100000; i++) f(date1, date2); return Date.now() - start; } alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' ); alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' ); |
对字符串调用 Date.parse
- 可以从一个字符串中读取日期
- 字符串的格式应该为:
YYYY-MM-DDTHH:mm:ss.sssZ
,其中:YYYY-MM-DD
—— 日期:年-月-日- 字符
"T"
是一个分隔符 HH:mm:ss.sss
—— 时间:小时,分钟,秒,毫秒- 可选字符
'Z'
为+-hh:mm
格式的时区。单个字符Z
代表UTC+0
时区
- 简短形式也是可以的,比如
YYYY-MM-DD
或YYYY-MM
,甚至可以是YYYY
Date.parse(str)
调用会解析给定格式的字符串,并返回时间戳(自1970-01-01 00:00:00
起所经过的毫秒数)- 如果给定字符串的格式不正确,则返回
NaN
1 2 3 |
let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); alert(ms); // 1327611110417 (时间戳) |
- 可以通过时间戳来立即创建一个
new Date
对象:- 通常使用
new Date(timestamp)
通过时间戳来创建日期,并可以使用date.getTime()
将现有的Date
对象转化为时间戳
- 通常使用
1 2 3 |
let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); alert(date); |
JSON
概述
- 假设我们有一个复杂的对象,我们希望将其转换为字符串,以通过网络发送,或者只是为了在日志中输出它
1 2 3 4 5 6 7 8 9 10 |
let user = { name: "John", age: 30, toString() { return `{name: "${this.name}", age: ${this.age}}`; } }; alert(user); // {name: "John", age: 30} |
- 但在开发过程中,会新增一些属性,旧的属性会被重命名和删除
- 每次更新这种
toString
都会非常痛苦
- 每次更新这种
方法
JavaScript
提供了如下方法:JSON.stringify
将对象转换为JSON
JSON.parse
将JSON
转换回对象
JSON.stringify
- 用于对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
let student = { name: 'John', age: 30, isAdmin: false, courses: ['html', 'css', 'js'], spouse: null }; let json = JSON.stringify(student); alert(typeof json); // we've got a string! alert(json); /* JSON 编码的对象: { "name": "John", "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], "spouse": null } */ |
- 用于原始类型
Objects
{ ... }
Arrays
[ ... ]
Primitives
:
strings
,
numbers
,
boolean
values
true/false
,
null
1 2 3 4 5 6 7 8 9 |
// 数字在 JSON 还是数字 alert( JSON.stringify(1) ) // 1 // 字符串在 JSON 中还是字符串,只是被双引号扩起来 alert( JSON.stringify('test') ) // "test" alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] |
JSON
是语言无关的纯数据规范,因此一些特定于JavaScript
的对象属性会被JSON.stringify
跳过
1 2 3 4 5 6 7 8 9 |
let user = { sayHi() { // 被忽略 alert("Hello"); }, [Symbol("id")]: 123, // 被忽略 something: undefined // 被忽略 }; alert( JSON.stringify(user) ); // {}(空对象) |
- 支持嵌套对象转换,并且可以自动对其进行转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
let meetup = { title: "Conference", room: { number: 23, participants: ["john", "ann"] } }; alert( JSON.stringify(meetup) ); /* 整个结构都被字符串化了 { "title":"Conference", "room":{"number":23,"participants":["john","ann"]}, } */ |
- 注意
- 不得有循环引用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let room = { number: 23 }; let meetup = { title: "Conference", participants: ["john", "ann"] }; meetup.place = room; // meetup 引用了 room room.occupiedBy = meetup; // room 引用了 meetup JSON.stringify(meetup); // Error: Converting circular structure to JSON |
排除和转换:replacer
stringify
语法value
要编码的值replacer
要编码的属性数组或映射函数function(key, value)
space
用于格式化的空格数量
1 |
let json = JSON.stringify(value[, replacer, space]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup 引用了 room }; room.occupiedBy = meetup; // room 引用了 meetup alert( JSON.stringify(meetup, ['title', 'participants']) ); // {"title":"Conference","participants":[{},{}]} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup 引用了 room }; room.occupiedBy = meetup; // room 引用了 meetup alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) ); /* { "title":"Conference", "participants":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */ |
replacer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup 引用了 room }; room.occupiedBy = meetup; // room 引用了 meetup alert( JSON.stringify(meetup, function replacer(key, value) { alert(`${key}: ${value}`); return (key == 'occupiedBy') ? undefined : value; })); /* key:value pairs that come to replacer: : [object Object] title: Conference participants: [object Object],[object Object] 0: [object Object] name: John 1: [object Object] name: Alice place: [object Object] number: 23 occupiedBy: [object Object] */ |
space
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
let user = { name: "John", age: 25, roles: { isAdmin: false, isEditor: true } }; alert(JSON.stringify(user, null, 2)); /* 两个空格的缩进: { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */ /* 对于 JSON.stringify(user, null, 4) 的结果会有更多缩进: { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */ |
自定义toJSON
- 像
toString
进行字符串转换,对象也可以提供toJSON
方法来进行 JSON 转换。如果可用,JSON.stringify
会自动调用它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
let room = { number: 23 }; let meetup = { title: "Conference", date: new Date(Date.UTC(2017, 0, 1)), room }; alert( JSON.stringify(meetup) ); /* { "title":"Conference", "date":"2017-01-01T00:00:00.000Z", // (1) "room": {"number":23} // (2) } */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
let room = { number: 23, toJSON() { return this.number; } }; let meetup = { title: "Conference", room }; alert( JSON.stringify(room) ); // 23 alert( JSON.stringify(meetup) ); /* { "title":"Conference", "room": 23 } */ |
JSON.parse
- 解码
JSON
字符串
1 2 3 4 5 6 |
// 字符串化数组 let numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); alert( numbers[1] ); // 1 |
1 2 3 4 5 |
let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; let user = JSON.parse(userData); alert( user.friends[1] ); // 1 |
使用 reviver
- 想象一下,我们从服务器上获得了一个字符串化的
meetup
对象
1 2 |
// title: (meetup title), date: (meetup date) let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; |
- 现在我们需要对它进行 反序列(
deserialize
),把它转换回JavaScript
对象meetup.date
的值是一个字符串,而不是Date
对象JSON.parse
怎么知道应该将字符串转换为Date
呢
1 2 3 4 5 |
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str); alert( meetup.date.getDate() ); // Error! |
- 让我们将
reviver
函数传递给JSON.parse
作为第二个参数,该函数按照“原样”返回所有值,但是date
会变成Date
:
1 2 3 4 5 6 7 8 |
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str, function(key, value) { if (key == 'date') return new Date(value); return value; }); alert( meetup.date.getDate() ); // 现在正常运行了! |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let schedule = `{ "meetups": [ {"title":"Conference","date":"2017-11-30T12:00:00.000Z"}, {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"} ] }`; schedule = JSON.parse(schedule, function(key, value) { if (key == 'date') return new Date(value); return value; }); alert( schedule.meetups[1].date.getDate() ); // 正常运行了! |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!