日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

b66c81373bfb902b4ddb6c8c953acbb9.png
英文 | https://medium.com/frontend-canteen/my-friend-almost-lost-his-year-end-bonus-because-of-json-stringify-9da86961eb9e

翻譯 | 楊小愛(ài)

這是發(fā)生在我朋友身上的真實(shí)故事,他的綽號(hào)叫胖頭。由于JSON.stringify的錯(cuò)誤使用,他負(fù)責(zé)的其中一個(gè)業(yè)務(wù)模塊上線后出現(xiàn)了bug,導(dǎo)致某個(gè)頁(yè)面無(wú)法使用,進(jìn)而影響用戶(hù)體驗(yàn),差點(diǎn)讓他失去年終獎(jiǎng)。

在這篇文章中,我將分享這個(gè)悲傷的故事。然后我們還將討論 JSON.stringify 的各種功能,以幫助您避免將來(lái)也犯同樣的錯(cuò)誤。

我們現(xiàn)在開(kāi)始

故事是這樣的。

他所在的公司,有一位同事離開(kāi)了,然后胖頭被要求接受離開(kāi)同事的工作內(nèi)容。

沒(méi)想到,在他接手這部分業(yè)務(wù)后不久,項(xiàng)目中就出現(xiàn)了一個(gè)bug。

當(dāng)時(shí),公司的交流群里,很多人都在討論這個(gè)問(wèn)題。

產(chǎn)品經(jīng)理先是抱怨:項(xiàng)目中有一個(gè)bug,用戶(hù)無(wú)法提交表單,客戶(hù)抱怨這個(gè)。請(qǐng)開(kāi)發(fā)組盡快修復(fù)。

然后測(cè)試工程師說(shuō):我之前測(cè)試過(guò)這個(gè)頁(yè)面,為什么上線后就不行了?

而后端開(kāi)發(fā)者說(shuō):前端發(fā)送的數(shù)據(jù)缺少value字段,導(dǎo)致服務(wù)端接口出錯(cuò)。

找到同事抱怨后,問(wèn)題出在他負(fù)責(zé)的模塊上,我的朋友胖頭真的很頭疼。

經(jīng)過(guò)一番檢查,我的朋友終于找到了這個(gè)錯(cuò)誤。

事情就是這樣。

發(fā)現(xiàn)頁(yè)面上有一個(gè)表單允許用戶(hù)提交數(shù)據(jù),然后前端應(yīng)該從表單中解析數(shù)據(jù)并將數(shù)據(jù)發(fā)送到服務(wù)器。

表格是這樣的:(下面是我的模擬)

25c82f627e8d6fcc2e45312b4972e1ed.png

這些字段是可選的。

通常,數(shù)據(jù)應(yīng)如下所示:


 
  1. let data = {
  2. signInfo: [
  3. {
  4. "fieldId": 539,
  5. "value": "silver card"
  6. },
  7. {
  8. "fieldId": 540,
  9. "value": "2021-03-01"
  10. },
  11. {
  12. "fieldId": 546,
  13. "value": "10:30"
  14. }
  15. ]
  16. }

然后它們應(yīng)該轉(zhuǎn)換為:

f4688da1a27da7febc59481dd9a609f8.png

但問(wèn)題是,這些字段是可選的。如果用戶(hù)沒(méi)有填寫(xiě)某些字段,那么數(shù)據(jù)會(huì)變成這樣:


 
  1. let data = {
  2. signInfo: [
  3. {
  4. "fieldId": 539,
  5. "value": undefined
  6. },
  7. {
  8. "fieldId": 540,
  9. "value": undefined
  10. },
  11. {
  12. "fieldId": 546,
  13. "value": undefined
  14. }
  15. ]
  16. }

他們將變成這樣:

7ca09a08e371bf7ed8f5e567375b852b.png

JSON.stringify 在轉(zhuǎn)換過(guò)程中忽略其值為undefined的字段。

因此,此類(lèi)數(shù)據(jù)上傳到服務(wù)器后,服務(wù)器無(wú)法解析 value 字段,進(jìn)而導(dǎo)致錯(cuò)誤。

一旦發(fā)現(xiàn)問(wèn)題,解決方案就很簡(jiǎn)單,為了在數(shù)據(jù)轉(zhuǎn)換為 JSON 字符串后保留 value 字段,我們可以這樣做:

b24aa3fdc317fa3d32263d1700e13f27.png


 
  1. let signInfo = [
  2. {
  3. fieldId: 539,
  4. value: undefined
  5. },
  6. {
  7. fieldId: 540,
  8. value: undefined
  9. },
  10. {
  11. fieldId: 546,
  12. value: undefined
  13. },
  14. ]
  15. let newSignInfo = signInfo.map((it) => {
  16. const value = typeof it.value === 'undefined' ? '' : it.value
  17. return {
  18. ...it,
  19. value
  20. }
  21. })
  22. console.log(JSON.stringify(newSignInfo))
  23. // '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'

如果發(fā)現(xiàn)某個(gè)字段的值為undefined,我們將該字段的值更改為空字符串。

雖然問(wèn)題已經(jīng)解決了,但是,我們還需要思考這個(gè)問(wèn)題是怎么產(chǎn)生的。

本來(lái)這是一個(gè)已經(jīng)上線好幾天的頁(yè)面,為什么突然出現(xiàn)這個(gè)問(wèn)題?仔細(xì)排查,原來(lái)是產(chǎn)品經(jīng)理之前提出了一個(gè)小的優(yōu)化點(diǎn),然后,胖頭對(duì)代碼做了一點(diǎn)改動(dòng)。但是胖頭對(duì) JSON.stringify 的特性并不熟悉,同時(shí),他認(rèn)為改動(dòng)比較小,所以沒(méi)有進(jìn)行足夠的測(cè)試,最終導(dǎo)致項(xiàng)目出現(xiàn) bug。

好在他發(fā)現(xiàn)問(wèn)題后,很快就解決了問(wèn)題。這個(gè)bug影響的用戶(hù)少,所以老板沒(méi)有責(zé)怪他,我的朋友獎(jiǎng)金沒(méi)有丟掉,不然,影響大的話(huà),估計(jì)獎(jiǎng)金真的就沒(méi)有了,甚至還會(huì)讓他直接離開(kāi)。

接著,我們一起來(lái)了解一下 JSON.stringify,它為啥那么“厲害”,差點(diǎn)把我朋友的獎(jiǎng)金都給弄丟了。

了解一下 JSON.stringify

其實(shí),這個(gè)bug主要是因?yàn)榕诸^對(duì)JSON.stringify不熟悉造成的,所以,這里我們就一起來(lái)分析一下這個(gè)內(nèi)置函數(shù)的一些特點(diǎn)。

基本上,JSON.stringify() 方法將 JAVAScript 對(duì)象或值轉(zhuǎn)換為 JSON 字符串:

8c236068569b8bdae2073a34351647f7.png

同時(shí),JSON.stringify 有以下規(guī)則。

1、如果目標(biāo)對(duì)象有toJSON()方法,它負(fù)責(zé)定義哪些數(shù)據(jù)將被序列化。

494b0b6ed82fb36be17922aad24bf1a3.png

2、 Boolean、Number、String 對(duì)象在字符串化過(guò)程中被轉(zhuǎn)換為對(duì)應(yīng)的原始值,符合傳統(tǒng)的轉(zhuǎn)換語(yǔ)義。

cdcc9b0fbe5288ec8c38f8c2861ea0fc.png

3、 undefined、Functions 和 Symbols 不是有效的 JSON 值。如果在轉(zhuǎn)換過(guò)程中遇到任何此類(lèi)值,則它們要么被忽略(在對(duì)象中找到),要么被更改為 null(當(dāng)在數(shù)組中找到時(shí))。

d790f8afaa0aefeaebabf1c881a6318c.png

ca8ee1d038ccd528058614118da5943a.png

4、 所有 Symbol-keyed 屬性將被完全忽略

f7fc9ba44aa40c37810b6aa92ebb7fc5.png

5、 Date的實(shí)例通過(guò)返回一個(gè)字符串來(lái)實(shí)現(xiàn)toJSON()函數(shù)(與date.toISOString()相同)。因此,它們被視為字符串。

1d385aaed9000ad32dfff5677c8db945.png

6、 數(shù)字 Infinity 和 NaN 以及 null 值都被認(rèn)為是 null。

774c3d97adda1ffeb8c392462f4db392.png

7、 所有其他 Object 實(shí)例(包括 Map、Set、WeakMap 和 WeakSet)將僅序列化其可枚舉的屬性。

e7ecd3a4626dce00aaf1038bc4d0b561.png

8、找到循環(huán)引用時(shí)拋出TypeError(“循環(huán)對(duì)象值”)異常。

be1479a0b522933ad7636a158194c9d9.png

9、 嘗試對(duì) BigInt 值進(jìn)行字符串化時(shí)拋出 TypeError(“BigInt 值無(wú)法在 JSON 中序列化”)。

af612b440a5d4104e48915c76af1caa9.png

自己實(shí)現(xiàn) JSON.stringify

