對於JS == 運算的一些理解

来源:http://www.cnblogs.com/zhang1990/archive/2016/10/07/5935721.html
-Advertisement-
Play Games

聲明:本文是摘自一篇文章,放在這隻為做為一個筆記能更好學習。 大家知道,==是JavaScript中比較複雜的一個運算符。它的運算規則奇怪,容易讓人犯錯,從而成為JavaScript中“最糟糕的特性”之一。 在仔細閱讀了ECMAScript規範的基礎上,畫了一張圖,通過它你會徹底地搞清楚關於==的一 ...


聲明:本文是摘自一篇文章,放在這隻為做為一個筆記能更好學習。

 

家知道,==是JavaScript中比較複雜的一個運算符。它的運算規則奇怪,容易讓人犯錯,從而成為JavaScript中“最糟糕的特性”之一。

 

在仔細閱讀了ECMAScript規範的基礎上,畫了一張圖,通過它你會徹底地搞清楚關於==的一切。同時,試圖通過此文向大家證明==並不是那麼糟糕的東西,它很容易掌握,甚至看起來很合理。

 

先上圖:

圖1 ==運算規則的圖形化表示

 

==運算規則的精確描述在此:The Abstract Equality Comparison Algorithm(http://es5.github.io/#x11.9.3)。但是,這麼複雜的描述,你確定看完後腦子不暈?確定立馬就能拿它指導實踐?

 

肯定不行,規範畢竟是給JavaScript運行環境的開發人員看的(比如V8引擎的開發人員們),而不是給語言的使用者看的。而上圖正是將規範中複雜的描述翻譯成了更容易看懂的形式。

 

在詳細介紹圖1中的每個部分前,我們來複習一下JS中關於類型的知識:

  1. JS中的值有兩種類型:原始類型(Primitive)、對象類型(Object)。

  2. 原始類型包括:Undefined、Null、Boolean、Number和String等五種。

  3. Undefined類型和Null類型的都只有一個值,即undefined和null;Boolean類型有兩個值:true和false;Number類型的值有很多很多;String類型的值理論上有無數個。

  4. 所有對象都有valueOf()和toString()方法,它們繼承自Object,當然也可能被子類重寫。

現在考慮表達式:

x == y

其中x和y是上述六種類型中某一種類型的值。

 

當x和y的類型相同時,x == y可以轉化為x === y,而後者是很簡單的(唯一需要註意的可能是NaN),所以下麵我們只考慮x和y的類型不同的情況。

 

 1   有和無

 

在圖1中,JavaScript值的六種類型用藍底色的矩形表示。它們首先被分成了兩組:

  • String、Number、Boolean和Object (對應左側的大矩形框)

  • Undefined和Null (對應右側的矩形框)

 

分組的依據是什麼?我們來看一下,右側的Undefined和Null是用來表示不確定或者的,而右側的四種類型都是確定的非空。我們可以這樣說:

左側是一個存在的世界,右側是一個的世界。

所以,左右兩個世界中的任意值做==比較的結果都是false是很合理的。(見圖1中連接兩個矩形的水平線上標的false)

 

 2   空和空

 

JavaScript中的undefined和null是另一個經常讓我們崩潰的地方。通常它被認為是一個設計缺陷,這一點我們不去深究。不過我曾聽說,JavaScript的作者最初是這樣想的:

假如你打算把一個變數賦予對象類型的值,但是現在還沒有賦值,那麼你可以用null表示此時的狀態(證據之一就是typeof null 的結果是'object');相反,假如你打算把一個變數賦予原始類型的值,但是現在還沒有賦值,那麼你可以用undefined表示此時的狀態。

不管這個傳聞是否可信,它們兩者做==比較的結果是true是很合理的。(見圖1中右側垂直線上標的true)

在進行下一步之前,我們先來說一下圖1中的兩個符號:大寫字母N和P。這兩個符號並不是PN結中正和負的意思。而是:

  • N表示ToNumber操作,即將操作數轉為數字。它是規範中的抽象操作,但我們可以用JS中的Number()函數來等價替代。

  • P表示ToPrimitive操作,即將操作數轉為原始類型的值。它也是規範中的抽象操作,同樣也可以翻譯成等價的JS代碼。不過稍微複雜一些,簡單說來,對於一個對象obj:

ToPrimitive(obj)等價於:先計算obj.valueOf(),如果結果為原始值,則返回此結果;否則,計算obj.toString(),如果結果是原始值,則返回此結果;否則,拋出異常。

註:此處有個例外,即Date類型的對象,它會先調用toString()方法,後調用valueOf()方法。

 

在圖1中,標有N或P的線表示:當它連接的兩種類型的數據做==運算時,標有N或P的那一邊的操作數要先執行ToNumber或ToPrimitive變換。

 

 3   真與假

 

從圖1可以看出,當布爾值與其他類型的值作比較時,布爾值會轉化為數字,具體來說

true -> 
1false -> 0

這一點也不需浪費過多口舌。想一下在C語言中,根本沒有布爾類型,通常用來表示邏輯真假的正是整數1和0。

 

 4   字元的序列

 

在圖1中,我們把String和Number類型分成了一組。為什麼呢?在六種類型中,String和Number都是字元的序列(至少在字面上如此)。字元串是所有合法的字元的序列,而數字可以看成是符合特定條件的字元的序列。所以,數字可以看成字元串的一個子集。

 

根據圖1,在字元串和數字做==運算時,需要使用ToNumber操作,把字元串轉化為數字。假設x是字元串,y是數字,那麼:

x == y -> Number(x) == y

那麼字元串轉化為數字的規則是怎樣的呢?規範中描述得很複雜,但是大致說來,就是把字元串兩邊的空白字元去掉,然後把兩邊的引號去掉,看它能否組成一個合法的數字。如果是,轉化結果就是這個數字;否則,結果是NaN。例如:

Number('123') // 結果123
Number('1.2e3') // 結果1200
Number('123abc') // 結果NaN
Number('\r\n\t123\v\f') // 結果123

當然也有例外,比如空白字元串轉化為數字的結果是0。即

Number('') // 結果0Number('\r\n\t \v\f') // 結果0

 

 5   單純與複雜

 

原始類型是一種單純的類型,它們直接了當、容易理解。然而缺點是表達能力有限,難以擴展,所以就有了對象。對象是屬性的集合,而屬性本身又可以是對象。所以對象可以被構造得任意複雜,足以表示各種各樣的事物。

 

但是,有時候事情複雜了也不是好事。比如一篇冗長的論文,並不是每個人都有時間、有耐心或有必要從頭到尾讀一遍,通常只瞭解其中心思想就夠了。於是論文就有了關鍵字、概述。JavaScript中的對象也一樣,我們需要有一種手段瞭解它的主要特征,於是對象就有了toString()和valueOf()方法。

toString()方法用來得到對象的一段文字描述;而valueOf()方法用來得到對象的特征值。

當然,這隻是我自己的理解。顧名思義,toString()方法傾向於返回一個字元串。那麼valueOf()方法呢?根據規範中的描述,它傾向於返回一個數字——儘管內置類型中,valueOf()方法返回數字的只有Number和Date。

 

根據圖1,當一個對象與一個非對象比較時,需要將對象轉化為原始類型(雖然與布爾類型比較時,需要先將布爾類型變成數字類型,但是接下來還是要將對象類型變成原始類型)。這也是合理的,畢竟==是不嚴格的相等比較,我們只需要取出對象的主要特征來參與運算,次要特征放在一邊就行了。

 

 6   萬物皆數

 

我們回過頭來看一下圖1。裡面標有N或P的那幾條連線是沒有方向的。假如我們在這些線上標上箭頭,使得連線從標有N或P的那一端指向另一端,那麼會得到(不考慮undefined和null):

 7   舉個例子

 

前面廢話太多了,這裡還是舉個例子,來證明圖1確實是方便有效可以指導實踐的。

例,計算下麵表達式的值:

[''] == false

首先,兩個操作數分別是對象類型、布爾類型。根據圖1,需要將布爾類型轉為數字類型,而false轉為數字的結果是0,所以表達式變為:

[''] == 0

兩個操作數變成了對象類型、數字類型。根據圖1,需要將對象類型轉為原始類型:

  • 首先調用[].valueOf(),由於數組的valueOf()方法返回自身,所以結果不是原始類型,繼續調用[].toString()。

  • 對於數組來說,toString()方法的演算法,是將每個元素都轉為字元串類型,然後用逗號','依次連接起來,所以最終結果是空字元串'',它是一個原始類型的值。

 

此時,表達式變為:

'' == 0

兩個操作數變成了字元串類型、數字類型。根據圖1,需要將字元串類型轉為數字類型,前面說了空字元串變成數字是0。於是表達式變為:

0 == 0

到此為止,兩個操作數的類型終於相同了,結果明顯是true。

 

從這個例子可以看出,要想掌握==運算的規則,除了牢記圖1外,還需要記住那些內置對象的toString()和valueOf()方法的規則。包括Object、Array、Date、Number、String、Boolean等,幸好這沒有什麼難度。

 

 8   再次變形

 

其實,圖一還不夠完美。為什麼呢?因為對象與字元串/數字比較時都由對象來轉型,但是與同樣是原始類型的布爾類型比較時卻需要布爾類型轉型。實際上,只要稍稍分析一下,全部讓對象來轉為原始類型也是等價的。所以我們得到了最終的更加完美的圖形:

      

圖3 更完美的==運算規則的圖形化表示

 

有一個地方可能讓你疑惑:為什麼Boolean與String之間標了兩個N?雖然按照規則應該是由Boolean轉為數字,但是下一步String就要轉為數字了,所以乾脆不如兩邊同時轉成數字。

 

 9   總結一下

 

前面說得很亂,根據我們得到的最終的圖3,我們總結一下==運算的規則:

  • undefined == null,結果是true。且它倆與所有其他值比較的結果都是false

  • String == Boolean,需要兩個操作數同時轉為Number。

  • String/Boolean == Number,需要String/Boolean轉為Number。

  • Object == Primitive,需要Object轉為Primitive(具體通過valueOftoString方法)。

瞧見沒有,一共只有4條規則!是不是很清晰、很簡單。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SSH框架整合 一、原理圖 action:(struts2) 1、獲取表單的數據 2、表單的驗證,例如非空驗證,email驗證等 3、調用service,並把數據傳遞給service Service:業務層 增刪改查,比如:登錄,調用dao的query方法進行查詢,返回結果,進行用戶名密碼的比對,將 ...
  • 1、手動編譯 1.1、首先找到TypeScript的安裝目錄,我的在”C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.0“。 1.2、使用cmd工具命令cd到安裝目錄。 1.3、輸入命令:tsc 文件名.ts,回車編譯。 一旦編譯成功,就會在相同 ...
  • Css(Cascading Style Sheets,層疊樣式表)是一種頁面美化方法,通過編輯Css的對象屬性達到美化頁面的效果。Css的操作基本單元為對象,使用CSS的感覺就像是使用C++/C中的函數,CSS對象就像是函數,通過定義,聲明,調用來使用。 CSS有三種選擇器(對象定義和使用方式):標 ...
  • <select id="selector"></select> 1、設置value為pxx的項選中 $("#selector").val("xxx"); 2、設置text為pxx的項選中 $("#selector").find("option[text='xxx']").attr("selected ...
  • Json的語法及使用方法 Json(JavaScript Object Notation)對象表示標識,是一種輕量級的數據交換語言,比XML更容易解析,獨立於語言和平臺。 語法規則: 對象用{}保存 數據用鍵值對保存 數據間由逗號分隔 數組用[]保存 如下所示: {"id":1,"name":"jo ...
  • js中的text(),html() ,val()的區別 text(),html() ,val()三個方法用於html元素的存值和取值,但是他們各有特點,text()用於html元素文本內容的存取,html()不但可以用於html元素文本內容的存取,還可以用於html內容的存取。val()用於inpu ...
  • js中的回調函數的理解和使用方法 一. 回調函數的作用 js代碼會至上而下一條線執行下去,但是有時候我們需要等到一個操作結束之後再進行下一個操作,這時候就需要用到回調函數。 二. 回調函數的解釋 因為函數實際上是一種對象,它可以存儲在變數中,通過參數傳遞給另一個函數,在函數內部創建,從函數中返回結果 ...
  • js原生的迴圈有兩種,一般的for迴圈和for...in迴圈。還有一種常用jQuery.each()迴圈。 一. js原生迴圈 a. for迴圈,代碼如下: var myArray = [1,2,3];for (var i = 0; i < myArray.length; i++) { consol ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...