qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          一次關于使用status作為變量引發的bug及思考

           這個bug出現在一年前,當時自己大學還沒畢業,剛剛進入一家公司實習。那個時候還沒有用seajs或者requirejs那樣的模塊化管理的庫,也沒有用一個自執行的函數將要執行的代碼包裹起來,于是bug就在這樣的一個場景下誕生了。當時自己定位了比較久,也不知道status是window下的一個屬性,所以請了高手幫忙定位,高手也是定位了半天才定位出來,只是湊巧將status換了一個名字就正常了,后來我問高手原因,他當時也答不出來,后來就一直沒管它了,也忘記了。就在前幾天,群里有人在討論一些bug以及要注意的一些坑,我突然想起了一年前自己遇到過的坑,于是提了出來,在各位高手的討論下終于搞懂了這個bug出現的原因以及原理,于是記錄下,方便那些跟我一樣做開發的同學能夠繞過這些坑,少走彎路。
            1.場景再現(為了方便最簡化代碼,當時的情景不像下面這么直白的提示錯誤):
            咦,為什么這里status是一個數組,為什么會提示它沒有push函數呢,這到底是為什么呢?這個bug對于當時初出茅廬的我來說簡直就是一個大挑戰,那個時候對調試還不熟,看到bug那個小心臟頓時就有點受不了了,緊張啊,抓狂啊隨之而來。因為當時代碼量比較多,所以當時不能一下子定位到這里的問題。
            2.討論
            我們看到就因為變量名不同,卻一個出錯一個正常,難道不能將一個數組賦值給status嗎?
            我們看到將一個數組賦值給status是完全沒有問題的,它是數組類型。既然是數組為什么就沒有push方法呢?這個時候我們不防打印下status的類型
            我們看到我們將一個數組賦值給status,按理說應該是object類型,可是這里卻是string類型,string類型沒有push方法,這時我們對于為什么報錯就沒有那么疑惑了。按這樣理解的話,就是在給status賦值時確實是將一個數組賦給了它,但是就是在讀取status時瀏覽器強制將status轉化成了字符串。我們不防在chrome控制臺看看。
            看來我們的猜測是對的,賦值成功,在取值的時候將status強制轉化為了字符串,那要是將一個對象賦值給status在讀取status的時候是不是也會將status轉化為json格式的字符串呢?
            我們看到,瀏覽器并沒有按我們的預期將它轉化為一個json格式的字符串,而是轉化為[object Object]這樣的東東,這不是我們經常用來判斷變量的類型嗎?一般我們會調用Object.prototype.toString.call(變量)來查看變量類型,因為使用typeof太不靠譜。于是我們猜到在將status轉化為字符串的時候是調用了toString方法。

           難道status只能是字符串嗎?想想status當初設計出來的初衷,它就是為了臨時在狀態欄展示一些用戶信息,所以必須是字符串。這樣理解的話就順理成章了。
            所以我們看到使用status來定義變量是不可行的,除非定義的status是string類型,但是有的人就說,經常用status,沒啥問題啊。
            看,用得挺好的,妥妥的啊。
            我們再來看另外一種情況。
            xx,報錯了,咋回事?在項目中,由于我們的疏忽,有時候定義的變量忘記寫var關鍵字都是時常有的事,在一個代碼量很龐大的應用中,定位這樣的一個bug肯定需要花費不少時間,而且很容易讓人抓狂。
            上面那種情況將一個數組賦值給status并調用push方法為啥不出錯,這里就涉及到javascript變量作用域的問題了,因為一個自執行函數就是一個作用域,系統在查找這個變量時是先在這個作用域內進行查找,找不到就往上一層作用域中查找,直到作用域的最前端,沒有找到就報錯提示變量is not defined。這里因為在當前作用域中申明了變量status,所以不會去window環境下去查找status變量,所以是ok的,但是下面這種情況因為沒有使用var進行變量的申明,所以status就會成為window下的變量,而status又是window下的一個固有屬性,取值的時候只能是string類型,從而沒有push方法,最終報錯。
            所以,為了不給自己制造那么多麻煩,在定義變量時應該盡量避免使用javascript中的關鍵字、保留字和window下的固有屬性進行命名,這些都是坑,實際項目中應該多注意避免。
            從以上分析中,我們看到全局的status可以設置,但是讀取的時候卻調用了toSting方法返回了字符串,這里我們可以利用es5提供的Object.defineProperty來模擬一下這種行為。代碼如下:
          var a = {};
          Object.defineProperty(a, 'm', (function () {
          var _a = 'xx';
          return {
          get : function () {
          return _a.toString();
          },
          set : function (v) {
          _a = v;
          }
          };
          })());
            利用Object.defineProperty方法,可以對一個變量或者屬性進行監控,當直接賦值給變量的時候就會調用set方法,當直接讀取變量的時候就會將調用tostring方法將變量轉化為字符串。
            我們看到我們模擬的行為和status默認的行為一模一樣。
            一年前遇到的bug今天才豁然開朗,這讓我意識到針對任何一個小的bug都不應該放過,而要報著打破沙鍋問到底的態度去探究,這樣才可以看到別樣的風景以及讓自己更加專業。

          posted on 2014-11-04 10:23 順其自然EVO 閱讀(241) 評論(0)  編輯  收藏 所屬分類: 測試學習專欄

          <2014年11月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 永宁县| 贡山| 昌吉市| 鄂州市| 淮安市| 五河县| 垫江县| 车致| 通化县| 辽宁省| 泰兴市| 彭阳县| 元谋县| 梁平县| 稻城县| 吴川市| 安宁市| 灌阳县| 清远市| 沅陵县| 巨野县| 虹口区| 怀仁县| 黄平县| 广水市| 托克托县| 土默特右旗| 昭苏县| 通江县| 廊坊市| 渝北区| 同江市| 岑巩县| 印江| 梧州市| 屯门区| 乐安县| 垫江县| 阆中市| 苍梧县| 巫山县|