一次關于使用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) 編輯 收藏 所屬分類: 測試學習專欄