Hsieh Ya Chu
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # JavaScript 的相等比較:`==`、`===` 和 `Object.is()` 在 JavaScript 中想判斷變數或對象是否相等有以下三種方法: - 一般相等(`==`) - 嚴格相等(`===`) - `Object.is()` 方法 前兩個[比較運算子](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_operators#comparison_operators)`==` 和 `===` 都可以拿來判斷比較對象是否相等,不過兩者的差別究竟在哪裡?又為什麼要這樣設計?是我一開始學習 JavaScript 感到有點混淆的地方,也是滿常見的面試考題。 ## `==` 與 `===` 的差別 > 「大部分情況下不建議使用 `==`,應該使用 `===`」 初學 JavaScript 的我們通常會直接看到這樣的結論,但這是為什麼呢?簡單來說 **`==` 不會進行型別檢查只會比較值,`===` 則會同時檢查型別與值**。 ```javascript console.log("1" == 1); // true console.log("1" === 1); // false ``` 看到這邊可能會覺得 `==` 不用管型別簡單明瞭又方便!但其實會因為型別轉型導致出乎意料的結果,即使是 `===` 也有一些特殊的例外狀況需要使用 `Object.is()` 來解決。 ## `==` 等於(Equal) 與 `!=` 不等於 `==` 等於(Equal)也稱作「鬆散/寬鬆相等(Loose Equal)」(相較於「嚴格相等」來說)。由於 JavaScript 是個[動態型別語言](https://hackmd.io/tW7ORGtMTCmKHtm8lA2WGA#Dynamic-amp-Weak-Typing),因此可以允許不同型別的值做比較。此外, `==` 也具有「對稱性(symmetric)」, `A == B` 的意思與 `B == A` 是相同的。 遇到不同型別的運算元時,會**先自動轉型(type conversion)再比較內容**。 ```javascript console.log("1" = 1); //true console.log(true = 1); // true ``` 不必管型別使用起來看起來方便許多,但⋯⋯事情真的不是憨人所想得這麼簡單! 來看看這幾個有點 tricky 的例子: ```javascript console.log(-0 == +0); console.log(null == undefined); console.log([1,2] == "1,2"); console.log([] == false); console.log([0] == false); console.log("\n" == false); console.log("0XA19" == 2585); ``` 以上的結果竟然都是 true! 到這邊你可能會跟我一樣頭痛。原來不同型別的值經過 **JavaScript 的隱式轉型(Implicit Type Coercion)** 反而會有預期之外的結果。 這也是為什麼通常來說更建議使用 `===`。(是不是突然又覺得 `===` 比較簡單了...) 以下來做個簡單的分類: ### 同型別之間的比較 如果 `Type(x)` 與 `Type(y)` 相同,執行的結果就跟使用嚴格相等 `x === y` 一樣。 - String: 兩個 string 每一個字元跟順序都相同時才是 true - Number: 兩個 number 的值相同時才是 true - `+0` 與 `-0` 在 `==` 被視為相等、在 `===` 視為不相等 - 只要任一運算元是 `NaN` 就是 false,`NaN` 不等於任何值(包含自己)(Ref: [Javascript - NaN(Not a Number)](/9FVpL8cLTM2jH6tBvUkwUA) - Boolean: 運算元都是 true 或都是 false 時才是 true - BigInt:兩個 BigInt 都有相同的值時才是 true - Symbol: 兩個 symbol 都引用相同的 symbol 值時才是 true - Object: 兩個 object 都指向相同的位置時才是 true :::info 要注意,JavaScript 的比較也會因為是不是原始型別(primitive type) 而有所差異。非原始型別(像是 object、array 或 class)比較的基準會是看他們**是否指向同一個參考(reference),而不是比較他們的值(value)**。 ```javascript let obj1 = { name: 'John' } let obj2 = { name: 'John' } let array1 = [1,2,3] let array2 = [1,2,3] console.log(obj1 == obj2); // false console.log(array1 == array2); // false ``` 這部分在下方 [Reference vs Value](#Reference-vs-Value) 會有進一步的說明。 ::: ### string 或 boolean 會轉為 number string 或 boolean 被拿來跟 number 比較時,會先透過 `Number()` 轉為 number,再跟另一個 number 比較。 #### string 與 number 比較 ```javascript console.log("" == 0); // true console.log(" " == 0); // true console.log("" == " "); // false ``` 上面這幾個例子可以看到: 1. `""` 被自動轉型成 0,因此 `"" == 0` 可以看作是 `0 == 0` ,兩值相等所以回傳 true 2. `" "` 即使包含了空白字元,但經過 `Number(" ")`自動轉型仍是 `0` ,因此也會回傳 true 3. `""` 與 `" "` 兩者型別相同(都是 string) 因此不需要轉型、直接比較內容,內容並不相等所以回傳 false。 #### boolean 與 number 比較 ```javascript console.log(true == 1); // true console.log(false == 0); // true ``` - true 被 `Number(true)` 轉成 1 - false 被 `Number(false)` 轉成 0 #### string 與 boolean 比較 string 與 boolean 都會先轉為 number 再作比較。 ```javascript console.log('0' == false); // true ``` 1. `'0'` 字串透過 `Number('0')` 轉為數字 `0` 2. `false` 布林值透過 `Number(false)` 轉為數字 `0` 3. `'0' == false` 經過轉型後變成 `0 == 0` ,兩值相同回傳 true ```javascript console.log('true' == true); // false ``` 1. `'true'` 字串透過 `Number('true')` 因為無法轉型成數字因此結果是 `NaN` 2. `true` 布林值透過 `Number(true)` 轉為數字 `1` 3. `'true' == true` 經過轉型後變成 `NaN == 1` --> 只要出現 `NaN` 就回傳 false ### null、undefined 的比較 運算元是 `null` 或 `undefined` 時的規則如下: 當其中一個運算元是 `null` 或 `undefined`,另一個運算元也必須是 `null` 或 `undefined` 才會回傳 true,否則一律回傳 false。 ```javascript console.log(null == undefined) // true console.log(null == 0); // false console.log(undefined == 0); // false ``` ### object 與 non-object 的比較 ```javascript console.log([] == 0); // true console.log([1] == 1); // true console.log([] == true); // false ([] 物件透過 .toString() 被轉為 '',再透過 Boolean('') 轉為 false) ``` 上述的例子們拆解後是這樣的: 1. `[] == 0` 轉型後 => `0 == 0` => true - 左邊的物件 `[]` 透過 `.toString()` 被轉為空字串 `''`,再透過 `Number('')` 轉為數字 `0` 2. `[1] == 1` 轉型後 => `1 == 1` => true - 左邊的物件 `[1]` 透過 `.toString()` 被轉為字串 `'1'`,再透過 `Number(1)` 轉為數字 `1` 4. `[] == true` 轉型後 => `0 == 1` => false - 左邊的物件 `[]` 透過 `.toString()` 被轉為空字串 `''`,再透過 `Number('')` 轉為數字 `0` - 右邊的布林值 `true` 透過 `Number(true)` 被轉為數字 `1` #### `{}` 的比較會回傳 error ```javascript console.log({} == true) // Uncaught SyntaxError: Unexpected token '==' ``` `{}` 不能被拿來比對,因為 `{}` 不代表物件,而是代表一個區塊程式碼。區塊程式碼屬於陳述式,不能拿來做比對。 :::info 關於物件被轉型成基本型態,這邊節錄 [MDN 裡的說明](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#type_coercion): > Objects are converted to primitives by calling its [@@toPrimitive]() (with "default" as hint), `valueOf()`, and `toString()` methods, in that order. > For `valueOf()` and `toString()`, if one returns an object, the return value is ignored and the other's return value is used instead; if neither is present, or neither returns a primitive, a TypeError is thrown. 我們可以得知物件轉換成基本型態的步驟為:先 `valueOf()` → 返回 object → 再 `toString()` ::: ### == 的隱式轉型(Implicit Type Coercion) 關於 Javascript 的轉型(Coercion)一查下去又是另一個大坑,決定另起一篇來談。[Javascript 的自動轉型(Coercion)](/3FpUUDKFSzKGFJCD4QzNnA)。 不過這邊找到這個滿有趣的網站,詳細展示了使用 `==` 時 JavaScript 是如何 step by step 針對 `x == y` 進行自動轉型與判斷:https://felix-kling.de/js-loose-comparison/ 這邊嘗試當個翻譯蒟蒻並歸納網站的說明: 1. 判斷型別:`Type(x)` 與 `Type(y)` 是否相同 --> 若型別相同則回傳嚴格比較的結果: `x === y` 2. 判斷 **`null` 與 `undefined`** - 如果 `x` 是 null、 `y` 是 undefined,回傳 true - 如果 `x` 是 undefined、 `y` 是 null,回傳 true 4. **字串或布林值 vs 數字**:如果是字串或布林值與數字相比較,會先透過 `ToNumber()` 將字串或布林值轉型成數字 5. **物件 vs 與非物件(字串、數字、Symbol)**:如果是物件(object)與非物件相比較,會先將物件透過 `ToPrimitive()`將物件轉型成基本型態 6. 回傳 false ## `===` 嚴格等於(Strict Equal)與 `!==` 嚴格不等於 顧名思義相較於寬鬆等於(`==`)會更嚴謹,`===` 會**先比較型別、再判斷內容**是否相等。上面提到幾個在 `==` 下會回傳 true 的例子,改用 `===` 後結果都是 false: ```javascript console.log("" === 0); // false console.log(true === 1); // false console.log(false === 0); // false console.log(null === undefined); // false console.log([1] === 1); // false ``` ### `===` 的例外情況 BUT! ~~人生中最厲害就是這個 BUT!~~ 其實 `===` 有兩種特殊的例外情況: - 比較 `+0` 與 `-0` 時,會回傳 true - `NaN` 不等於任何值(包含自己),因此 `NaN === NaN` 會是 false(關於 `NaN` 的詳細說明:[NaN (Not a Number)](/9FVpL8cLTM2jH6tBvUkwUA)) ```javascript +0 === -0; // true NaN === NaN; // false ``` ## `Object.is()` 同值比較 (same-value equality) 上述的特殊狀況可以在 ES6 改用 `Object.is()` 方法來解決。 ```javascript Object.is(value, value) ``` `Object.is()` 跟 `===` 唯一的差別只在於處理帶符號的 0 (例如 `+0`、`-0`)以及 `NaN` 值的時候。 ```javascript console.log(Object.is(+0, -0)); // false console.log(Object.is(NaN, NaN)); // true ``` `Object.is()` 在以下幾種情況成立時會返回 true: - 都是 `undefined` - 都是 `null` - 都是 `true` 或都是 `false` - 都是長度相同、字符相同、順序相同的字串 - 都指向相同的位置 - 都是 BigInt 且具有相同的數值 - 都是 symbol 且引用相同的 symbol 值 - 都是數字且 - 都是 `+0` 或都是 `-0` - 都是 `NaN` - 都有相同的值,並且非 `0` 也非 `NaN` ## 零值相等 1. null 和 undefined ```javascript null == undefined // true null === undefined // false Object.is(null, undefined) // false ``` 2. NaN ```javascript NaN == NaN // false NaN === NaN // false Object.is(NaN, NaN) // true ``` 3. 0 和帶符號的 0(+0 或 -0) ```javascript 0 == -0 // true 0 === -0 // true Object.is(0, -0) // false ``` ## Reference vs Value 除了轉不轉型,這邊還想要特別補充 JavaScript 的比較基準也會因為型別不同而有所差異。 如果運算元是一般的**原始型別(primitive type)**,例如字串或數字,就是直接**比較它們的值是否相等**;如果運算元是陣列(array)、物件(object)或 class 等**非原始型別**時 ,那麼比較的基準就會是看他們**是否指向同一個參考(reference),而不是比較他們的值(value)**。 ```javascript // 運算元是陣列 var a = [1,2,3,4,5] var b = [1,2,3,4,5] var c = a console.log(a == b) // false console.log(a === b) // false console.log(Object.is(a, b)) // false console.log(a === c) // true // 運算元是物件 var obj1 = { name: 'John', age: 18 } var obj2 = { name: 'John', age: 18 } var obj3 = obj1 console.log(obj1 == obj2) // false console.log(obj1 === obj2) // false console.log(Object.is(obj1, obj2)) // false console.log(obj1 === obj3) // true ``` 內容一模一樣的 array 跟 object 不管用 `==`、`===` 還是 `Object.is()` 統統都是 false。 只有在兩者指向同一位置的時候(`var c = a` 或 `obj1 = obj3`),才會讓兩者在進行相等比較的時候是 true。 為什麼不是根據內容來判斷是否相等呢?以下直接引用我覺得講得很清楚的這篇「[我知道 == 與 === 不同,但為什麼要這樣設計?](https://f2e.kalan.dev/javascript-basic/4.html#reference-v-s-value)」的內容。 先定義所謂的「相等性」: > 在程式語言當中,相等性通常可以分為三種: > 1. Reference:兩個值指向的指標相同 > 2. Shallow Compare:兩個物件的 attributes 長度以及名稱相同,且屬性的 reference 也相同。 > 3. Deep Equal:兩個物件的 attibutes 長度、名稱相同,值也相同,如果有巢狀結構,會遞迴持續比較。 如果不用指標、而是想要進一步比較物件裡面的內容,需要定義要比較到「多深」(物件可能有好幾層,也可能遇到 circular reference 的問題),是要 shallow compare 還是要 deep equal?Deep equal 又可能會遇到效能問題。 因此: > 如果不用指標(reference)來判斷相等性,程式語言不知道要怎麼幫你判斷兩個物件是否相等 > 最簡單也最有效的方式就是直接用 reference 判斷 至於要如何做 Shallow Compare 或 Deep Equal 呢?一般來說會自己寫函式來定義到底要比較到多深,或使用 `JSON.stringify()` 來比較。(延伸閱讀:[How to compare objects in JavaScript?](https://codedamn.com/news/javascript/compare-objects-in-javascript)) ## 補充 [**JS Comparison Table (dorey.github.io)**](https://dorey.github.io/JavaScript-Equality-Table/) 透過這個比較表可以查看各種運算元在 `==` 和 `===` 的結果,以及 `if` 判斷式會將哪些情況視為 true 或 false。 [**MDN 的 equality methods 比較表**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#comparing_equality_methods) ![](https://hackmd.io/_uploads/S1D3Hi9JT.png) ## Ref - [我知道 `==` 與 `===` 不同,但為什麼要這樣設計?](https://f2e.kalan.dev/javascript-basic/4.html#reference-v-s-value) - [在 JavaScript 當中,==、=== 與 Object.is()的區別](https://www.explainthis.io/zh-hant/swe/js-equality) - [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness) - [JavaScript 核心篇 學習筆記: Chap.24 — 寬鬆相等、嚴格相等以及隱含轉型](https://medium.com/@yining1204/javascript-%E6%A0%B8%E5%BF%83%E7%AF%87-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-chap-24-%E5%AF%AC%E9%AC%86%E7%9B%B8%E7%AD%89-%E5%9A%B4%E6%A0%BC%E7%9B%B8%E7%AD%89%E4%BB%A5%E5%8F%8A%E9%9A%B1%E5%90%AB%E8%BD%89%E5%9E%8B-f0794db394c8)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully