社区文档构建中,欢迎编辑。 社区答疑群(非官方):717421103,点点小课堂(腾讯会议):5696651544

全站通知:

帮助:使用JSON动态更新页面

来自WIKI实验室WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索

须知

作者会尽量采用通俗的语言来介绍,但涉及到核心的部分依然需要你懂得一定的编程及前端知识。
本文中的代码块,绿色底表示正确,红色底表示错误,黄色底表示警告。

前言

什么是JSON

JSON是JS通用的外部数据储存格式,其形式是对象或数组,形如:
{
	"键": "值"
}
[
	"值"
]
为了便于展示,本文大多数情况会采用对象作为示例,但你应该知道数组也是JSON中重要的形式,按照需求设计结构会让你事半功倍。
其实,数组本就是一种特殊的对象,数组的键就是元素的位置(index),这里不展开介绍了。

规范

根对象/根数组

一个JSON必须有且只有一个最外层对象/数组,就如同开篇的两个样例。
"无根的键": "值",
{
	"键": "值"
}
{
	"键": "值"
}

分隔

键与值之间必须使用英文冒号:进行分隔。
一条内容之间必须使用英文逗号,进行分隔,最后一条内容后不能加逗号。
{
	"键1": "值1",
	"键2": "值2"
}
{
	"键1": "值1"
	"键2": "值2"
}
{
	"键1": "值1",
	"键2": "值2",
}

格式化

JSON对换行、缩进(空格/制表符)均没有要求:
{
         "键1":
		 "值1",
	"键2":        "值2"
}
{
"键1":"值1",
"键2":"值2"
}
请不要学习以上两种写法。
{"键1":"值1","键2":"值2"}
以上便是压缩后的JSON。

(对象的)键

必须是使用英文双引号""包裹的字符串
{
	'单引号': "值"
}
在一个对象内,不能有同名的键:
{
	"同名的键": "值1",
	"同名的键": "值2"
}

值是可以重复的。
{
	"键1": "值",
	"键2": "值",
	"键3": "值"
}
值可以储存以下数据:

字符串

{
	"字符串": "你好,世界。"
}
字符串值和键一样,必须使用英文双引号""包裹:
{
	"字符串": '单引号,不行。'
}

数字

{
	"整数": 5,
	"浮点数": 3.1416,
	"科学计数": 6.02e23
}
{
	"其他进制表示法": 0xf2dede,
	"极限值": Infinity
}

真假(布尔)

{
	"布尔真": true,
	"布尔假": false
}

空(null)

{
	"空": null
}

对象/数组

{
	"子对象": {
		"子对象的键": "子对象的值"
	},
	"子数组": [
		"子数组的值1",
		"子数组的值2"
	]
}
基于此特点,可以设计出复杂的结构来储存大量的数据:
{
	"table": {
		"id": null,
		"class": ["wikitable","test"]
	},
	"headLine": [
		"序号",
		"事件",
		"发生时间",
		"是否完成"
	],
	"lines": [
		[
			1,
			"起床",
			1660608000000,
			true
		],
		[
			2,
			"吃饭",
			1660608900000,
			true
		],
		[
			3,
			"上车",
			1660609800000,
			true
		],
		[
			4,
			"上班",
			1660613400000,
			true
		],
		[
			5,
			"下班",
			1660645800000,
			false
		]
	]
}

事实上,JSON可以不需要根,仅单独存一个值,但这几乎只在极少数情况下有用,本文之后也不会介绍。
其实,字符串、数字、布尔、空,也是特殊的对象,这里也不展开介绍了。
"值"
1
false
null

与JS对象的关系

在阅读本节内容之前,请你先复述这句话无数遍:
JSON对于JS来说是字符串。
JSON 对于JS来说 是字符串。
JSON 对于 JS 来说是 字符串。
如果你无法理解这句话的含义,可能会对后续的阅读产生障碍。
当然,你也可以尝试带着疑问继续阅读。

互相转化

JSON可以通过JS的函数JSON.parse转为JS对象。
JSON.parse('{"键":"值"}')
//输出:一个JS对象
这个动作称为解析

JS对象可以通过JS的函数JSON.stringify转为JSON。
let obj={
	"键": "值"
}
JSON.stringify(obj)
//输出:'{"键":"值"}'
这个动作称为序列化

不同点

