<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我似乎發(fā)現(xiàn)了vue的一個(gè)bug

          共 9676字,需瀏覽 20分鐘

           ·

          2021-08-03 06:01


          公元2021年7月23日,我以為我發(fā)現(xiàn)了vue的一個(gè)bug,此時(shí)此刻,我離給vue提issue只有1個(gè)字節(jié)的距離。

          用過(guò)vue的同學(xué)應(yīng)該都知道Vue.set這個(gè)api的用法吧,來(lái),今天教你一個(gè)"新玩法"。。

          事件還原

          事情得從同事的一行代碼說(shuō)起,看這里:

          if (data.result.length > 0) {
                  data.result.forEach(item1 => {
                    this.assoStatList.forEach((item2, index2) => {
                      if (item1.LGTD == item2.LGTD && item1.LTTD == item2.LTTD) {
                        // this.$set(item2, 'RAIN_FORECAST_24H', item1.RAIN_FORECAST_24H)
                        // item2.RAIN_FORECAST_24H = item1.RAIN_FORECAST_24H
                        // this.$set(this.assoStatList, index2, item2)
                        this.$set(this.assoStatList, item2, (item2.RAIN_FORECAST_24H = item1.RAIN_FORECAST_24H))
                        console.log(this.assoStatList)
                        console.log(item2)
                      }
                    })
                  })
                }

          劃重點(diǎn):

          this.$set(this.assoStatList, item2, (item2.RAIN_FORECAST_24H = item1.RAIN_FORECAST_24H))

          就是這行代碼,我覺(jué)得寫錯(cuò)了,因?yàn)閰?shù)傳得不對(duì)了。

          話不多說(shuō),來(lái)看官方文檔:

          顯然,同事這個(gè)寫法明顯是有問(wèn)題的,按文檔意思,第一個(gè)參數(shù)如果是數(shù)組,那第二個(gè)應(yīng)該給索引值,他這里居然給了個(gè)對(duì)象!我難以忍受,甚至手把手地教他怎么按文檔來(lái),然后用兩種正確方式都寫出來(lái)了。

          vue出bug了?

          但是同事堅(jiān)持說(shuō)他沒(méi)有錯(cuò),然后執(zhí)行給我看,結(jié)果確實(shí)沒(méi)有報(bào)錯(cuò),而且正常執(zhí)行了!?后面在用到this.assoStatList的代碼可以正常執(zhí)行。這讓我相當(dāng)?shù)膶擂危?/p>

          我又仔細(xì)看了文檔,上面寫得很清楚,第二個(gè)參數(shù)要么是字符串鍵值,要么是索引,這取決于你要操作的對(duì)象即第一個(gè)參數(shù)是對(duì)象還是數(shù)組;第三個(gè)參數(shù)沒(méi)做限制,寫個(gè)函數(shù)也沒(méi)問(wèn)題。

          這矛盾的情況讓我百思不得其解,直覺(jué)告訴我,這里面有問(wèn)題:

          這張圖是我打印的被賦值過(guò)的數(shù)組this.assoStatList,結(jié)果發(fā)現(xiàn)這個(gè)數(shù)組除了正常的索引元素,還多了一個(gè)屬性[object Object],屬性值為0,而這個(gè)0就是item1.RAIN_FORECAST_24H帶過(guò)來(lái)的值。

          image.png

          從這張圖可以看出來(lái),第三個(gè)參數(shù)的賦值操作成功了,但是同時(shí)也給數(shù)組this.assoStatList加了一個(gè)[object Object]屬性,這個(gè)屬性其實(shí)是多余的,并不是我要的。

          image.png

          所以如果嚴(yán)格按照文檔來(lái),這種寫法肯定是錯(cuò)誤的,只是鉆了空子,沒(méi)報(bào)錯(cuò)而已。

          至于這種寫法為什么會(huì)不報(bào)錯(cuò),我本著認(rèn)真的鉆研精神開(kāi)始了下面的一系列分析,先從vue源碼來(lái)看。

          源碼分析

          源碼位置:src/core/observer/index.js

          我從vue2中找到set定義的完整代碼:

          /**
           * Set a property on an object. Adds the new property and
           * triggers change notification if the property doesn't
           * already exist.
           */

          export function set (target: Array<any> | Object, key: any, val: any): any {
            // target如果是`undefined`、`null`或是原始類型,則報(bào)錯(cuò)
            if (process.env.NODE_ENV !== 'production' &&
              (isUndef(target) || isPrimitive(target))
            ) {
              warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
            }
            // 如果target是數(shù)組且key是數(shù)組索引,則修改數(shù)組對(duì)應(yīng)鍵的值
            if (Array.isArray(target) && isValidArrayIndex(key)) {
              target.length = Math.max(target.length, key)
              target.splice(key, 1, val)
              return val
            }
            // 如果是對(duì)象且傳入屬性存在于對(duì)象中,則修改屬性值
            if (key in target && !(key in Object.prototype)) {
              target[key] = val
              return val
            }
            // 判斷是否是響應(yīng)式對(duì)象
            const ob = (target: any).__ob__
            // 如果是Vue對(duì)象或者是Vue實(shí)例的根數(shù)據(jù)對(duì)象,則報(bào)錯(cuò)
            if (target._isVue || (ob && ob.vmCount)) {
              process.env.NODE_ENV !== 'production' && warn(
                'Avoid adding reactive properties to a Vue instance or its root $data ' +
                'at runtime - declare it upfront in the data option.'
              )
              return val
            }
            // 如果是非響應(yīng)式的普通對(duì)象,則給上屬性值就可以了
            if (!ob) {
              target[key] = val
              return val
            }
            // 如果是響應(yīng)式對(duì)象,則調(diào)用defineReactive方法賦值
            defineReactive(ob.value, key, val)
            ob.dep.notify()
            return val
          }

          看源碼,我加上了備注,還是很容易看明白的。在我們這種條件下,target是數(shù)組但key不是索引值,代碼最后其實(shí)會(huì)走到defineReactive,那就順藤摸瓜繼續(xù)找。。

          源碼位置:src/core/observer/index.js

          class Observer {
              constructor (data) {
                  this.walk(data)
              }
              walk (data) {
                  // 遍歷 data 對(duì)象屬性,調(diào)用 defineReactive 方法
                  let keys = Object.keys(data)
                  for(let i = 0; i < keys.length; i++){
                      defineReactive(data, keys[i], data[keys[i]])
                  }
              }
          }

          // defineReactive方法僅僅將data的屬性轉(zhuǎn)換為訪問(wèn)器屬性即響應(yīng)式
          function defineReactive (data, key, val{
              // 遞歸觀測(cè)子屬性
              observer(val)

              Object.defineProperty(data, key, {
                  enumerabletrue,
                  configurabletrue,
                  getfunction ({
                      return val
                  },
                  setfunction (newVal{
                      if(val === newVal){
                          return
                      }
                      // 對(duì)新值進(jìn)行觀測(cè)
                      observer(newVal)
                  }
              })
          }

          // observer 方法首先判斷data是不是純JavaScript對(duì)象,如果是,調(diào)用 Observer 類進(jìn)行觀測(cè)
          function observer (data{
              if(Object.prototype.toString.call(data) !== '[object Object]') {
                  return
              }
              new Observer(data)
          }

          這段代碼的意思就是給響應(yīng)式屬性設(shè)置值,我加上了注釋,應(yīng)該容易看明白,不過(guò)重點(diǎn)在這一步:Object.defineProperty!請(qǐng)接著往下看:

          終極奧義?

          上面的代碼在調(diào)用Object.defineProperty方法時(shí),就會(huì)把對(duì)象類型的鍵值轉(zhuǎn)換為'[object Object]'類型的字符串,最后給數(shù)組加了一個(gè)'[Object object]'屬性。不明白的可以看下mdn上的defineProperty定義[2],然后走下這段代碼:

          let Person = [1,2]
          Object.defineProperty(Person, {sL:1}, {
             value'jack'// 屬性值
             writabletrue // 是否可以改變
          });
          Person.s = 2;
          console.log(Person) // logs  [1, 2, s: 2, [object Object]: "jack"]

          輸出的結(jié)果非常奇葩!直觀看起來(lái),[object Object]: "jack"似乎也是一個(gè)值,但是仔細(xì)一想又不是,因?yàn)樗皇且粋€(gè)對(duì)象,你把[1, 2, s: 2, [object Object]: "jack"]輸入控制臺(tái)是會(huì)報(bào)錯(cuò)的,但是Person['[object Object]']又可以取到值jack,然后你再看一下Person的length,你會(huì)發(fā)現(xiàn),長(zhǎng)度卻是2!我把Person展開(kāi)來(lái)看,發(fā)現(xiàn)是這樣的:

          image.png

          這樣看應(yīng)該比較明白了,你可以把Person理解為特殊的對(duì)象,數(shù)組本身的索引值也是這個(gè)“對(duì)象”的鍵值,"s"也是鍵值,[object Object]也是鍵值。為了謹(jǐn)慎起見(jiàn),我又打印了它的鍵值:

          Object.keys(Person)  // ["0", "1", "s"]

          看到這結(jié)果,是不是又被驚訝到了?剛剛明明似乎看到了[object Object]這個(gè)鍵值,結(jié)果在這里卻沒(méi)有被打印出來(lái)!?好吧,神奇的js。。。我猜也許是因?yàn)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">[object Object]這個(gè)鍵值的特殊性吧,而且你用for in 去遍歷Person的屬性,也是遍歷不到[object Object]的,也就是說(shuō)這個(gè)屬性不是可枚舉的。

          [object Object]

          最后的最后還是要說(shuō)一下[object Object],它是怎么來(lái)的呢?其實(shí)它是通過(guò)toString[5]方法得到的,mdn上對(duì)它是有說(shuō)明的,就是在默認(rèn)情況下任何對(duì)象調(diào)用toString()都會(huì)返回返回 "[object type]"。可以看下面的代碼:

          var s = new Object();
          s.toString() // [object Object]

          但是我們知道數(shù)組也有toString方法,它覆蓋了默認(rèn)的toString方法,所以并不會(huì)輸出"[object Array]",如果要輸出這個(gè)結(jié)果,可以這樣寫:

          var a = new Array();
          toString.call(a) 

          回到上一步,也就是說(shuō)Object.defineProperty實(shí)際上是把它的第二個(gè)參數(shù)強(qiáng)制toSring了,所以在文章最開(kāi)始的地方,我們執(zhí)行這句this.$set(array, object, ())會(huì)得到一個(gè)包含[object Object]屬性的數(shù)組。

          要不要提issue?

          好了,本文有點(diǎn)冷門,本來(lái)只是懷疑找到了vue的一個(gè)bug,結(jié)果搞出了這么多瓜來(lái),把我吃撐了都!但是話說(shuō)回來(lái),如果api規(guī)定了參數(shù)及類型,入?yún)麇e(cuò)了,即使執(zhí)行不報(bào)錯(cuò),從嚴(yán)格上來(lái)講也要警告才對(duì),所以,要不要提issue呢?

          參考資料:

            1. https://blog.csdn.net/leelxp/article/details/107212555

            2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

            3. https://www.jianshu.com/p/8fe1382ba135

            4. http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/

            1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString

            2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/in


          掃碼關(guān)注 字節(jié)逆旅 公眾號(hào),為您奉獻(xiàn)更多技術(shù)干貨!


          瀏覽 116
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  视频在线播放一区二区 | 欧美性爱导航 | 电影91久久 | 欧美成人操比 | 新天堂AV|