在上篇文章咱們已經瞭解什麼是深拷貝和淺拷貝,也着重介紹了淺拷貝相關的一下實現方法,或者本身實現一個淺拷貝等等。本篇文章主要介紹深拷貝的一種簡單實現方式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
。