據說是面試題:由【if(a==1&&a==2&&a==3)】引發的思考探討
- 2020 年 9 月 26 日
- 筆記
- ES6, javascript, javascript基礎, js類型轉換
有一天,突然在一個微信群有個群友發了張圖片拋出了一道題,如圖:
下面,我們先還原原題:
說實話,我第一眼看到時居然理所當然地認為讓a=true或者a=!0應該就可以了,但是程式碼世界的種種複雜變數讓我不能輕易相信第一感覺,於是馬上打開電腦,在chrome瀏覽器的控制台快速敲下以下程式碼:
【a=true時】
1 var a=true; 2 if(a==1&&a==2&&a==3){ 3 console.log("猜想正確:"+a); 4 }else{ 5 console.log("猜想錯誤"); 6 }
【a=!0時】
然後列印結果讓我意識到錯誤的同時也激發了深入探索的興趣:既然猜想錯誤,那到底要怎麼做才能實現呢?背後的原理又會是怎樣的呢?帶著疑問,然後打開了百度上CSDN的一篇部落格文章開始看。
文章一開頭就說是一道有趣的面試題:Excuse Me?!驚愕之餘就仔細品讀了共有4種主要方法。接下來我結合自己的理解儘力將4種方法的原理給解釋一下,如有錯誤,請多多指正,謝謝各位~
【解法①:利用對象的類型裝換】
目的達到了,其中的原理是什麼呢?我們先看a==1&&a==2&&a==3,這是一個短路邏輯與運算符,這就表明只有左端條件為真能會繼續往右端進行判斷,否則立即整個判斷像短路一樣為假了,所以呢,a的第一個值必須是a==1為真之後才會進行第二步的a==2判斷,由此推斷a的值或者說是間接返回值(類型轉換後的值)應該是可以自增長的!另外,這種a==1的判斷,JavaScript中當遇到不同類型的值進行比較時,會根據類型轉換規則試圖將它們轉為同一個類型再比較。比如 Object 類型與 Number 類型進行比較時,Object 類型會轉換為 Number 類型。轉換為時會嘗試調用 Object.valueOf 和 Object.toString 來獲取對應的數字基本類型。
在上述的程式碼中,邏輯轉換先調用了valueOf方法,如果返回的還是對象,再接著調用toString()方法。每次比較都會先執行重寫後的對象方法toString(),這個方法里先返回屬性num的值再自增(區分:return a.num++表示先返回再自增,return ++a.num表示先自增再把結果返回)。知道了對象a的內部之後就能明白,執行a==1判斷時,對象a調用toString()方法返回了屬性num的值1,此時比較兩個當然是相等的。與此類似,a==2和a==3一樣成立。看到這裡是否有豁然開朗的感覺捏?
【解法②:利用數組的取值和類型轉換】
JavaScript里的數組真的是靈魂支柱,因為絕大多數的數據都在數組裡操作,因此很多時候解決問題的巧妙思路也能從它著手。下面先上程式碼和運行結果:
眨眼一看這個寫法莫名其妙讓人匪夷所思,當好好地理解了之後就霎時拍案叫絕,程式碼之簡潔優雅,思路之清奇獨到,堪稱膩害!我們知道在JavaScript中一切皆對象,那麼Array當然也是對象的子類了,同樣繼承了Object對象的方法valueOf()和toString(),而且重寫了toString()方法,在調用數組中的每個元素的 toString() 返回值經調用 join() 方法連接(由逗號隔開)組成。所以在這裡可以不重寫toString()方法了,只需要對join()方法進行處理即可。那麼join()方法作用扮演的是什麼角色呢?沒錯,它用來將數組各項通過連接符拼接起來形成字元串,它不會改變原數組僅僅是取出元素連接起來。shift()方法是會將數組的第一個元素刪除並返回被刪除的元素,換言之就好像是直接將數組的第一個元素移出數組,因此它改變了原數組的結構和長度,但是自身不會創建新的數組。
讓我們把目光聚焦到a.join=a.shift,這句話的意思是當數組調用toString()方法而間接調用join方法時,shift()方法替代了join方法,這樣就相當於每次從a數組中截取第一個元素返回。所以當判斷a==1時其實是從原數組截取了第一個元素的值返回後再判斷,這樣原數組就變成了[2,3],接著a==2判斷執行類似操作即可。怎麼樣,這個方法巧妙吧?有沒有被驚訝到捏?
【解法③:理由Object對象的defineProperty()方法定義屬性並重寫getter()方法】
同樣道理,Let’s show the code to see see!
可能有的人看到defineProperty()並不是很了解它的用處,我查了下MDN上的說法:Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回此對象。
首先,JavaScript的運行環境通常主要分為兩種:客戶端(瀏覽器)和服務端(node),這兩種環境下的全局對象管理所有的變數和函數,客戶端是window,node是global,在本例以window為參考。此時通過defineProperty()給window對象定義了一個a屬性,a屬性的值由get()方法返回後再自增。因此,當判斷a==1時,實際上是獲取省略掉window對象前綴的a的值後再比較。這個defineProperty()方法應當直接在 Object 構造器對象上調用此方法,而不是在任意一個 Object 類型的實例上調用。
【小擴展:defineProperty()方法有兩種給定義的屬性賦值的方法:數據描述符和存取描述符(有set或get方法)】
【解法④:利用Unicode字元編碼,這種方式沒什麼技術含量不必深究也不推薦,了解即可】
【解法⑤:利用ES6的類來實現】
ES6是引入了類的比較規範的寫法,我們可以在類的定義里做想做的事情,下面演示用傳統函數和類分別實現:
從上面的程式碼和列印結果看出,傳統函數和ES6類都藉助了Object自帶的valueOf()方法,只是二者在處理時不一樣:傳統函數被調用時valueOf()並沒有被立即調用,只是通過匿名函數的方式聲明了函數,真正調用valueOf()還是在執行判斷時隱式調用的;而ES6類則選擇了再構造函數里直接調用在類里重寫後的valueOf()方法。因此,兩者在定義變數num的初始值時需要注意一下!
通過上述的探討大體上就使用了5種解決方法,其中最簡潔優雅巧妙的當屬解法②數組對象方式,解法③方式屬於修改對象屬性,解法①和解法5的核心還是利用對象的內置方法valueOf()或toString()進行重寫值返回,解法④就權當看看了解吧~
OK,本次探討暫且到此為止,如有錯漏,歡迎指正,謝謝~
版權所有,轉載請註明出處!