在上篇文章咱们已经了解什么是深拷贝和浅拷贝,也着重介绍了浅拷贝相关的一下实现方法,或者本身实现一个浅拷贝等等。本篇文章主要介绍深拷贝的一种简单实现方式JSON.parse/JSON.stringify
。在日常开发时咱们能够常常的看到别人使用,或者在不那么了解深拷贝时本身也有使用。编程
JSON.parse/JSON.stringify实际上是用来序列化 JSON 格式的数据的方法。那它为何能实现一个简单的深拷贝呢? 在执行JSON.stringify
会把咱们的一个对象序列化为字符串,而字符串是基本类型。 再经过JSON.parse
时,把字符串类型反序列化为对象,这个时候由于在反序列化以前它是基本类型因此他会指向一个新的地址,在反序列化以后它是一个对象会再分配内存空间。 因此JSON.parse/JSON.stringify能够实现一个简单的深拷贝。json
本篇文章首先实现一个JSON.stringify/JSON.parse
,下一篇文章实现一个比较完整的深拷贝。数组
直接上代码验证一下函数
// 声明原始对象
var old = {
name: "old",
attr: {
age: 18,
sex: "man"
},
title: ["M1", "P6"]
};
// 声明一个新对象,经过SON.parse/JSON.stringify 实现对原始对象深拷贝,而且赋值给新对象
var newValue = JSON.parse(JSON.stringify(old));
console.log(newValue); // {name: "old", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}
// 修改原始对象的name,新对象不受影响
old.name = "new";
console.log(newValue); // {name: "old", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}
console.log(old); // {name: "new", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}
// 修改原始对象的引用类型,新对象也不受影响
old.attr.age = 20;
console.log(newValue); // {name: "old", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}
console.log(old); // {name: "new", attr: {age: 20, sex: "man"}, title: [['M1', 'P6']]}
复制代码
实际上是不是觉得用这个就能够了,并无什么问题啊,下面咱们就来一点点揭开它的面纱。测试
其实JSON.parse/JSON.stringify
仍是有不少局限性,大体以下:ui
undefined
Symbol
function
,也会忽略直接上代码验证spa
// 声明一个包含undefined、null、symbol、function的对象
var oldObj = {
name: "old",
age: undefined,
sex: Symbol("setter"),
title: function() {},
lastName: null
};
var newObj = JSON.parse(JSON.stringify(oldObj));
// 能够看到会忽略undefined、symbol、function的对象
console.log(newObj); // {name: "old", lastName: null}
var firstObj = {
name: "firstObj"
};
firstObj.newKey = firstObj;
// Converting circular structure to JSON
var newFirstObj = JSON.parse(JSON.stringify(firstObj));
复制代码
若是循环引用报错以下图所示: prototype
一个生成任意深度、广度对象方法。code
function createData(deep, breadth) {
var data = {};
var temp = data;
for (var i = 0; i < deep; i++) {
temp = temp["data"] = {};
for (var j = 0; j < breadth; j++) {
temp[j] = j;
}
}
return data;
}
复制代码
验证JSON.stringify
递归爆栈regexp
JSON.stringify(createData(10000));
// VM97994:1 Uncaught RangeError: Maximum call stack size exceeded
复制代码
String
与Boolean
、Number
、null
undefined
、symbol
、function
实现目标
// 数据类型判断
function getType(attr) {
let type = Object.prototype.toString.call(attr);
let newType = type.substr(8, type.length - 9);
return newType;
}
// 转换函数
function StringIfy(obj) {
// 若是是非object类型 or null的类型直接返回 原值的String
if (typeof obj !== "object" || getType(obj) === null) {
return String(obj);
}
// 声明一个数组
let json = [];
// 判断当前传入参数是对象仍是数组
let arr = obj ? getType(obj) === "Array" : false;
// 循环对象属性
for (let key in obj) {
// 判断属性是否在对象自己上
if (obj.hasOwnProperty(key)) {
// 获取属性而且判断属性值类型
let item = obj[key];
// 若是为object类型递归调用
if (getType(obj) === "Object") {
// consoarrle.log(item)
item = StringIfy(item);
}
// 拼接数组字段
json.push((arr ? '"' : '"' + key + '": "') + String(item) + '"');
}
}
console.log(arr, String(json));
// 转换数组字段为字符串
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
// 测试代码
StringIfy({ name: { name: "abc" } }); // "{"name": "{"name": "abc"}"}"
StringIfy([1, 2, 4]); // "["1","2","4"]"
复制代码
在上面代码中咱们基本的JSON序列化
,能够序列化引用类型和基本类型。
我说的区分的类型,是JSON.stringify
再序列化时,像Number
、Boolean
、null
它是不会加上双引号
的,只有在String
类型或者Object中的key
才会带双引号
。
// 。。。省略代码
// 转换函数
function StringIfy(obj) {
// 。。。省略代码
let IsQueto =
getType(item) === "Number" ||
getType(item) === "Boolean" ||
getType(item) === "Null"
? ""
: '"';
// 拼接数组字段
json.push((arr ? IsQueto : '"' + key + '": "') + String(item) + IsQueto);
// 。。。省略代
}
// 测试代码
StringIfy({ name: { name: "abc" } }); // "{"name": "{"name": "abc"}"}"
StringIfy([1, 2, 4]); // "[1,2,4]"
复制代码
Symbol|Function|Undefined
if (/Symbol|Function|Undefined/.test(getType(item))) {
delete obj[key];
continue;
}
let test = {
name: 'name',
age: undefined,
func: function () {},
sym: Symbol('setter')
};
let newTest = StringIfy(test);
console.log(newTest); // {"name": "name"}
复制代码
if (item === obj) {
console.error(new TypeError("Converting circular structure to JSON"));
return false;
}
复制代码
JSON.stringify
它能够传入三个参数。
语法: JSON.stringify(value[, replacer [, space]])
参数
value
:将要序列化成 一个 JSON 字符串的值。replacer(可选)
:若是该参数是一个函数
,则在序列化过程当中,被序列化的值的每一个属性都会
通过该函数的转换和处理
;若是该参数是一个数组
,则只有包含
在这个数组中的属性名
才会被序列化到最终的 JSON
字符串中;space
:指定缩进用的空白字符串
,用于美化输出(pretty-print)
;这里主要记录replacer
的实现,首先咱们要知道replacer
参数的使用才能本身实现。
replacer实例
let oJson = {
name: "oJson",
age: 20,
sex: "man",
calss: "one"
};
JSON.stringify(oJson, ["sex", "name"]); // "{"sex":"man","name":"oJson"}"
// 两个参数 key/value的形式
JSON.stringify(oJson, function(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}); // "{"age":20}"
复制代码
实现
// 转换函数
function StringIfy(obj, replacer) {
// 若是是非object类型 or null的类型直接返回 原值的String
if (typeof obj !== "object" || getType(obj) === null) {
return String(obj);
}
// 声明一个数组
let json = [];
// 判断当前传入参数是对象仍是数组
let arr = obj ? getType(obj) === "Array" : false;
// 循环对象属性
for (let key in obj) {
// 判断属性是否可枚举
if (obj.hasOwnProperty(key)) {
// console.log(key, item);
// 获取属性而且判断属性值类型
let item = obj[key];
// <!-------修改开始-------!>
let flag = true;
// 处理第二个参数
if (replacer) {
// 判断第二个参数类型
switch (getType(replacer)) {
case "Function":
// 若是为函数执行
flag = replacer(key, item);
break;
case "Array":
// 若是为数组
flag = replacer.indexOf(key) !== -1;
break;
}
}
// 判断返回结果
if (!flag) {
continue;
}
// <!-------修改结束-------!>
if (item === obj) {
console.error(new TypeError("Converting circular structure to JSON"));
return false;
}
if (/Symbol|Function|Undefined/.test(getType(item))) {
delete obj[key];
continue;
}
// 若是为object类型递归调用
if (getType(item) === "Object") {
// consoarrle.log(item)
item = StringIfy(item);
}
let IsQueto =
getType(item) === "Number" ||
getType(item) === "Boolean" ||
getType(item) === "Null"
? ""
: '"';
// 拼接数组字段
json.push((arr ? IsQueto : '"' + key + '": "') + String(item) + IsQueto);
}
}
console.log(arr, String(json));
// 转换数组字段为字符串
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
复制代码
咱们新增第二个参数的处理,第三个参数暂时就忽滤了,主要用于设置space
的,下面直接测试上面的代码:
let test = {
name: "name",
age: undefined,
func: function() {},
sym: Symbol("setter"),
age: 30,
sex: 'man'
};
console.log(StringIfy(test, ['name', 'sex'])); // {"name": "name","sex": "man"}
let newTest = StringIfy(test, function (key, value) {
if (typeof value === 'string') {
return undefined;
}
return value;
});
console.log(newTest); // {"age": "30}
复制代码
到此StringIfy
的实现到此结束。
到此本身实现JSON.stringify
到此结束了,完整代码以下:
// 数据类型判断
function getType(attr) {
let type = Object.prototype.toString.call(attr);
let newType = type.substr(8, type.length - 9);
return newType;
}
// 转换函数
function StringIfy(obj) {
// 若是是非object类型 or null的类型直接返回 原值的String
if (typeof obj !== "object" || getType(obj) === null) {
return String(obj);
}
// 声明一个数组
let json = [];
// 判断当前传入参数是对象仍是数组
let arr = obj ? getType(obj) === "Array" : false;
// 循环对象属性
for (let key in obj) {
// 判断属性是否在对象自己上
if (obj.hasOwnProperty(key)) {
// console.log(key, item);
// 获取属性而且判断属性值类型
let item = obj[key];
if (item === obj) {
console.error(new TypeError("Converting circular structure to JSON"));
return false;
}
if (/Symbol|Function|Undefined/.test(getType(item))) {
delete obj[key];
continue;
}
// 若是为object类型递归调用
if (getType(item) === "Object") {
// consoarrle.log(item)
item = StringIfy(item);
}
let IsQueto =
getType(item) === "Number" ||
getType(item) === "Boolean" ||
getType(item) === "Null"
? ""
: '"';
// 拼接数组字段
json.push((arr ? IsQueto : '"' + key + '": "') + String(item) + IsQueto);
}
}
console.log(arr, String(json));
// 转换数组字段为字符串
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
let aa = StringIfy([1, 2, 4]);
let test = {
name: "name",
age: undefined,
func: function() {},
sym: Symbol("setter")
};
let newTest = StringIfy(test);
console.log(aa, newTest);
var firstObj = {
name: "firstObj"
};
firstObj.newKey = firstObj;
StringIfy(firstObj);
复制代码
有两种方法实现parse
效果,第一种是eval
实现,另外一种是Function
实现,下面直接开始。
function ParseJson(opt) {
return eval("(" + opt + ")");
}
let aa = StringIfy([1, 2, 4]);
ParseJson(aa); // [1, 2, 4]
let test = {
name: "name",
age: undefined,
func: function() {},
sym: Symbol("setter")
};
let newTest = StringIfy(test);
console.log(ParseJson(newTest)); // {name: "name"}
复制代码
能够看到上面的代码能够实现基本的反序列化。
避免在没必要要的状况下使用 eval,eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。若是你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。
function ParseJsonTwo(opt) {
return new Function("return " + opt)();
}
let aa = StringIfy([1, 2, 4]);
ParseJson(aa); // [1, 2, 4]
let test = {
name: "name",
age: undefined,
func: function() {},
sym: Symbol("setter")
};
let newTest = StringIfy(test);
console.log(ParseJson(newTest)); // {name: "name"}
复制代码
eval
与 Function
都有着动态编译js代码
的做用,可是在实际的编程中并不推荐使用。
它会执行 JS 代码,有 XSS 漏洞。
若是你只想记这个方法,就得对参数 json 作校验。
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(
json.replace(rx_two, "@").replace(rx_three, "]").replace(rx_four, "")
);
) {
var obj = ParseJson(json); // ParseJson(json) or ParseJsonTwo(json)
}
复制代码
其实不管在何时都不太推荐eval
和function
,由于它很容形成入侵。 若是有兴趣能够去看一下JSON.parse 三种实现方式,它有涉及到递归实现,状态机实现,讲的也不错。
本篇文章主要讲解了JSON.parse/JSON.stringify
是怎么实现的深拷贝,而且深刻了解一下JSON.parse/JSON.stringify
在深拷贝上的实现,其实还有怎么加速JSON
序列化的速度,会在另外一篇文章中讲解。最后本身也简单实现了一个ParseJson/StringIfy
。