JSON无法储存JS对象中的函数,序列化一个含有函数的JS对象时,会被舍弃:
let obj={
	键:"值",
	函数:function(){
		return "你好,世界。"
	}
}
JSON.stringify(obj)
//输出:'{"键":"值"}'
总体来说,序列化一个JS对象时,只有 $值 中提到的类型可以保留,其他的可能变成空(null),可能被舍弃,也可能变成空对象:
let obj={
	极限:Infinity,
	标签:Symbol(),
	DOM元素:document.createElement("div")
}
JSON.stringify(obj)
//输出:'{"极限":null,"DOM元素":{}}'

正文

在谷歌浏览器等现代浏览器中均可以在控制台中直接运行JS,如果方便的话,请你按下F12打开控制台,跟着作者一步步操作。
使用函数console.log输出结果:
console.log("你好,世界。")
//输出:你好,世界。
本文后续的所有的内容不再像 $与JS对象的关系 中直接输出结果了,而是通过console.log输出结果。

读取JSON

JSON最常见的形式有两种:静态文件(*.json)和请求结果(HttpRequest)。
前端实际操作都是通过让JS访问一个地址(URL),获取到了JSON的字符串,再将其解析为JS对象后使用。

访问地址(异步)

作者已经准备好了一个JSON,就放在 https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw 这里,你可以先通过浏览器直接看到它的内容。

原生JS

let url = "https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
let ajax = new XMLHttpRequest()
ajax.open("GET", url)
ajax.send()
let obj = JSON.parse(ajax.responseText)
console.log(obj)
//报错
为什么在控制台中没有结果?
原来这是一个异步操作。顾名思义,异步是不同步的,JS不会立刻执行异步操作中的代码,而是等待来告诉它“你可以继续了”才会继续执行。
在上述代码中,就是服务器。服务器把JSON发过来需要时间,虽然bilibili的服务器很快很快,但在你敲下回车的一瞬间,JS就已经执行完了同步的部分,服务器还没快到这种程度呀。
同步中的ajax.responseText还是空,JSON.parse(ajax.responseText)等同于JSON.parse(null),这便是报错的来源。
综上所述,不能直接同步执行JSON.parse(ajax.responseText),而是告诉JS,“在服务器把JSON发过来之后,再执行JSON.parse(ajax.responseText)”:
let url = "https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
let ajax = new XMLHttpRequest()
ajax.open("GET", url)
ajax.send()
ajax.onreadystatechange = function () {
	if (ajax.readyState === 4) {
		if (ajax.status === 200) {
			let obj = JSON.parse(ajax.responseText)
			console.log(obj)
		}
	}
}
//输出:那个JSON解析后的JS对象

jQuery

jQuery是一个JS的库,功能很强大,BWIKI安装了,就正好利用一下吧。
console.log($)
//输出:ƒ (selector,context){return new jQuery.fn.init(selector,context);}
如果你看到了上述结果,说明jQuery是生效的。
console.log($)
//输出:ƒ $() { [native code] }
如果你看到了上述结果,说明jQuery没有生效。

let url = "https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
$.getJSON(url, function (obj) {
	console.log(obj)
})
//输出:那个JSON解析后的JS对象
注意:$.getJSON会自动将JSON解析为JS对象,无需手动使用JSON.parse
因为jQuery无论是获取JSON还是操作DOM元素都实在是太方便了,而且BWIKI也能用,所以本文后续所有的读取JSON都会用jQuery来完成。

查值

JS将JSON解析后,可以像查JS对象一样查JSON的值。

基本

对象:
使用英文点.组成对象.键,或者英文方括号[]包裹键的字符串对象["键"],来读取键对应的值:
let json = '{"key1":"值1","key2":"值2"}'
let obj = JSON.parse(json)
console.log(obj.key1)	//或:console.log(obj["key1"])
//输出:值1

数组:
使用英文方括号[]包裹值所在的位置数组[位置],来读取对应位置的值:
let json = '["零","一","二"]'
let arr = JSON.parse(json)
console.log(arr[0])
//输出:零
注意,数组的第一个元素的位置是0,最后一个元素的位置是长度-1
或许你听过这个段子:程序员数数都是从0开始数的。

来点复杂的:
let json = `{
	"arr": [
		"零",
		"一",
		{
			"inner": "二"
		}
	]
}`
let obj = JSON.parse(json)
console.log(obj.arr[2].inner)
//输出:二

遍历

