演示地址: http://json.imlht.com/vue-json-viewer-demo.html
JSON
是一种轻量级的数据交换格式, 相信大家用得比较多, 平时也用了很多格式化工具, 例如我最常用的 Json.cn, 还有这个 BeJson, 前者清爽无广告, 后者性能给力(有广告), 可以复制下面的 JSON
体验一下:
{
"name": "小明",
"age": 24,
"gender": true,
"height": 1.85,
"weight": null,
"skills": [
{
"PHP": [
"Laravel",
"Composer"
]
},
{
"JavaScript": [
"jQuery",
"Vue",
"React"
]
},
"Golang",
"Python",
"Lua"
]
}
想自己实现一个 JSON
工具, 偷师是必不可少滴. 翻下 Json.cn 的源码, 发现是用 jQuery
写的, 代码量不多, 比较有用的就是缩进填充函数 indent_tab
还有类型判断函数 _typeof
:
function indent_tab(indent_count) {
return (new Array(indent_count + 1)).join(' ');
}
function _typeof(object) {
var tf = typeof object,
ts = _toString.call(object);
return null === object ? 'Null' :
'undefined' == tf ? 'Undefined' :
'boolean' == tf ? 'Boolean' :
'number' == tf ? 'Number' :
'string' == tf ? 'String' :
'[object Function]' == ts ? 'Function' :
'[object Array]' == ts ? 'Array' :
'[object Date]' == ts ? 'Date' : 'Object';
};
当然, 样式我也抄了, 折叠看数组长度这个酷炫的想法也抄了哈哈! 折叠展开的实现可以看下函数 show
和 hide
, 原理比较简单: 折叠的时候把 innerHTML
存进 data-inner
, 展开的时候再写回去:
function hide(obj) {
var data_type = obj.parentNode.getAttribute('data-type');
var data_size = obj.parentNode.getAttribute('data-size');
obj.parentNode.setAttribute('data-inner',obj.parentNode.innerHTML);
if (data_type === 'array') {
obj.parentNode.innerHTML = 'Array[' + data_size + ']';
} else {
obj.parentNode.innerHTML = 'Object{...}';
}
}
function show(obj) {
var innerHtml = obj.parentNode.getAttribute('data-inner');
obj.parentNode.innerHTML = innerHtml;
}
再看看函数 format
: 根据值的类型和缩进层级返回字符串, 如果是 Array
或 Object
, 将会递归调用: format
-> _format_array
-> format
-> _format_object
-> …
function format(object, indent_count) {
var html_fragment = '';
switch (_typeof(object)) {
case 'Null':
0 html_fragment = _format_null(object);
break;
case 'Boolean':
html_fragment = _format_boolean(object);
break;
case 'Number':
html_fragment = _format_number(object);
break;
case 'String':
html_fragment = _format_string(object);
break;
case 'Array':
html_fragment = _format_array(object, indent_count);
break;
case 'Object':
html_fragment = _format_object(object, indent_count);
break;
}
return html_fragment;
};
function _format_null(object) {
return 'null';
}
function _format_boolean(object) {
return '' + object + '';
}
function _format_number(object) {
return '' + object + '';
}
function _format_string(object) {
object = object.replace(/\/g, ">");
if (0 <= object.search(/^http/)) {
object = '' + object + ''
}
return '"' + object + '"';
}
function _format_array(object, indent_count) {
var tmp_array = [];
for (var i = 0,
size = object.length; i < size; ++i) {
tmp_array.push(indent_tab(indent_count) + format(object[i], indent_count + 1));
}
return '[
' + tmp_array.join(',
') + '
' + indent_tab(indent_count - 1) + ']';
}
function _format_object(object, indent_count) {
var tmp_array = [];
for (var key in object) {
tmp_array.push(indent_tab(indent_count) + '"' + key + '":' + format(object[key], indent_count + 1));
}
return '{
' + tmp_array.join(',
') + '
' + indent_tab(indent_count - 1) + '}';
}
了解原理之后, 再回头想想该如何用 Vue.js 实现? 熟悉 Vue
官方文档的人应该会想到官方实例: 树形视图 Example, 它演示了组件的递归使用, 这次派上用场了! 因为格式化的原理是根据值的类型返回特定的字符串, 结合组件化的思想, 我们递归返回组件就可以了.
也就是说, item
父组件包含了 key
和 val
子组件, val
有多种类型, 如果是 Array
或 Object
, 递归展开为 item
组件. 至于为什么叫 val
不叫 value
组件, 因为我强迫症啊哈哈哈! 都是3个字母看起来很顺眼.
OK, 瞎哔哔了这么多, 是时候看代码了. 定义(呸)抄一下缩进字符串和类型判断函数:
// 缩进字符串
var padstr = ' ';
// 返回给定value的类型
function valueType(value) {
var tf = typeof value;
var ts = Object.prototype.toString.call(value);
return value === null ? 'Null' :
'boolean' === tf ? 'Boolean' :
'number' === tf ? 'Number' :
'string' === tf ? 'String' :
'[object Array]' === ts ? 'Array' : 'Object';
}
什么鬼?! 第一个单词 var
, 用 const
啊! 好吧我只是为了说明原理, 所以没有用 ES6/7
等高级特性, 没有 webpack
也没有 npm
, 全部被我撸在一个 html
里了哈哈哈!
组件 key
逻辑比较简单, key
用双引号 "
包起来, 如果是数组的 key
, 那就不渲染. 另外再根据层级填充缩进字符即可:
组件 val
模板复杂了些. 如果是 Array
或 Object
, 判断当前组件的 open
打开状态, 如果为 true
, 渲染折叠 -
图标并递归渲染 item
组件, 否则渲染展开 +
图标, 并根据类型生成折叠后的字符串; 如果是 Null
, String
, Number
或 Boolean
, 渲染带有样式的 span
标签, 如果不是最后一个元素渲染 ,
逗号, 最后再渲染
标签:
item
组件把 key
和 val
组件合起来就OK了:
根组件没有 key
, 所以 #vm
里面只有一个 val
组件. current-depth
为 0
, 表示根节点, 无缩进层级, max-depth
表示初始化之后展示到第几层, 这里设为 3
:
#vm
提供了 getJson
和 setJson
接口, getJson
返回当前实例的 JSON
对象, 看起来没什么卵用, 但它治好了我的强迫症; setJson
可以动态改变实例的 JSON
对象, 妈妈我再也不用 F5 刷新了, 按下键盘 F12
进入开发者工具的控制台, 然后 vm.setJson(...)
就可以看效果了.
目前没发现有 bug
, 如果有的话麻烦告知, 谢谢! 性能上, 解析比较简单的 JSON
倒是可以, 层级多的或者体积大的 JSON
会特别慢, 可能消耗在递归上. 有兴趣的可以动手测试一下, 欢迎交流.
分享一下自用的 JSON 解析工具, 绿色无广告, 解析速度飞快: http://json.imlht.com/index.html. 看了下时间, 凌晨1点! 睡觉睡觉! 晚安世界!
文章来源于本人博客,发布于 2017-12-21,原文链接:https://imlht.com/archives/88/