關(guān)于for...in循環(huán)、var寒跳、塊作用域引發(fā)的思考

某天聘萨,公司代碼審查會(huì)的時(shí)候,從某同事的代碼里發(fā)現(xiàn)這樣的代碼:

for(var i in this.pointObject){
    if(i == editCircle.id){
        for(var i in this.pointObject){
            if(editCircle.overlay.id == this.pointObject[i].shapeId){
                this.$emit("editShape", this.pointObject[i][editCircle.overlay.id]);
                this.editRangeCircle(editCircle,1);
            }
        }
    }
}

這段代碼的運(yùn)行從實(shí)現(xiàn)的功能來(lái)看并不會(huì)出現(xiàn)bug童太、但是從業(yè)務(wù)邏輯與代碼可讀性米辐、js的語(yǔ)法問(wèn)題、執(zhí)行性能方面來(lái)看书释,都頗具有槽點(diǎn)翘贮,于是,提出了修改要求爆惧,但是同事說(shuō):“這樣子寫(xiě)沒(méi)事狸页,塊作用域〖旒ぃ”肴捉。看他那堅(jiān)定的態(tài)度叔收,以及各種原因齿穗,有一瞬間,我甚至懷疑了自己對(duì)js的var聲明與塊作用域的理解饺律,雖然很快就打消了窃页,但是我還是決定自己驗(yàn)證一遍、順便做個(gè)完整复濒、說(shuō)服力的JavaScript語(yǔ)法科普脖卖。以下的內(nèi)容會(huì)將這塊代碼稱(chēng)為代碼A;

  • 關(guān)于塊作用域
    我們知道塊作用域是作用于{// 代碼塊}這樣的代碼塊中的作用域巧颈,使用過(guò)java畦木、c、C#燈語(yǔ)言的小伙伴肯定不陌生砸泛,但是早期的javaScript并不具備有塊作用域十籍,只有全局作用域、函數(shù)作用域唇礁,在ECMAScript 6后才新增了塊作用域的實(shí)現(xiàn)勾栗,并且var定義的變量,是沒(méi)有塊的概念的盏筐,他是可以跨塊訪(fǎng)問(wèn)围俘,此處我們以這樣的代碼塊來(lái)證明這一結(jié)論:
var i = 3;
for(var i = 0; i < 5 i++) {}
console.log(i); // 輸出5

如果此處for中聲明的i具有塊作用域,此時(shí)輸出的i的值不應(yīng)該為5,而應(yīng)該是3,所以很明顯的界牡,for循環(huán)中的i泄露到了全局作用域簿寂。有的小伙伴可能會(huì)問(wèn),為什么會(huì)這樣呢欢揖,這就涉及到javaScript的編譯原理了陶耍。簡(jiǎn)而言之,js在運(yùn)行前她混,會(huì)將用var聲明的變量進(jìn)行提升烈钞,所以即使先輸出后聲明,也只是會(huì)輸出undefined并不會(huì)報(bào)錯(cuò)坤按。具體的編譯順序可以參考《你不知道的javaScript·上》第一章的內(nèi)容毯欣。
此處我們回到開(kāi)頭,代碼A中兩個(gè)for循環(huán)都用i來(lái)進(jìn)行循環(huán)臭脓,是會(huì)發(fā)生變量泄露的酗钞,不信看以下的例子

for(var i = 0; i < 5; i++) {
    if(i == 3) {
        for(var i = 0;i < 5; i++) {
        }
    }
    console.log(i); // 輸出結(jié)果為 0、1来累、2砚作、5
}

很明顯的,第二個(gè)for循環(huán)的i污染了第一個(gè)for循環(huán)的i嘹锁,導(dǎo)致輸出的結(jié)果為0葫录、1、2领猾、5米同,如果沒(méi)有發(fā)生變量泄露我們輸出的結(jié)果應(yīng)該是0、1摔竿、2面粮、3、4继低,不信的小伙伴可以用let來(lái)進(jìn)行測(cè)試

for(var i = 0; i < 5; i++) {
    if(i == 3) {
        for(let i = 0;i < 5; i++) {
        }
    }
    console.log(i); // 輸出結(jié)果為0熬苍、1、2袁翁、3冷溃、4
}

綜上所述,我們可以知道梦裂,代碼A所聲明的var i并不具備塊作用域,是存在變量泄露的問(wèn)題的盖淡,但是細(xì)心的伙伴會(huì)說(shuō)年柠,代碼A的循環(huán)是使用 for...in...,并不是使用普通的for循環(huán),所以我在證明、測(cè)試的時(shí)候也使用了 for...in...冗恨,測(cè)試代碼如下

obj = {1: 'a',2: 'b',3: 'c',4: 'd',5: 'e'};

for(var i in obj) {
    if(i == 3) {
        for(i in obj) {
            console.log(i)
        }
        
    }
    console.log(i, "?");
}
// 輸出結(jié)果如下
// 1 ?   2 ?   1   2   3   4   5   5 ?   4 ?   5 ?

輸出的結(jié)果可以說(shuō)是在意料之中答憔、也在意料之外(涉及到了for...in循環(huán)的不同)。
從輸出結(jié)果來(lái)看掀抹,我們可以看到在第二層for里輸出了1虐拓、2、3傲武、4蓉驹、5之后,外部的console.log輸出了5 ?,所以很明顯的此時(shí)第二層循環(huán)中的var i變量是不具備塊作用域的揪利,他泄露到了外部态兴。所以這里的結(jié)論與之前的結(jié)論都是一樣的:

var聲明的變量不具備塊作用域的特性!

但是細(xì)心的小伙伴會(huì)發(fā)現(xiàn)疟位,此時(shí)在輸出5 ?之后瞻润,外面的循環(huán)并沒(méi)有結(jié)束,而是繼續(xù)進(jìn)行循環(huán)甜刻,輸出了4 ? 5 ?,這個(gè)就是涉及到了我上面提到關(guān)于for...in...循環(huán)的不同

  • for...in...循環(huán)
for (variable in object)
  statement

MDN中提到绍撞,for...in...循環(huán)是以任意順序遍歷一個(gè)對(duì)象的除Symbol以外的可枚舉屬性,在每次迭代時(shí),variable會(huì)被賦值為不同的屬性名得院。這句話(huà)以及上面的現(xiàn)象傻铣,我們可以獲得一個(gè)結(jié)論,在variable in object這句代碼中尿招,variable是我們外部循環(huán)所聲明的變量矾柜,但是for...in...內(nèi)部是有自己的作用域的,所以即使我們?cè)谘h(huán)內(nèi)部修改了值就谜,并不會(huì)影響for...in...循環(huán)的循環(huán)次數(shù)以及某種順序怪蔑。for...in...會(huì)取出對(duì)象的所有可枚舉的屬性名(除了Symbol以外),每次循環(huán)將其中一個(gè)屬性名賦值給變量variable 丧荐,從而運(yùn)行循環(huán)體缆瓣。

