摘要:深入理解規則字符串化并非嚴格意義上的強制類型轉換,但其中涉及的相關規則基本類型值的字符串化規則為轉換為,轉換為,轉換為。如果對象有自己的方法,字符串化時就會調用該方法并使用其返回值。將對象強制類型轉換為是通過抽象操作來完成的。
[TOC]
序言最近在看《你所不知道的javascript》[中卷]一書,第一部分是類型和語法。本文是基于這部分的產物。在強制類型轉換->抽象值操作-> toString 部分,其中對工具函數 JSON.stringify(..) 將 JSON 對象序列化為字符串部分介紹進行了詳細的介紹,而自己之前對 JSON.stringify(..) 認識也比較淺。
JSON.stringify() 不論是在面試還是工作中(對象的深拷貝、json 字符串序列化)都是重點,總是能看到它的身影。所以針對這個知識點記錄整理一下。
語法參考MDN
JSON.stringify(value[, replacer [, space]])參數
value
將要序列化成 一個JSON 字符串的值。
這是第一個參數,應該都不陌生,最常用的也是這個。其他兩個基本用不到。
一般傳入一個對象。但是不僅僅如此,還可以傳入其他值哦。
replacer | 可選
可以三種類型的值:
函數,在序列化過程中,被序列化的值的每個屬性都會經過該函數的轉換和處理
數組,只有包含在這個數組中的屬性名才會被序列化到最終的 JSON 字符串中
null或者未提供,對象所有的屬性都會被序列化
一般情況下,我們都不傳,按第3種方式處理。
space | 可選
指定縮進用的空白字符串,用于美化輸出。
可以指定三種類型的值:
數字,代表有多少的空格。上限為10,該值若小于1,則意味著沒有空格。
字符串,字符串的前十個字母,該字符串將被作為空格。
null或者未提供,將沒有空格。
一般情況下,我們都不傳,按第3種方式處理。
返回值一個表示給定值的 json 字符串。
深入理解 ToString 規則JSON 字符串化并非嚴格意義上的強制類型轉換,但其中涉及 ToString 的相關規則:
基本類型值的字符串化規則為:null 轉換為 "null",undefined 轉換為 "undefined",true 轉換為 "true"。
數字的字符串化則遵循通用規則,變成字符串數字,其中對極小和極大的數字使用指數形式:
// 1.07 連續乘以七個 1000 var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000; // 七個1000一共21位數字 a.toString(); // "1.07e21"
對普通對象來說,除非自行定義,否則 toString()(Object.prototype.toString())返回
內部屬性 [[Class]] 的值,如 "[object Object]"。
如果對象有自己的 toString() 方法,字符串化時就會調用該方法并使用其返回值。
將對象強制類型轉換為 string 是通過 ToPrimitive 抽象操作來完成的。
補充:
[[Class]]:所有 typeof 返回值為 "object" 的對象(如數組)都包含一個內部屬性 [[Class]](可以把它看作一個內部的分類,而非傳統的面向對象意義上的類)。這個屬性無法直接訪問,一般通過 Object.prototype.toString(..) 來查看。
Object.prototype.toString.call( [1,2,3] ); // "[object Array]" Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]" 上例中,數組的內部 [[Cl
ToPrimitive:為了將值轉換為相應的基本類型值,抽象操作 ToPrimitive 會首先(通過內部操作 DefaultValue)檢查該值是否有 valueOf() 方法。
如果有并且返回基本類型值,就使用該值進行強制類型轉換。如果沒有就使用 toString()的返回值(如果存在)來進行強制類型轉換。
如果 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
對大多數簡單值來說,JSON 字符串化和 toString() 的效果基本相同,只不過序列化的結果總是字符串:
JSON.stringify( 42 ); // "42" JSON.stringify( "42" ); // ""42"" (含有雙引號的字符串) JSON.stringify( null ); // "null" JSON.stringify( true ); // "true"json 序列化為字符串時,需要注意的點:
轉換值如果有toJSON()方法,該方法定義什么值將被序列化。
非數組對象的屬性不能保證以特定的順序出現在序列化后的字符串中。
布爾值、數字、字符串的包裝對象在序列化過程中會自動轉換成對應的原始值。
undefined、任意的函數以及 symbol 值,在序列化過程中
出現在非數組對象的屬性值中時,會被忽略(包括屬性名)
出現在數組中時,會被轉換成 null(以保證單元位置不變)。
函數、undefined被多帶帶轉換時,會返回undefined,如 JSON.stringify(function(){}) or JSON.stringify(undefined).
對包含循環引用的對象(對象之間相互引用,形成無限循環)執行此方法,會拋出錯誤。
所有以 symbol 為屬性鍵的屬性都會被完全忽略掉,即便 replacer 參數中強制指定包含了它們。
Date日期調用了toJSON()將其轉換為了string字符串(同Date.toISOString()),因此會被當做字符串處理。
NaN和Infinity格式的數值及null都會被當做null。
其他類型的對象,包括Map/Set/weakMap/weakSet,僅會序列化可枚舉的屬性。
會拋棄對象的 constructor。即 JSON.parse(JSON.stringify(obj))后得到的對象,不管這個對象原來的構造函數是什么,在深拷貝之后都會變成 Object。
JSON.stringify({}); // "{}" JSON.stringify(true); // "true" JSON.stringify("foo"); // ""foo"" JSON.stringify([1, "false", false]); // "[1,"false",false]" JSON.stringify({ x: 5 }); // "{"x":5}" JSON.stringify({x: 5, y: 6}); // "{"x":5,"y":6}" JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]" JSON.stringify({x: undefined, y: Object, z: Symbol("")}); // "{}" JSON.stringify([undefined, Object, Symbol("")]); // "[null,null,null]" JSON.stringify({[Symbol("foo")]: "foo"}); // "{}" JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]); // "{}" JSON.stringify( {[Symbol.for("foo")]: "foo"}, function (k, v) { if (typeof k === "symbol"){ return "a symbol"; } } ); // undefined // 不可枚舉的屬性默認會被忽略: JSON.stringify( Object.create( null, { x: { value: "x", enumerable: false }, y: { value: "y", enumerable: true } } ) ); // "{"y":"y"}" // 序列化,然后反序列化后丟失 constructor function Animation (name) { this.name = name; } var dog = new Animation("小白"); console.log(dog.constructor); // ? Animation (name) { this.name = name; } var obj = JSON.parse(JSON.stringify(dog)); console.log(obj.constructor); // ? Object() { [native code] }安全的 json 值
所有安全的 JSON 值都可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指能夠呈現為有效 JSON 格式的值。
不安全的 JSON 值:undefined、function、symbol(ES6+)和包含循環引用的對象都不符合 JSON 結構標準,支持 JSON 的語言無法處理它們。例如:
JSON.stringify( undefined ); // undefined JSON.stringify( function(){} ); // undefined JSON.stringify( [1,undefined,function(){},4] ); // "[1,null,null,4]" JSON.stringify( { a:2, b:function(){} } ); // "{"a":2}"將不安全的 json 轉換成安全的 json
方式1:toJSON
如果對象中定義了 toJSON() 方法,JSON 字符串化時會首先調用該方法,然后用它的返回值來進行序列化。
如果要對含有非法 JSON 值的對象做字符串化,或者對象中的某些值無法被序列化時,就需要定義 toJSON() 方法來返回一個安全的 JSON 值。例如:
var o = { }; var a = { b: 42, c: o, d: function(){} }; // 在a中創建一個循環引用 o.e = a; // 循環引用在這里會產生錯誤 // JSON.stringify( a ); // 自定義的JSON序列化 a.toJSON = function() { // 序列化僅包含b return { b: this.b }; }; JSON.stringify( a ); // "{"b":42}"
toJSON() 應該“返回一個能夠被字符串化的安全的 JSON 值”,而不是“返回一個 JSON 字符串”。
方式2:向 JSON.stringify(..) 傳遞一個可選參數 replacer
可選參數 replacer,可以是數組或者函數,用來指定對象序列化過程中哪些屬性應該被處理,哪些應該被排除,和 toJSON() 很像。
如果 replacer 是一個數組,那么它必須是一個字符串數組,其中包含序列化要處理的對象
的屬性名稱,除此之外其他的屬性則被忽略。
作為函數,它有兩個參數,鍵(key)值(value)都會被序列化。
如果返回一個 Number, 轉換成相應的字符串被添加入JSON字符串。
如果返回一個 String, 該字符串作為屬性值被添加入JSON。
如果返回一個 Boolean, "true" 或者 "false"被作為屬性值被添加入JSON字符串。
如果返回任何其他對象,該對象遞歸地序列化成JSON字符串,對每個屬性調用replacer方法。除非該對象是一個函數,這種情況將不會被序列化成JSON字符串。
如果返回undefined,該屬性值不會在JSON字符串中輸出。
注意: 不能用replacer方法,從數組中移除值(values),如若返回undefined或者一個函數,將會被null取代。
所以如果要忽略某個鍵就返回 undefined,否則返回指定的值。舉例:
var a = { b: 42, c: "42", d: [1,2,3] }; JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}" JSON.stringify( a, function(k,v){ if (k !== "c") return v; } ); // "{"b":42,"d":[1,2,3]}"
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7}; function replacer(key, value) { if (typeof value === "string") { return undefined; } return value; } // 函數 var jsonString = JSON.stringify(foo, replacer); // {"week":45,"month":7} // 數組 JSON.stringify(foo, ["week", "month"]); // "{"week":45,"month":7}", 只保留“week”和“month”屬性值。
方式3:利用一些工具(比如JSON-js),主要處理 循環引用問題。可參考:
JSON-js解決對象循環引用問題
decycle和retrocycle實現
JSON-js是老外寫的一個對JSON處理的小工具,其中的decycle和retrocycle是專門用來破除/恢復這種循環結構的。基本用法如下:
let a={name:"aaa",link:""} let b={name:"bbb",link:""} a.link=b; b.link=a; /*decycle*/ JSON.stringify(JSON.decycle(a)); /*結果*/ "{"name":"aaa","link":{"name":"bbb","link":{"$ref":"$"}}}"
可以看到,破解循環后確實沒有報錯,但是出現了$ref:"$"這樣的代碼,這種標志表示識別除了循環引用,其中$ref為固定的,右邊的"$..."表示它循環引用的部分,單個$為頂層對象。
美化序列化后的字符串JSON.string 還有一個可選參數 space,用來指定輸出的縮進格式。
正整數時,是指定每一級縮進的字符數,最多10個空格
字符串時,是最前面的十個字符被用于每一級的縮進:
var a = { b: 42, c: "42", d: [1,2,3] }; // 數字 JSON.stringify( a, null, 3 ); /* "{ "b": 42, "c": "42", "d": [ 1, 2, 3 ] }" */ // 字符串 JSON.stringify( a, null, "-----" ); /* "{ -----"b": 42, -----"c": "42", -----"d": [ ----------1, ----------2, ----------3 -----] }" */反序列化:JSON.parse(..)
參考MDN
語法:
JSON.parse(text[, reviver])
參數:
text:要被解析成JavaScript值的字符串。
reviver(可選):轉換器, 如果傳入該參數(函數),可以用來修改解析生成的原始值,調用時機在parse函數返回之前。
返回值:Object類型, 對應給定JSON文本的對象/值
reviver 參數和 JSON.stringify 的第二個參數 replacer,原理差不多。具體為:
解析值本身以及它所包含的所有屬性,會按照一定的順序(從最最里層的屬性開始,一級級往外,最終到達頂層,也就是解析值本身)分別的去調用 reviver 函數,在調用過程中,當前屬性所屬的對象會作為 this 值,當前屬性名和屬性值會分別作為第一個和第二個參數傳入 reviver 中。
如果 reviver 返回 undefined,則當前屬性會從所屬對象中刪除,如果返回了其他值,則返回的值會成為當前屬性新的屬性值。
當遍歷到最頂層的值時,傳入 reviver 函數的參數會是空字符串 ""(因為此時已經沒有真正的屬性)和當前的解析值(有可能已經被修改過了),當前的 this 值會是 {"": 修改過的解析值},在編寫 reviver 函數時,要注意到這個特例。
函數的遍歷順序依照:從最內層開始,按照層級順序,依次向外遍歷
舉例
JSON.parse("{"p": 5}", function (k, v) { if(k === "") return v; // 如果到了最頂層,則直接返回屬性值, return v * 2; // 否則將屬性值變為原來的 2 倍。 }); // { p: 10 } JSON.parse("{"1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}}", function (k, v) { console.log(k); // 輸出當前的屬性名,從而得知遍歷順序是從內向外的, // 最后一個屬性名會是個空字符串。 return v; // 返回原始屬性值,相當于沒有傳遞 reviver 參數。 }); // 1 2 4 6 5 3 ""
注意:不允許用逗號作為結尾
// both will throw a SyntaxError JSON.parse("[1, 2, 3, 4, ]"); JSON.parse("{"foo" : 1, }");原生 js 實現
var myJson = { parse: function (jsonStr) { return (new Function("return " + jsonStr))(); }, stringify: function (jsonObj) { var result = "", curVal; if (jsonObj === null) { return String(jsonObj); } switch (typeof jsonObj) { case "number": case "boolean": return String(jsonObj); case "string": return """ + jsonObj + """; case "undefined": case "function": return undefined; } switch (Object.prototype.toString.call(jsonObj)) { case "[object Array]": result += "["; for (var i = 0, len = jsonObj.length; i < len; i++) { curVal = JSON.stringify(jsonObj[i]); result += (curVal === undefined ? null : curVal) + ","; } if (result !== "[") { result = result.slice(0, -1); } result += "]"; return result; case "[object Date]": return """ + (jsonObj.toJSON ? jsonObj.toJSON() : jsonObj.toString()) + """; case "[object RegExp]": return "{}"; case "[object Object]": result += "{"; for (i in jsonObj) { if (jsonObj.hasOwnProperty(i)) { curVal = JSON.stringify(jsonObj[i]); if (curVal !== undefined) { result += """ + i + "":" + curVal + ","; } } } if (result !== "{") { result = result.slice(0, -1); } result += "}"; return result; case "[object String]": return """ + jsonObj.toString() + """; case "[object Number]": case "[object Boolean]": return jsonObj.toString(); } } };
說明:JSON.parse() 在這里是利用 new Function() 擁有字符串參數特性,即能動態編譯 js 代碼的能力。可參考神奇的eval()與new Function()
JSON.parse() 其他方式實現:
利用 eval() 實現,盡量避免在不必要的情況下使用。 eval() "惡名昭彰",擁有執行代碼的能力(可能被惡意使用,帶來安全問題),除此之外,不能利用預編譯的優勢進行性能優化,會比較慢。
var json = eval("(" + jsonStr + ")");
還有其他方式,比如遞歸,可參考:JSON.parse 三種實現方式
言盡于此,當然,不止于此(你懂得)。歡迎大家來補充~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105642.html
摘要:舉個例子來說明一下什么是淺拷貝什么是深拷貝淺拷貝得出的結果可以看出是淺拷貝非對象的屬性值一個改變不影響另一個的值對象屬性是引用賦值所以一個改變會影響另一個的改變出現這種情況的本質是對象是按引用賦值的深拷貝指的是拷貝一個對象,改變一個值不影響 舉個例子來說明一下什么是淺拷貝什么是深拷貝 淺拷貝 var x = { a: 1, b: {f: { g: 1 ...
摘要:它將返回目標對象。該方法使用源對象的和目標對象的,所以它會調用相關和。注意,會跳過那些值為或的源對象。合并對象注意目標對象自身也會改變。注意,只有字符串的包裝對象才可能有自身可枚舉屬性。,第三個源對象更是不會被拷貝到的。 Object.assign() Object.assign()方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。 語法 Object.a...
摘要:說句玩笑話,如果是基于的,可能就叫了,形式可能就是這樣的了,如果這樣,那么可能現在是和比較密切了。此外,還有一個函數,我們較少看到,但是它會影響。 我們先來看一個JS中常見的JS對象序列化成JSON字符串的問題,請問,以下JS對象通過JSON.stringify后的字符串是怎樣的?先不要急著復制粘貼到控制臺,先自己打開一個代碼編輯器或者紙,寫寫看,寫完再去仔細對比你的控制臺輸出,如果有...
摘要:深拷貝相比于淺拷貝速度較慢并且花銷較大。所以在賦值完成后,在棧內存就有兩個指針指向堆內存同一個數據。結果如下擴展運算符只能對一層進行深拷貝如果拷貝的層數超過了一層的話,那么就會進行淺拷貝那么我們可以看到和展開原算符對于深淺拷貝的結果是一樣。 JS中數據類型 基本數據類型: undefined、null、Boolean、Number、String和Symbol(ES6) 引用數據類型:...
摘要:動手實現深拷貝利遞歸來實現對對象或數組的深拷貝。遞歸思路對屬性中所有引用類型的值進行遍歷,直到是基本類型值為止。深拷貝只對對象自有屬性進行拷貝測試數據拷貝方式其實也是一種繼承的方式,當然繼承還是有其他方法的感謝支持 深淺拷貝 基本類型 & 引用類型 ECMAScript中的數據類型可分為兩種: 基本類型:undefined,null,Boolean,String,Number,Symb...
閱讀 1614·2021-11-16 11:45
閱讀 2544·2021-09-29 09:48
閱讀 3269·2021-09-07 10:26
閱讀 1840·2021-08-16 10:50
閱讀 1866·2019-08-30 15:44
閱讀 2698·2019-08-28 18:03
閱讀 1898·2019-08-27 10:54
閱讀 1823·2019-08-26 14:01