某天聘萨,公司代碼審查會(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)七兜。