for(var i in this.pointObject){
    if(i == editCircle.id){
        for(var i in this.pointObject){
            if(editCircle.overlay.id == this.pointObject[i].shapeId){
                this.$emit("editShape", this.pointObject[i][editCircle.overlay.id]);
                this.editRangeCircle(editCircle,1);
            }
        }
    }
}

最后我們?cè)诟鶕?jù)上述的js基礎(chǔ)對(duì)代碼A進(jìn)行總結(jié),在代碼A中虹统,第一層循環(huán)會(huì)在i == editCircle.id時(shí)進(jìn)行第二層循環(huán)弓坞,此時(shí)i在第二層for...in循環(huán)時(shí),依舊是從this.pointObject里的第一個(gè)屬性名開(kāi)始進(jìn)行遍歷的车荔,在第二層for...in循環(huán)結(jié)束后渡冻,代碼A并不會(huì)結(jié)束循環(huán),而是繼續(xù)第一層的循環(huán)忧便,直到結(jié)束族吻。所以在此處第一層循環(huán)顯得冗余、多余、是不必要的循環(huán)超歌。
從代碼的可讀性砍艾、邏輯、性能方面出發(fā)巍举,這段代碼其實(shí)可以簡(jiǎn)化為

if(this.pointObject.hasOwnProperty(editCircle.id)) {
    for(var i in this.pointObject) {
        if(editCircle.overlay.id == this.pointObject[i].shapeId){
            this.$emit("editShape", this.pointObject[i][editCircle.overlay.id]);
            this.editRangeCircle(editCircle,1);
        }
    }
}

這樣我們能從代碼很快的理解到脆荷,當(dāng)this.pointObject中含有editCircle.id值的屬性名時(shí),我們才需要循環(huán)變量懊悯,從而做一系列的處理蜓谋。
在《你不知道的javascript》中,作者提到:

不滿(mǎn)足于只是讓代碼正常工作定枷,而是要弄清楚“為什么”孤澎。

在我的思想里,團(tuán)隊(duì)工作中欠窒,代碼審查應(yīng)該重點(diǎn)關(guān)注“我需要完全理解這部分代碼才能確保它能夠正常工作,如果由我來(lái)修復(fù)代碼中的問(wèn)題,我是不會(huì)這么寫(xiě)的,因此希望你也不要這么來(lái)寫(xiě)”覆旭。所以才有了這篇文章。使代碼具有健壯性岖妄、簡(jiǎn)潔性型将、可讀性、以及更好的性能荐虐,我覺(jué)得是每個(gè)程序員應(yīng)該有的態(tài)度與目標(biāo)七兜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市福扬,隨后出現(xiàn)的幾起案子腕铸,更是在濱河造成了極大的恐慌,老刑警劉巖铛碑,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狠裹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡汽烦,警方通過(guò)查閱死者的電腦和手機(jī)涛菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撇吞,“玉大人俗冻,你說(shuō)我怎么就攤上這事‰咕保” “怎么了迄薄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)煮岁。 經(jīng)常有香客問(wèn)我噪奄,道長(zhǎng)死姚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任勤篮,我火速辦了婚禮,結(jié)果婚禮上色罚,老公的妹妹穿的比我還像新娘碰缔。我一直安慰自己,他們只是感情好戳护,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布金抡。 她就那樣靜靜地躺著,像睡著了一般腌且。 火紅的嫁衣襯著肌膚如雪梗肝。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天铺董,我揣著相機(jī)與錄音巫击,去河邊找鬼。 笑死精续,一個(gè)胖子當(dāng)著我的面吹牛坝锰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播重付,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼顷级,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了确垫?” 一聲冷哼從身側(cè)響起弓颈,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎删掀,沒(méi)想到半個(gè)月后翔冀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爬迟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年橘蜜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片付呕。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡计福,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出徽职,到底是詐尸還是另有隱情象颖,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布姆钉,位于F島的核電站说订,受9級(jí)特大地震影響抄瓦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陶冷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一钙姊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧埂伦,春花似錦煞额、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至基跑,卻和暖如春婚温,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳否。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工栅螟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逆日。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓嵌巷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親室抽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搪哪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348