对象:
形如for (let 键 in 对象) {},让JS访问对象中的所有内容,并执行{}中的动作:
let json = `{
	"键1": "值1",
	"键2": "值2",
	"键3": "值3"
}`
let obj = JSON.parse(json)
for (let key in obj) {
	console.log(key)
	console.log(obj[key])	//请思考一下这里为什么不能用obj.key。
}
//输出:键1
//输出:值1
//输出:键2
//输出:值2
//输出:键3
//输出:值3

数组:
形如for (let 位置 = 0; 位置 < 数组.length; 位置++) {},让JS访问数组中的所有内容,并执行{}中的动作:
let json = '["零","一","二"]'
let arr = JSON.parse(json)
for (let i = 0; i < arr.length; i++) {
	console.log(arr[i])
}
//输出:零
//输出:一
//输出:二

让JSON按照你的想法展示给用户

现在,是时候正式阅读一下这个JSON了:https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw
相信你不难看出,这个JSON描述了一个表格:
  • 表格本身拥有class="wikitable test"
  • 表格拥有1个标题行,标题行有4个单元格,内容分别是“序号”、“事件”、“发生时间”、“是否完成”;
  • 表格拥有5个内容行,描述了标题行中对应的东西。
就像这样:
序号 事件 发生时间 是否完成
1 起床 1660608000000 true
2 吃饭 1660608900000 true
3 上车 1660609800000 true
4 上班 1660613400000 true
5 下班 1660645800000 false

不要使用WIKI文本

通过WIKI文本写出上面这个表格是很简单的
{|class="wikitable test"
|-
!序号||事件||发生时间||是否完成
|-
|1||起床||1660608000000||true
|-
|2||吃饭||1660608900000||true
|-
|3||上车||1660609800000||true
|-
|4||上班||1660613400000||true
|-
|5||下班||1660645800000||false
|}
但,JS看不懂WIKI文本,它只能看懂HTML:
<table class="wikitable test">
	<tr>
		<th>序号</th>
		<th>事件</th>
		<th>发生时间</th>
		<th>是否完成
		</th>
	</tr>
	<tr>
		<td>1</td>
		<td>起床</td>
		<td>1660608000000</td>
		<td>true
		</td>
	</tr>
	<tr>
		<td>2</td>
		<td>吃饭</td>
		<td>1660608900000</td>
		<td>true
		</td>
	</tr>
	<tr>
		<td>3</td>
		<td>上车</td>
		<td>1660609800000</td>
		<td>true
		</td>
	</tr>
	<tr>
		<td>4</td>
		<td>上班</td>
		<td>1660613400000</td>
		<td>true
		</td>
	</tr>
	<tr>
		<td>5</td>
		<td>下班</td>
		<td>1660645800000</td>
		<td>false
		</td>
	</tr>
</table>

DOM操作

获取

↓这里放了一个<div>元素,它拥有id="testDiv1"
↑你现在肯定看不见它,因为它没有任何内容,也没有任何样式:
<div id="testDiv1"></div>
看不见不代表不存在,你现在可以通过JS来发现它:
再次强调,接下来本文使用的均为jQuery库支持的写法。
let div=$("#testDiv1")
console.log(div)
//输出:jQuery.fn.init [div#testDiv1]
尝试往这个<div>元素中写一句话:
let div=$("#testDiv1")
div.append("你好,世界。")
这下就看见它了。

创建

DOM元素中可以包含另一个DOM元素。
先用JS来创建一个DOM元素。
let newDom=$('<span class="newDom">')
console.log(newDom)
//输出:jQuery.fn.init [span.newdom]
JS创建的DOM也可以变化,试试往里面输入一些文字,然后改变一下它的文字颜色和字号
let newDom=$('<span class="newDom">')
newDom.append("你好,世界")
newDom.css({
	"color": "red",
	"font-size": "2em"
})
↑这里放了一个<div>元素:
<div id="testDiv2"></div>
尝试把新的DOM放进上面这个<div>元素:
let newDom=$('<span class="newDom">')
newDom.append("你好,世界。")
newDom.css({
	"color": "red",
	"font-size": "2em"
})
let div=$("#testDiv2")
div.append(newDom)

jQuery还提供一种更便捷的写法:
let newDom=$('<span class="newDom" style="color:red;font-size:2em">你好,世界。</span>')
可以直接将表示DOM元素信息的字符串直接变成DOM元素,对创建一些简单的DOM元素会更加方便。

清空

