深入淺出JSON.parse的實(shí)現(xiàn)方法
前言
眾所周知,JSON.parse
方法用于將一個(gè)json
字符串轉(zhuǎn)換成由字符串描述的 JavaScript 值或?qū)ο?,該方法支持傳?個(gè)參數(shù),第一個(gè)參數(shù)就是需要被轉(zhuǎn)換的json
字符串,第二個(gè)參數(shù)則是一個(gè)轉(zhuǎn)換器函數(shù)(reviver,也叫還原函數(shù)),這個(gè)函數(shù)會(huì)針對(duì)每個(gè)鍵/值對(duì)都調(diào)用一次,這個(gè)轉(zhuǎn)換器函數(shù)又接受2個(gè)參數(shù),第一個(gè)參數(shù)為轉(zhuǎn)換的每一個(gè)屬性名,第二個(gè)參數(shù)則為轉(zhuǎn)換的每一個(gè)屬性值,并且該函數(shù)需要返回一個(gè)值,如果返回的是undefined,則結(jié)果中就會(huì)刪除相應(yīng)的鍵,如果返回了其他任何值,則該值就會(huì)成為相應(yīng)鍵的值插入到結(jié)果中。
對(duì)于轉(zhuǎn)換器函數(shù)更具體點(diǎn)講就是:解析值本身以及它所包含的所有屬性,會(huì)按照一定的順序(從最最里層的屬性開始,一級(jí)級(jí)往外,最終到達(dá)頂層,也就是解析值本身)分別的去調(diào)用 reviver
函數(shù),在調(diào)用過(guò)程中,當(dāng)前屬性所屬的對(duì)象會(huì)作為 this
值,當(dāng)前屬性名和屬性值會(huì)分別作為第一個(gè)和第二個(gè)參數(shù)傳入 reviver
中。如果 reviver
返回 undefined
,則當(dāng)前屬性會(huì)從所屬對(duì)象中刪除,如果返回了其他值,則返回的值會(huì)成為當(dāng)前屬性新的屬性值。
當(dāng)遍歷到最頂層的值(解析值)時(shí),傳入 reviver
函數(shù)的參數(shù)會(huì)是空字符串 ""
(因?yàn)榇藭r(shí)已經(jīng)沒(méi)有真正的屬性)和當(dāng)前的解析值(有可能已經(jīng)被修改過(guò)了),當(dāng)前的 this
值會(huì)是 {"": 修改過(guò)的解析值}
,在編寫 reviver
函數(shù)時(shí),要注意到這個(gè)特例。(這個(gè)函數(shù)的遍歷順序依照:從最內(nèi)層開始,按照層級(jí)順序,依次向外遍歷)。
我們來(lái)看以下幾個(gè)示例:
const bool = JSON.parse('true'); // true const obj = JSON.parse('{"k":1,"v":2}'); // { k:1 ,v: 2} const obj2 = JSON.parse('{"k":1,"v":2}',(k,v) => { if(k === 'k'){ return v + 2; } return v; }); // { k:3 } const obj3 = JSON.parse('{"k":1,"v":2}',(k,v) => { if(k === 'k'){ return v + 2; } // 尤其需要注意這個(gè)特例 if(k === ""){ return v; } return v + 1; }); // { k:3:,v:3 }
實(shí)現(xiàn)方法
前面我們已經(jīng)熟悉了該方法的使用方式,接下來(lái),我們就根據(jù)該方法的使用方式來(lái)實(shí)現(xiàn)這個(gè)方法。在實(shí)現(xiàn)這個(gè)方法之前,我們需要知道一點(diǎn),那就是想要解析出合格的JSON數(shù)據(jù),那么數(shù)據(jù)格式就必須符合規(guī)定,例如:undefined不符合正確格式的數(shù)據(jù)格式,因此在實(shí)現(xiàn)的時(shí)候,我們都要將這種情況給考慮進(jìn)去。
從前面的使用方式,我們不難看出,實(shí)際上整個(gè)解析過(guò)程就是在對(duì)整個(gè)json字符串進(jìn)行遍歷,而在遍歷過(guò)程中我們就需要針對(duì)不同的數(shù)據(jù)類型做不同的處理,例如如果是解析字符串,我們只需要?jiǎng)?chuàng)建一個(gè)空字符串,遍歷字符串每一個(gè)字符,然后將字符拼接起來(lái)即可,當(dāng)然在遍歷過(guò)程中,我們還需要對(duì)一些特殊字符或者符號(hào)進(jìn)行處理。
理解了整體的思路,接下來(lái),我們就來(lái)一步一步的實(shí)現(xiàn)這個(gè)方法吧。
創(chuàng)建一個(gè)自調(diào)用函數(shù)
我們采用的是創(chuàng)建一個(gè)自調(diào)用函數(shù),并且這個(gè)函數(shù)返回一個(gè)函數(shù),而在這個(gè)返回的函數(shù)當(dāng)中,我們會(huì)提供2個(gè)參數(shù),正如前面所介紹的那樣,這2個(gè)參數(shù)分別是被解析的json字符串和轉(zhuǎn)換器函數(shù),命名為source與reviver,代碼如下所示:
const jsonParser = (() => { // ... return (source,reviver) => { // ... } })();
這個(gè)函數(shù)內(nèi)部,我們將會(huì)定義一個(gè)變量result,用來(lái)存儲(chǔ)最終的解析結(jié)果,并且我們會(huì)根據(jù)第二個(gè)參數(shù)reviver是否是一個(gè)函數(shù)來(lái)確定是直接返回這個(gè)結(jié)果還是返回reviver轉(zhuǎn)換器函數(shù),這個(gè)轉(zhuǎn)換器函數(shù)也是一個(gè)自調(diào)用函數(shù),由于我們的json數(shù)據(jù)可能是嵌套的對(duì)象或者數(shù)組,因此這里我們也需要定義一個(gè)函數(shù)名,方便遞歸調(diào)用。
一些變量的定義
這里的實(shí)現(xiàn)我們最后來(lái)說(shuō),接下來(lái)我們需要先定義一些變量,比如當(dāng)前字符索引值,當(dāng)前字符,一些特殊字符的定義,以及需要一個(gè)變量來(lái)緩存原始json字符串,同樣的如果在解析字符串時(shí)出現(xiàn)不符合規(guī)定的字符,則需要提示錯(cuò)誤,因此我們也會(huì)封裝一個(gè)error函數(shù),如下所示:
const jsonParser = (() => { let at, // 當(dāng)前字符索引值 ch, // 當(dāng)前遍歷字符 text, // 緩存原始json字符串 escapee = { '"': '"', '\\': '\\', '/': '/', b: 'b', f: '\f', n: '\n', r: '\r', t: '\t' }, // 特殊字符 error = m => { const errorObj = { name: 'SyntaxError', message: m, at, text }; console.error(`${JSON.stringify(errorObj)}`); // 控制臺(tái)打印錯(cuò)誤 // 或者使用throw拋出錯(cuò)誤,即 // throw errorObj; }, // ... return (source,reviver) => { // ... } })();
next方法
接下來(lái),我們還需要實(shí)現(xiàn)一個(gè)next方法,這個(gè)方法,在這個(gè)方法中,我們會(huì)依次的去讀取字符串的每一個(gè)字符,并將索引值加1,讀取字符我們可以使用String.charAt
方法,該方法就是讀取字符串的每一個(gè)字符。如:
const str = "hello"; str.charAt(0); // h
需要注意的就是該方法支持傳入一個(gè)參數(shù),并且如果傳入的參數(shù),不等于我們的字符ch,則需要拋出一個(gè)兩者不相等的錯(cuò)誤。
有了以上的分析,我們的next方法就很好實(shí)現(xiàn)了,如下所示:
const jsonParser = (() => { let at, //... next = c => { // 如果有傳入?yún)?shù),并且該參數(shù)值不等于當(dāng)前字符,則需要給出錯(cuò)誤提示 if(c && c !== ch){ error(`預(yù)期${c}代替${ch}`); } // 根據(jù)索引值讀取當(dāng)前字符 ch = text.charAt(at); at++; // 索引值加1 //返回當(dāng)前字符 return ch; }, //... return (source,reviver) => { // ... } })();
依據(jù)數(shù)據(jù)類型解析
接下來(lái),我們就要根據(jù)當(dāng)前解析的json字符串屬于哪一種數(shù)據(jù)類型而依次去解析了,數(shù)據(jù)類型主要分為數(shù)值number,字符串string,布爾值boolean和null,以及對(duì)象object和數(shù)組,當(dāng)然還有空格字符。
也許有人好奇為什么會(huì)沒(méi)有undefined類型,我們來(lái)看如下圖所示:
如上圖所示,JSON.parse
是不能夠解析undefined的,當(dāng)然如果"undefined"字符串是作為一個(gè)對(duì)象的屬性值,還是可以被解析出來(lái)的,如果是undefined作為屬性值,是不會(huì)被解析出來(lái)的。如:
JSON.parse('{"a":"undefined"}'); // { a:"undefined" } JSON.parse('{"a":undefined}'); // Unexpected token 'u', "{"a":undefined}" is not valid JSON // 數(shù)組解析同理
布爾值,null與空白字符的解析
我們先來(lái)看最簡(jiǎn)單的兩種數(shù)據(jù)類型的解析,由于布爾值和null兩者解析過(guò)程相似,因此歸為一類定義一個(gè)方法來(lái)解析,空白字符只需要跳過(guò)即可。代碼如下所示:
const jsonParser = (() => { let at, //... white = () => { // 注意這里為什么是使用<=而非==,因?yàn)檫€有類似\n這樣的空白符 while(ch && ch <= ' '){ next(); } }, word = () => { switch (ch) { case 't': next('t'); next('r'); next('u'); next('e'); return true; case 'f': next('f'); next('a'); next('l'); next('s'); next('e'); return false; case 'n': next('n'); next('u'); next('l'); next('l'); return null; }; error(`意料之外的值:${ch}`); }, //... return (source,reviver) => { // ... } })();
可以看到解析布爾值和null,我們只需要根據(jù)首字符是否為該類型數(shù)據(jù)的首字母即可判定解析,然后將結(jié)果返回出去即可,如果不滿足條件,則需要報(bào)錯(cuò)。
數(shù)值的解析
接下來(lái)我們來(lái)看數(shù)值類型數(shù)據(jù)的解析,數(shù)值類型我們需要考慮四種情況,第一種就是正負(fù)號(hào)的解析,第二種則是e字母的解析(即科學(xué)計(jì)數(shù)法),第三種則是小數(shù)點(diǎn)'.'的解析,最后一種則是數(shù)字的解析。在該方法內(nèi)部,我們將創(chuàng)建2個(gè)變量,因?yàn)殡m然是數(shù)值數(shù)據(jù),但是我們是一個(gè)字符一個(gè)字符的解析,而非做計(jì)算,因此就需要拼接字符串,不過(guò)拼接完之后的字符串我們需要轉(zhuǎn)換成數(shù)字,這兩個(gè)變量就做這2個(gè)工作的。
首先我們需要判斷是否為負(fù)號(hào),從而直接拼接,然后繼續(xù)下一個(gè)字符,下一個(gè)字符我們需要將負(fù)號(hào)當(dāng)做參數(shù)傳給next方法,接著我們循環(huán)當(dāng)前字符是否是數(shù)字,如何判斷是否是數(shù)字呢?我們只需要比較是否大于等于0并且小于等于9即可,注意這里我們比較的是字符串的碼序,而不是單純的比數(shù)字大小來(lái)判定是否是數(shù)字。即:
const isNumber = v => v >= '0' && v <= '9';
拼接數(shù)字完成之后,我們還要繼續(xù)調(diào)用next方法進(jìn)行下一步,注意這里調(diào)用不需要傳任何參數(shù)。
完成數(shù)字的拼接之后,我們接著判斷是否是小數(shù)點(diǎn)從而繼續(xù)拼接,小數(shù)點(diǎn)之后會(huì)繼續(xù)是數(shù)字,因此我們還要繼續(xù)循環(huán)數(shù)字從而繼續(xù)拼接。
最后一步就是判斷當(dāng)前字符是否是e字母,注意e字母不區(qū)分大小寫,因此需要兩個(gè)判斷條件,e字母后面也有可能有正負(fù)號(hào),因此也需要判斷是否是正負(fù)號(hào),正負(fù)號(hào)后面還會(huì)有數(shù)字,也需要繼續(xù)拼接,從而調(diào)用next方法進(jìn)行下一步。
最后把拼接后的字符串利用加號(hào)操作符轉(zhuǎn)換成數(shù)值,從而得到最終的結(jié)果,最終的結(jié)果有可能是一個(gè)NaN,因此我們還需要判斷一下是否是NaN,如果是NaN,則給出一個(gè)錯(cuò)誤提示,否則直接返回最終的結(jié)果。
根據(jù)以上的分析,最終我們的number轉(zhuǎn)換方法如下所示:
const jsonParser = (() => { let at, //... number = () => { let number,string = ''; // 定義number存儲(chǔ)最終轉(zhuǎn)換成數(shù)字的結(jié)果,定義string變量拼接字符串 // 符號(hào)的拼接,+號(hào)通常是不會(huì)寫的,因此不需要判斷 if(ch === '-'){ string += ch; next('-'); } // 循環(huán)數(shù)字 while(ch >= '0' && ch <= '9'){ string += ch; next(); } //小數(shù)點(diǎn) if(ch === '.'){ string += ch; while(next() && ch >= '0' && ch <= '9'){ string += ch; next(); } } //科學(xué)計(jì)數(shù)法 if(ch === 'e' || ch === 'E'){ string += ch; next(); // 科學(xué)計(jì)數(shù)法e字母后還有可能是正負(fù)號(hào) if(ch === '-' || ch === '+'){ string += ch; next(); } // 科學(xué)計(jì)數(shù)法e字母之后的數(shù)字 while(ch >= '0' && ch <= '9'){ string += ch; next(); } } // 轉(zhuǎn)換成數(shù)值賦值給number變量 number = +string; //判斷是否是NaN if(isNaN(number)){ error('錯(cuò)誤的數(shù)值'); }else{ return number; } }, //... return (source,reviver) => { // ... } })();
字符串的解析
數(shù)值類型解析完成,接下來(lái)我們來(lái)看字符串的解析,字符串的解析也是需要分情況的,首先是Unicode字符,即以u(píng)字母開頭的字符,最準(zhǔn)確的說(shuō)應(yīng)該是類似這樣的unicode字符串'\u2233'的解析。遇到這樣的字符,我們會(huì)使用String.fromCharCode
方法轉(zhuǎn)換成普通的字符串,這里的轉(zhuǎn)換也涉及到了一個(gè)轉(zhuǎn)換公式原理,我們會(huì)使用parseInt將其轉(zhuǎn)換成16進(jìn)制的數(shù)值,然后將該數(shù)字乘以16,并相加,初始結(jié)果為0,我們會(huì)定義一個(gè)變量uChar來(lái)用作計(jì)算后的結(jié)果。
首先第一步,我們知道字符串以"
開頭,因此首先我們需要判斷是否是"
,最開始我們也需要定義4個(gè)變量,即hex,i,uChar,string,其中hex用來(lái)存儲(chǔ)parseInt轉(zhuǎn)換成16進(jìn)制后的結(jié)果,uChar用來(lái)存儲(chǔ)最終的轉(zhuǎn)換結(jié)果,i就是循環(huán)變量,string則是最終拼接出來(lái)的結(jié)果。
判斷完成之后,我們將依次循環(huán)下一個(gè)字符,在循環(huán)當(dāng)中,如果遇到另一個(gè)"
,則代表字符串已經(jīng)拼接完成,直接返回string結(jié)果,并退出循環(huán),否則遇到當(dāng)前字符是"\\"
,則需要將unicode字符進(jìn)行轉(zhuǎn)換,首先還是調(diào)用next方法跳過(guò)該字符,然后判斷是否是u字母或者我們定義好的escapee中的特殊字符,如果兩者都不是,則需要跳出循環(huán),最后將String.fromCharCode
方法轉(zhuǎn)換uChar
的結(jié)果值拼接給結(jié)果變量string。
這其中額外需要注意的就是Unicode字符的計(jì)算,我們會(huì)以4為循環(huán)最終條件,去計(jì)算,并且我們?cè)谘h(huán)當(dāng)中還會(huì)判斷是否是一個(gè)有限的數(shù)值,從而決定是否跳出該循環(huán)。
否則就是直接字符串拼接直到循環(huán)完成,如果不滿足相應(yīng)的條件,我們最終也會(huì)給出錯(cuò)誤提示。根據(jù)以上分析,最終我們拼接字符串的代碼如下所示:
const jsonParser = (() => { let at, //... string = () => { let hex,i,string,uChar; if(ch === '"'){ // 從下一個(gè)字符開始循環(huán) while(next()){ if(ch === '"'){ // 如果是另一個(gè)雙引號(hào),則是字符串的結(jié)束 next(); return string; }else if(ch === '\\'){ // 如果是Unicode字符 next(); // 如果當(dāng)前字符是u字母 if(ch === 'u'){ uChar = 0; for(i = 0;i < 4;i++){ // 轉(zhuǎn)換成16進(jìn)制數(shù) hex = parseInt(next(),16); // 如果hex不是一個(gè)有限數(shù)值,則跳出循環(huán) if(!isFinite(hex)){ break; } // 計(jì)算uChar uChar = uChar * 16 + hex; } }else if(typeof escapee[ch] === 'string'){ // 如果是特殊字符,則直接拼接 string += escapee[ch]; }else{ // 跳出循環(huán) break; } // 拼接最終結(jié)果 string += String.fromCharCode(uChar); }else{ // 否則當(dāng)成普通字符拼接 string += ch; } } } // 如果當(dāng)前字符不是"開頭,則是一個(gè)錯(cuò)誤的字符串 error('錯(cuò)誤的字符串'); }, //... return (source,reviver) => { // ... } })();
數(shù)組的解析
字符串和數(shù)值以及布爾值還有null都解析完了,接下來(lái)就是數(shù)組和對(duì)象的解析了,我們先來(lái)看數(shù)組的解析。數(shù)組一定是以"["
開頭的,而它里面的值有可能是字符串,或者數(shù)組或者對(duì)象等,因此在這之前我們需要先定義一個(gè)值變量value用來(lái)存儲(chǔ)這種不可推測(cè)的值,如下所示:
const jsonParser = (() => { let at, //... value, //... return (source,reviver) => { // ... } })();
數(shù)組的解析也不復(fù)雜,我們還是會(huì)定義一個(gè)array變量用來(lái)緩存最終的結(jié)果,接著判斷是否以[
開頭,如果是就繼續(xù)下一個(gè)字符,并且有可能該字符后面有空白,因此我們需要調(diào)用white方法,緊接著我們判斷下一個(gè)字符是否是]
,如果是,就代表數(shù)組解析已結(jié)束,直接返回array結(jié)果。
否則循環(huán)當(dāng)前字符,并將值(也就是我們定義的value變量)添加到array中,然后再調(diào)用一次white方法跳過(guò)空白字符,緊接著判斷是否是]
字符,如果是就繼續(xù)下一個(gè)字符的遍歷,并返回結(jié)果,否則將逗號(hào)當(dāng)做參數(shù)傳給next方法,當(dāng)做下一個(gè)字符的遍歷,然后再調(diào)用一次white方法跳過(guò)空白字符。
否則最后我們就給出一個(gè)錯(cuò)誤提示,錯(cuò)誤的數(shù)組。根據(jù)以上的分析,最終可得代碼如下所示:
const jsonParser = (() => { let at, //... value, array = () => { const array = []; // [開頭則繼續(xù)下一個(gè)字符,并跳過(guò)空白 if(ch === '['){ next('['); white(); } // ]則解析結(jié)束,返回結(jié)果 if(ch === ']'){ next(']'); return array; } // 循環(huán)字符 while(ch){ array.push(value()); white(); // ]則結(jié)束解析 if(ch === ']'){ next(']'); return array; } // 跳過(guò)逗號(hào)字符的解析 next(','); white(); } // 錯(cuò)誤的數(shù)組數(shù)據(jù) error('錯(cuò)誤的數(shù)組'); } //... return (source,reviver) => { // ... } })();
對(duì)象的解析
對(duì)象的解析與數(shù)組的解析有些類似,不過(guò)對(duì)象需要考慮屬性名和屬性值,屬性名實(shí)際上就是對(duì)字符串的解析,而屬性值則與數(shù)組項(xiàng)一樣,是不可推測(cè)的value值,遇到:
字符,我們也需要跳過(guò),并解析下一個(gè)字符。
中間可能也會(huì)有空白字符,因此需要跳過(guò),我們會(huì)創(chuàng)建2個(gè)變量,第一個(gè)變量用于緩存屬性名,第二個(gè)變量則是存儲(chǔ)結(jié)果值,我們知道對(duì)象是"{"
開始,"}"
結(jié)束的,除了這些需要注意的地方,其它就和解析數(shù)組一樣差不多了。
根據(jù)以上的分析,我們最終的代碼如下所示:
const jsonParser = (() => { let at, //... value, object = () => { let key,object = {}; // 存儲(chǔ)屬性名和結(jié)果所定義的變量 if(ch === '{'){ // 跳過(guò){字符解析下一個(gè)字符 next('{'); // 可能存在空白字符 white(); // 如果是}字符,則結(jié)束解析,并返回結(jié)果 if(ch === '}'){ next('}'); return object; } // 循環(huán)字符 while(ch){ // 屬性名即解析字符串 key = string(); // 可能存在空白字符,跳過(guò) white(); // 跳過(guò):字符 next(':'); // value值是一個(gè)函數(shù),下文會(huì)介紹 object[key] = value(); // 跳過(guò)空白 white(); // 如果是},則解析結(jié)束 if(ch === '}'){ next('}'); return object; } // 跳過(guò),字符解析下一個(gè)字符 next(','); // 可能存在空白字符,跳過(guò) white(); } } // 如果不是以{開頭,則對(duì)象格式不符合,拋出錯(cuò)誤 error('錯(cuò)誤的對(duì)象'); }, //... return (source,reviver) => { // ... } })();
不可推測(cè)的值
前文也提到了不可推測(cè)的值value,它可以是數(shù)組,對(duì)象,字符串,數(shù)值,布爾值,null等其中的一個(gè),因此該值我們定義成一個(gè)函數(shù),并根據(jù)當(dāng)前字符以什么開頭來(lái)確定數(shù)據(jù)類型,從而決定使用哪個(gè)方法解析,比如是字符串,就會(huì)以"
開頭,從而調(diào)用前面實(shí)現(xiàn)的string方法進(jìn)行解析,如果是數(shù)組對(duì)象等同理,默認(rèn)當(dāng)然是以數(shù)值和布爾值以及null解析為主。
當(dāng)然最開始可能也會(huì)有空白字符,需要跳過(guò),根據(jù)以上的分析,value函數(shù)最終代碼如下所示:
const jsonParser = (() => { let at, //... value, //...定義完object方法之后再賦值value value = () => { // 可能存在空白字符,跳過(guò) white(); // 判斷以什么字符開頭 switch(ch){ case '{': return object(); // 對(duì)象解析 case '[': return array(); // 數(shù)組解析 case '"': return string(); // 字符串解析 case '-': return number(); // 數(shù)值解析 default: return ch >= '0' && ch <= '9' ? number() : word(); // 如果是數(shù)字則當(dāng)做是數(shù)值解析,否則當(dāng)做布爾值或null解析 } }; return (source,reviver) => { // ... } })();
返回結(jié)果:有轉(zhuǎn)換器函數(shù)與無(wú)轉(zhuǎn)換器函數(shù)
最后我們來(lái)看返回的函數(shù)的實(shí)現(xiàn)原理,首先我們創(chuàng)建了4個(gè)變量,result,text = source,at = 0,ch = ' ',分別代表最終的解析結(jié)果,原始json字符串,起始解析索引值,從0開始,起始解析字符,從空白字符開始。
接著調(diào)用value方法解析值,并賦值給結(jié)果變量result,然后調(diào)用white方法跳過(guò)空白字符,跳過(guò)空白字符之后,如果還存在字符未解析,就代表解析數(shù)據(jù)不是一個(gè)合格的json字符串,則給出錯(cuò)誤提示。
最后函數(shù)結(jié)果返回2個(gè)結(jié)果,第一個(gè)結(jié)果就是如果傳入了轉(zhuǎn)換器函數(shù),則返回一個(gè)自調(diào)用函數(shù),否則返回result。如下所示:
const jsonParser = (() => { // ... return (source,reviver) => { // 解析結(jié)果,原始字符串,起始解析索引值,起始解析字符 let result,text = source,at = 0,ch = ' '; // 解析值并賦值 result = value(); // 跳過(guò)空白字符 white(); // 如果還存在解析字符,則數(shù)據(jù)不符合json規(guī)范,給出錯(cuò)誤 if(ch){ error('解析語(yǔ)法錯(cuò)誤,不是一個(gè)合格的json數(shù)據(jù)'); } // 返回 return typeof reviver === 'function' ? (function walk(holder,key){ // ... })({ '':result },'') : result; } })();
轉(zhuǎn)換器內(nèi)部的實(shí)現(xiàn)原理
還記得前面有一個(gè)這樣的示例,如下所示:
const obj3 = JSON.parse('{"k":1,"v":2}',(k,v) => { if(k === 'k'){ return v + 2; } // 尤其需要注意這個(gè)特例 if(k === ""){ return v; } return v + 1; }); // { k:3:,v:3 }
從以上特例,我們可以得知最開始會(huì)以一個(gè)空屬性名作為遍歷的開始,這也是為什么我們的自調(diào)用函數(shù)的第一個(gè)參數(shù)值是{ '':result }
的原因,第二個(gè)參數(shù)也是以空屬性名作為遍歷開始的。
在遞歸函數(shù)walk內(nèi)部,我們會(huì)定義3個(gè)變量,即循環(huán)屬性名k,緩存的屬性值v,和起始屬性值value = holder[key]
。起始屬性值實(shí)際上就是原始解析結(jié)果開始,如果該值是一個(gè)對(duì)象,我們則需要遍歷該對(duì)象,如果我們的循環(huán)屬性名k是該對(duì)象的屬性,則遞歸的賦值緩存屬性值,然后判斷屬性值如果是undefined,則從對(duì)象中刪除該屬性,否則修改該屬性值,最終我們會(huì)返回調(diào)用轉(zhuǎn)換器函數(shù)的結(jié)果。
根據(jù)以上代碼分析,我們最終轉(zhuǎn)換器函數(shù)內(nèi)部實(shí)現(xiàn)原理代碼如下所示:
const jsonParser = (() => { // ... return (source,reviver) => { // ... return typeof reviver === 'function' ? (function walk(holder,key){ // 循環(huán)屬性名,緩存屬性值,讀取值 let k,v,value = holder[key]; // 如果值是對(duì)象,則需要繼續(xù)解析 if(value && typeof value === 'object'){ // 循環(huán)對(duì)象屬性值 for(k in value){ // 如果value中存在該屬性 if(Object.hasOwnProperty.call(value,k)){ // 繼續(xù)遞歸 v = walk(value,k); // 如果屬性值不是undefined則修改屬性值,否則刪除該屬性 if(v !== undefined){ value[k] = v; }else{ delete value[k]; } } } } // 返回轉(zhuǎn)換器函數(shù)調(diào)用的結(jié)果 return reviver.call(holder,key,value); })({ '':result },'') : result; } })();
最終
將以上代碼整合起來(lái),得到了我們的parse解析方法的實(shí)現(xiàn),以上源碼可以查看這里。
以上就是深入淺出JSON.parse的實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于JSON.parse方法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript 如何生成不重復(fù)的隨機(jī)數(shù)
javascript 如何生成不重復(fù)的隨機(jī)數(shù)...2007-11-11js實(shí)現(xiàn)類似MSN提示的頁(yè)面效果代碼分享
這篇文章主要介紹了模仿MSN消息提示的效果,推薦給大家,有需要的小伙伴可以參考下。2015-08-08Listloading.js移動(dòng)端上拉下拉刷新組件
這篇文章主要介紹了Listloading.js移動(dòng)端上拉下拉刷新組件的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08JS優(yōu)化與惰性載入函數(shù)實(shí)例分析
這篇文章主要介紹了JS優(yōu)化與惰性載入函數(shù),結(jié)合具體實(shí)例形式分析了JS惰性載入的原理、實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-04-04JavaScript進(jìn)階教程之非extends的組合繼承詳解
組合繼承有時(shí)候也叫偽經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式,下面這篇文章主要給大家介紹了關(guān)于JavaScript進(jìn)階教程之非extends的組合繼承的相關(guān)資料,需要的朋友可以參考下2022-08-08關(guān)于微信公眾號(hào)開發(fā)無(wú)法支付的問(wèn)題解決
這篇文章主要介紹了關(guān)于微信公眾號(hào)開發(fā)無(wú)法支付的問(wèn)題解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12