iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
1
Modern Web

JavaScript基本功修煉系列 第 7

JavaScript基本功修練:Day7 - [] == [] 、[]==![]、{} == {}、{} ==!{} 的解釋

承接昨天講解傳址和傳值的文章,今天再來看看一些相關的例子:

  • [] == [] //false
  • [] == ![] //true
  • {} == {} //false
  • {} == !{} //false

踩坑的緣由 (可跳過)

事緣在練習BMI計算器的時候,這個網頁要用到localStorage去儲存使用者的BMI計算結果並顯示在網頁上。我打算在顯示在網頁上的每項記錄旁,都有一個刪除的按鈕給使用者刪除該項記錄,所以把click事件連到splice這個語法。如果使用者把所有記錄全都刪除掉時,我就想在內容塞入「請輸入體重和身高」這段字。

if(data == []){
    list.innerHTML = `<p class="instruction">請輸入體重和身高</p>`
}

但寫完之後卻沒有得出我要的結果,它並沒有塞那段文字。所以我就去consoledata現在是什麼,之後查到底data == []這個判斷式是不是true,這時候才發現原來不是。

雖然我轉用以下的方法就行,成功蒙混過關:

if(data.length == 0){
        list.innerHTML = `<p class="instruction">請輸入體重和身高</p>`
}

但踩過的坑還是要老老實實地去學習XD,所以就去google和請教大大,然後發現這個問題也牽涉到不少經典的坑,於是就借這篇文章好好整理自己的理解。

轉型規則

看了一些網上文章,不少人都在引用JavaScript 高级程序設計(第3版)中提及的規則,令我也不禁去翻翻原文:

Both operators do conversions to determine if two operands are equal (often called type coercion).

首先,作者有提及到type coercion這個概念,他指出在判斷相等性時,會先進行型別轉換,才去決定兩邊的運算數(operand)是否對等。

以下是作者列舉的規則:


我自己的理解:

  1. 如果其中一個運算數是布林(Boolean),它會先被轉為數值,才去判斷是否相等。false會轉為0,true會轉為1。
  2. 如果一個運算數是字串,另一個是字,會先將字串轉成數字,才去判斷是否相等。
  3. 如果一個運算數是物件,另一個不是,就會用valueOf()去取得物件的基本類型值,並按剛才提及的規則做比較。
  4. nullundefined是相等的。
  5. nullundefined是不能被轉型為其他值來去檢查是否相等。
  6. 如果其中一個運算數是NaN,相等操作符會回傳false,不相等操作符會回傳true。注意:即使兩個運算數都是NaN,相等操作符都會回傳false,因為按規則,NaN不會等於NaN
  7. 如果兩個運算數都是物件,就會比較它們是否屬於同一個物件。如果是,就回傳true,否則回傳false

再看看MDN的簡潔圖解:

資料型別

金魚腦忘了物件是什麼?先重溫一下資料型別:

  • 基本型別(Primitive types)
    numberstringbooleanundefinednullsymbol(ES6加入)。它的值叫基本類型值(Prmitive value)。
  • 物件型別(Object types)
    不是基本類型的就是物件。例如objectarrayfunction等等。它的值叫引用值(Reference value)。

注意:基本類型值和引用值存放在記憶體的做法是不同的。如果它是基本值,就會單純被存放到棧內存(stack),如果是引用值,它的值會存放到堆內存(heap),並在棧內存(stack)裏存放它在堆內存(heap)的地址。這個重點在上文有提及:

當一個變數被賦予一個值的時候,直釋器會基於它的資料型別,檢查它是基本還是引用值,再把該值存放到某個記憶體位置。

[] == [] 、{} == {}

[] == []會回傳false

根據剛才提及的第7點,如果兩個運算數是物件(陣列是物件),就會比較它們是否屬於同一個物件。但因為它們的地址不同,所以它們並非同一個物件。

雖然這裏的兩個[]都是空陣列,但其實它們被存放到不同的記憶體位置裏,兩者各有不同的地址。如果要作比較,因為它們的地址不同,指向的物件不同,所以會回傳false

{} == {}都會回傳false,這個問題的原因與剛才提到[] == []回傳false是一樣的。

好像懂了?但再看看這個例子,為什麼會回傳true呢?

這裏會回傳true是因為testB拷貝了testA引用值的地址,而它們的地址都是指向同一個引用值

如果修改了testBtestA都會被修改,因為它們的地址是一樣,指向的物件也一樣,所以兩者都會被改掉。

[] == ![]

另外一個常見的問題就是[]==![],答案是回傳true

初學JS的我,經常會用到!==!===,但就很少把!放在值的前面。之後我Google了一下它的用法,原來除了!,還有!!

!的意思:

  • 找出該值是屬於true(truthy)還是false(falsy)
  • 回傳該布林值的相反

例如0的布林值是false!0就會回傳true。1的布林值是true!1就會回傳false

!!的意思,就是相反再相反:

  • 找出該值是屬於true(truthy)還是false(falsy),把它相反
  • 再相反一次已經相反的布林值,換言之,就是一開始的布林值。

例如!!0會回傳false!!1會回傳true,把該值老老實實地轉回它應有的布林值。

不過,!!並不是另一個運算符,只是重複打多個!而己。

有興趣了解!可看看這篇

廢話講多了,回到正題,這條問題的解答很易理解:

  1. 因為邏輯非! 的優先級比等號==高,所以會先把 ![] 轉成布林值,而它的值會是false
  2. 之後比較 []==false ,根據上面的7點規則中第1點,false會是等於0,所以會變成[]==0
  3. []轉成基本值,用([]).toString()轉成"",把""轉成數字是0,所以是true

也可以參考MDN的圖:

參考運算符優先級

{} == !{}

雖然剛才[] == ![]true,但{} == !{}會回傳false

這條問題的邏輯與剛才提及的非常相似:

  1. 先把!{}轉為布林值,即是false
  2. false轉為0,程式碼會變成{}==0
  3. 當物件與數字作對比時,物件會被嘗試用.toString().valueOf(),轉為原生值,再去跟數字作對比:


截圖自MDN

試試把物件轉成原生值:

一頭霧水...所以它現在基本值是什麼?

我們比較一下:

這裏我們發現用valueOf()去轉型後,它是直接返回原本的空物件,它根本沒有轉型,所以我們可以略過這方法,我們直接看toString()的方法。{}可以被轉為"[object Object]"這個字串。

根據比較規則,如果是字串比較數字,即是這裏的"[object Object]" == 0,我們就要把"[object Object]"轉為數字:

  1. 變成:NaN == 0,結果是false,所以{}==!{}false

這一條問題最後部分的推斷是自己參考資料後再嘗試的方法,如有錯誤請不吝指教~~

總結

  • []==[]回傳false,因為兩個物件的地址不同。
  • {}=={}回傳false,原因同上。
  • []==![]回傳true,因為會先處理![],變成[]==false,再變成[]==0
  • {}==!{}回傳false,因為會先處理!{},變成{}==false,再變成{}==0。當物件與數值進行比較時,會嘗試用toString()valueOf()將物件轉型,因為valueOf()只會返回原本物件,所以用toString()把它轉成字串,再比較"[object Object]" == 0,回傳false

參考資料

JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )
JavaScript 深入了解基本类型和引用类型的值
JavaScript values: not everything is an object
JAVASCRIPT.INFO - Object to primitive conversion


上一篇
JavaScript基本功修練:Day6 - 傳址、傳值
下一篇
JavaScript基本功修練:Day8 - 算術運算子、賦值運算子、比較運算子
系列文
JavaScript基本功修煉31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言