你好,世界。
↑这里放了一个<div>元素:
<div id="testDiv3">你好,世界。</div>
尝试把上面这个<div>元素清空:
let div=$("#testDiv3")
div.empty()
你看不见它了,但它依然还存在。

实例演示

读取JSON https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw
let url="https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
$.getJSON(url, function (obj) {
	console.log(obj)
})
//输出:那个JSON解析后的JS对象

创建HTML:
let url = "https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
$.getJSON(url, function (obj) {
	let className = obj.table.class.join(" ")
	let html = `<table class="${className}">`
	html += `<tr>`
	for (let i = 0; i < obj.headLine.length; i++) {
		let headLineCell = obj.headLine[i]
		html += `<th>${headLineCell}</th>`
	}
	html += `</tr>`
	for (let i = 0; i < obj.lines.length; i++) {
		let line = obj.lines[i]
		html += `<tr>`
		html += `<td>${line[0]}</td>`
		html += `<td>${line[1]}</td>`
		html += `<td>${line[2]}</td>`
		html += `<td>${line[3]}</td>`
		html += `</tr>`
	}
	html += `</table>`
	console.log(html)
})
//输出:<table class="wikitable test"><tr><th>序号</th><th>事件</th><th>发生时间</th><th>是否完成</th></tr><tr><td>1</td><td>起床</td><td>1660608000000</td><td>true</td></tr><tr><td>2</td><td>吃饭</td><td>1660608900000</td><td>true</td></tr><tr><td>3</td><td>上车</td><td>1660609800000</td><td>true</td></tr><tr><td>4</td><td>上班</td><td>1660613400000</td><td>true</td></tr><tr><td>5</td><td>下班</td><td>1660645800000</td><td>false</td></tr></table>
HTML对缩进也不敏感。

↑这里放了一个<div>元素:
<div id="tableDiv1"></div>
尝试把这段HTML放进上面这个<div>元素:
let url = "https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
$.getJSON(url, function (obj) {
	console.log(obj)
	let className = obj.table.class.join(" ")
	let html = `<table class="${className}">`
	html += `<tr>`
	for (let i = 0; i < obj.headLine.length; i++) {
		let headLineCell = obj.headLine[i]
		html += `<th>${headLineCell}</th>`
	}
	html += `</tr>`
	for (let i = 0; i < obj.lines.length; i++) {
		let line = obj.lines[i]
		html += `<tr>`
		html += `<td>${line[0]}</td>`
		html += `<td>${line[1]}</td>`
		html += `<td>${line[2]}</td>`
		html += `<td>${line[3]}</td>`
		html += `</tr>`
	}
	html += `</table>`
	let div = $("#tableDiv1")
	div.append(html)
})

↑这里放了一个<div>元素:
<div id="tableDiv2"></div>
你可能会希望把“发生时间”这一列换成“YYYY.MM.DD hh:mm:ss”这样的格式,还希望把“是否完成”这一列换成“是”或“否”:
let url = "https://wiki.biligame.com/tools/index.php?title=MediaWiki:JSONcourse.json&action=raw"
$.getJSON(url, function (obj) {
	console.log(obj)
	let className = obj.table.class.join(" ")
	let html = `<table class="${className}">`
	html += `<tr>`
	for (let i = 0; i < obj.headLine.length; i++) {
		let headLineCell = obj.headLine[i]
		html += `<th>${headLineCell}</th>`
	}
	html += `</tr>`
	for (let i = 0; i < obj.lines.length; i++) {
		let line = obj.lines[i]
		html += `<tr>`
		html += `<td>${line[0]}</td>`
		html += `<td>${line[1]}</td>`
		let time = new Date(line[2])
		let YYYY = time.getFullYear()
		let MM = (time.getMonth() + 1 + "").padStart(2, "0")
		let DD = (time.getDate() + "").padStart(2, "0")
		let hh = (time.getHours() + "").padStart(2, "0")
		let mm = (time.getMinutes() + "").padStart(2, "0")
		let ss = (time.getSeconds() + "").padStart(2, "0")
		let timeStr = `${YYYY}.${MM}.${DD} ${hh}:${mm}:${ss}`
		html += `<td>${timeStr}</td>`
		let isDo
		if (line[3]) {
			isDo = "是"
		} else {
			isDo = "否"
		}
		html += `<td>${isDo}</td>`
		html += `</tr>`
	}
	html += `</table>`
	let div = $("#tableDiv2")
	div.empty()
	div.append(html)
})