JavaScript Weird Part(20) - 比較運算子、強制型轉的常見用途與問題

型轉時要注意的問題

JavaScript 是「弱型別語言」,因此具有語法簡潔的優點,但要注意型態轉換時產生非預期的問題。

1
2
console.log(1 < 2 < 3);
true;

以上的小於運算子範例,我們會預設結果為 true

1
2
console.log(3 < 2 < 1);
//true

但是這個例子竟然也是 true ? 這可以用運算子優先性和相依性來解釋。這兩個運算子的優先性一樣,所以用相依性來決定執行順序,所以相依性是從左到右,左邊先執行。所以是 3<2 先執行,並且得出 false

1
2
console.log(false < 1);
//true

接下來,因為運算子遇到兩個不同型別的比較,這裡是一個布林值與數字,所以布林被強制型轉為數字。

1
2
3
4
Number(false);
//0
Number(true);
//1

我們可以用內建函數查詢,發現 false 被型轉為數字 0。所以 0<1 得到為 true 的結果,因為由相依性決定的函數運算子執行順序及值的強制型轉。所以需要注意的是,Javascript 並不是以我們直覺的邏輯在運作,強制型轉可能會得出一些人類直覺看來詭異的結果,但在 Javascript 卻是非常正常的狀況。NaN 表示有一個東西想要轉換成數值型別,但他不是數字,所以無法型轉。

1
2
Number(undefined);
//NaN

比較運算子

所以我們在特殊狀況下不要型轉不就好了嗎?只要看兩者是否相等即可?

1
2
3
4
3 == 3;
//true
"3" == 3;
//true

但這會造成一些奇怪的狀況,例如以下很難除錯的情況

1
2
3
4
5
6
7
8
9
10
11
12
13
false == 0;
//true

var a = false;
//undefined

a == 0;
//true

false == 0;
//true
null == 0;
//false

為了避免這種狀況,可以用 === (強制等於)或 !== (強制不等於) 來避免強制型轉的狀況。 三個等號這個內建函數會判定兩者是不同型別,所以不相等,這可以避免一些奇怪的潛在錯誤。

1
2
3
4
false == 0;
//false;
"3" == 3;
//false

如果只用雙等號,以下兩個值就會被強制型轉,如果用三等號,因為他們型別不同就可以避免意外的狀況

1
2
3
4
5
6
7
8
var a = 0;
var b=false;
if(a ===b){
console.log('they are equal');

}elsw{
console.log('Nope, not equal');
}

因此注意最好用三等號來比較相等性。

動態型別和強制型轉到底有什麼功能?所以我們可以用強制型轉的特性,來檢查變數有沒有值。

1
2
3
4
5
6
Boolean(undefined);
//false
Boolean(null);
//false
Boolean("");
//false

以上這些東西都表示不存在,所以被布林轉換成 false。然而,這些特性是可以利用的嗎? 現在我們宣告 var a 但不給值

1
2
3
4
5
6
var a;

if (a) {
console.log("something is there");
}
//undefined

在陳述句括號裡的東西,會轉換成布林,所以 a 是什麼不重要。如果 a 是上述三個任何一種,那麼這個 if 陳述句會不成立,因為 a 不存在。

因為在執行環境的創造階段,會在記憶體為這個變數建立位置,然後會設定它的初始值為 undefined,而 undefined 會被轉換為 false

所以我們可以用強制型轉來檢查 a 是否存在。如果 a 因為沒有設值所以是 undefined,或者 anull,檢查的時候不會出現任何東西。

但是當我有比如字串的東西,不是空的時候。

1
2
3
4
5
6
7
8
var a;

a = "hi";

if (a) {
console.log("something is there");
}
//something is there

強制型轉的危險

如果 a=0 變數最後的值為 0 ,它也是 fasle。就會出現問題,因為 0 不是不存在,這是一個有效的值。

1
2
3
4
5
6
7
var a;

a = "0";

if (a || a === 0) {
console.log("something is there");
}

如果檢查運算子優先性和相依性,|| 的優先性比=== 低,所以完全相等函數會比||執行。但是如果值不太可能為 0 的話。以這種方式檢查,看它是不是存在就好。這個利用強制型轉的模式去檢查東西是否存在,例如 debug 的時候。

1
2
3
4
5
6
7
var a;

a="1"

if (a) {
console.log('something is there');
}

運算子的強制型轉特性,在一些框架或 JS 程式裡究竟有什麼比較常見的用處?

1
2
3
4
5
function greet(name) {
console.log("hello" + name);
}

greet();

假使呼叫 greet 的時候 沒有參數,在 Javascript 並不會報錯,它會當做
name 沒有東西。所以如果 console.log(name)

1
2
3
4
5
6
7
8
function greet(name) {
console.log("name");
console.log("hello" + name);
}

greet();

//helloundefined

結果出現 undefined,因為函數被呼叫的時候,一個新的執行環境被創造,然後這個變數name 是在函數內被創造,雖然傳入的值是在呼叫階段被設定,但這個變數在記憶體內已被設定為 undefined。所以加號運算子,當看到字串加上一個變數和值為 undefined + name,它會將純值 undefined 強制型轉為字串。

既然我們已經了解運算子和強制型轉如何作用,所以雖然這非理想的行為,但也非完全無法預期的行為。

常見用途:設定預設值

如果 undefined 是函數被呼叫時最終的值,因為運算子只是會回傳值的函數,所以這個 || 運算子不只是回傳 true 或 false,它的特殊行為是如果傳入 2 個可以被型轉為布林值的,它會回傳其中一個被轉型為 true 的值。

1
2
3
4
5
6
7
8
undefined || hello;
//hello
Boolean("hello");
//true
0 || 1;
//1
null || hello;
//hello

所以可以用這個運算子來表示,如果一個東西不存在、null 或空字串,就給我另一個。運用|| 運算子讓函數傳入的參數假如沒有值或不存在、空字串等狀況時,就回傳預設值給等號運算子。因為優先性,所以 || 會比等號先執行。

1
2
3
4
5
6
7
8
function greet(name) {
name = name || "<Your name here>";
console.log("hello" + name);
}

greet("tony"); //hellotony
greet(); //hello<Your name here>
greet(0); //hello<Your name here>

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2020 CYC'S BLOG All Rights Reserved.

UV : | PV :