理解一個(gè)函數(shù)的最好方法是自己實(shí)現(xiàn)它。下面我寫(xiě)了一個(gè)模擬 JSON.stringify 的簡(jiǎn)單函數(shù)。


 
  1. const jsonstringify = (data) => {
  2. // Check if an object has a circular reference
  3. const isCyclic = (obj) => {
  4. // Use a Set to store the detected objects
  5. let stackSet = new Set()
  6. let detected = false
  7.  
  8.  
  9. const detect = (obj) => {
  10. // If it is not an object, we can skip it directly
  11. if (obj && typeof obj != 'object') {
  12. return
  13. }
  14. // When the object to be checked already exists in the stackSet,
  15. // it means that there is a circular reference
  16. if (stackSet.has(obj)) {
  17. return detected = true
  18. }
  19. // save current obj to stackSet
  20. stackSet.add(obj)
  21.  
  22.  
  23. for (let key in obj) {
  24. // check all property of `obj`
  25. if (obj.hasOwnProperty(key)) {
  26. detect(obj[key])
  27. }
  28. }
  29. // After the detection of the same level is completed,
  30. // the current object should be deleted to prevent misjudgment
  31. /*
  32. For example: different properties of an object may point to the same reference,
  33. which will be considered a circular reference if not deleted
  34.  
  35.  
  36. let tempObj = {
  37. name: 'bytefish'
  38. }
  39. let obj4 = {
  40. obj1: tempObj,
  41. obj2: tempObj
  42. }
  43. */
  44. stackSet.delete(obj)
  45. }
  46.  
  47.  
  48. detect(obj)
  49.  
  50.  
  51. return detected
  52. }
  53.  
  54.  
  55. // Throws a TypeError ("cyclic object value") exception when a circular reference is found.
  56. if (isCyclic(data)) {
  57. throw new TypeError('Converting circular structure to JSON')
  58. }
  59.  
  60.  
  61. // Throws a TypeError when trying to stringify a BigInt value.
  62. if (typeof data === 'bigint') {
  63. throw new TypeError('Do not know how to serialize a BigInt')
  64. }
  65.  
  66.  
  67. const type = typeof data
  68. const commonKeys1 = ['undefined', 'function', 'symbol']
  69. const getType = (s) => {
  70. return Object.prototype.toString.call(s).replace(/[object (.*?)]/, '$1').toLowerCase()
  71. }
  72.  
  73.  
  74. if (type !== 'object' || data === null) {
  75. let result = data
  76. // The numbers Infinity and NaN, as well as the value null, are all considered null.
  77. if ([NaN, Infinity, null].includes(data)) {
  78. result = 'null'
  79.  
  80.  
  81. // undefined, arbitrary functions, and symbol values are converted individually and return undefined
  82. } else if (commonKeys1.includes(type)) {
  83.  
  84.  
  85. return undefined
  86. } else if (type === 'string') {
  87. result = '"' + data + '"'
  88. }
  89.  
  90.  
  91. return String(result)
  92. } else if (type === 'object') {
  93. // If the target object has a toJSON() method, it's responsible to define what data will be serialized.
  94.  
  95.  
  96. // The instances of Date implement the toJSON() function by returning a string (the same as date.toISOString()). Thus, they are treated as strings.
  97. if (typeof data.toJSON === 'function') {
  98. return jsonstringify(data.toJSON())
  99. } else if (Array.isArray(data)) {
  100. let result = data.map((it) => {
  101. // 3# undefined, Functions, and Symbols are not valid JSON values. If any such values are encountered during conversion they are either omitted (when found in an object) or changed to null (when found in an array).
  102. return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
  103. })
  104.  
  105.  
  106. return `[${result}]`.replace(/'/g, '"')
  107. } else {
  108. // 2# Boolean, Number, and String objects are converted to the corresponding primitive values during stringification, in accord with the traditional conversion semantics.
  109. if (['boolean', 'number'].includes(getType(data))) {
  110. return String(data)
  111. } else if (getType(data) === 'string') {
  112. return '"' + data + '"'
  113. } else {
  114. let result = []
  115. // 7# All the other Object instances (including Map, Set, WeakMap, and WeakSet) will have only their enumerable properties serialized.
  116. Object.keys(data).forEach((key) => {
  117. // 4# All Symbol-keyed properties will be completely ignored
  118. if (typeof key !== 'symbol') {
  119. const value = data[key]
  120. // 3# undefined, Functions, and Symbols are not valid JSON values. If any such values are encountered during conversion they are either omitted (when found in an object) or changed to null (when found in an array).
  121. if (!commonKeys1.includes(typeof value)) {
  122. result.push(`"${key}":${jsonstringify(value)}`)
  123. }
  124. }
  125. })
  126.  
  127.  
  128. return `{${result}}`.replace(/'/, '"')
  129. }
  130. }
  131. }
  132. }

寫(xiě)在最后

從一個(gè) bug 開(kāi)始,我們討論了 JSON.stringify 的特性并自己實(shí)現(xiàn)了它。

今天我與你分享這個(gè)故事,是希望你以后遇到這個(gè)問(wèn)題,知道怎么處理,不要也犯同樣的錯(cuò)誤。

如果你覺(jué)得有用的話(huà),請(qǐng)點(diǎn)贊我,關(guān)注我,最后,感謝你的閱讀,編程愉快!
 

分享到:
標(biāo)簽:JSON
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定