iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 6
0
Modern Web

【這些年我似是非懂的 Javascript】系列 第 6

【這些年我似是非懂的 Javascript】Day 6 - 值

嗨各位好,我是 Robin,
今天來分享 JS 中幾個內建值型別,
希望我們能夠一起完全的理解且正確地善用他們~

陣列

可以儲存任何型別的容器

JS 的陣列特別的是不像其他強制施加型別的語言,他是可以儲存任何型別的容器,不管是 stringobjectnumber或著是另外一個 array

const a = ['1',{test:1},1,[0,1,2,3]];

a.length; // 4
a[0]; // '1'
a[1]; // {test:1}
a[2]; // 1
a[3]; // [0,1,2,3]

不用預設大小

在 JS 中你不必預先設定好 array 的大小,直接宣告就可以用了。

const a = [];

a.length; // 0
a[0] = 0;
a[1] = 1;
a[2] = 2;
a[3] = 3;

a.length; //4
console.log(a); // [0,1,2,3]

盡量別建立出有稀疏的 Array

const a = [];
a[0] = 1;
a[2] = 2;
a[4] = 4;

console.log(a[1]); // undefined
a.length; 5

這是可以 work 的,但是這樣可能會造成維護的人困擾,因為空著的是 undefined ,可是這跟直接使用 a[1] = undefined 沒有區別 ,嚴重可能會令你的同伴找 bug 找到瘋掉。

Key 值的強制轉型

在我們使用陣列時如果 key 是一個 string 並且能夠被強制轉型成十進位的 number 那 JS 就會強制將它變成 number 當成索引。

const a = [];

a['8'] = 8

a.length; // 9
a[8]; // 8

陣列其實可以額外增加特性

因為他是根本還是 object 所以其實你還是可以透過 stringkey 值來新增特性,但他並不會計算在 array 的長度裡。
然後...拜託先不要 xDD,如果你想要新增這樣的資料請使用 object ,把陣列留給使用索引的孩子。

const a = [1,2,3,4];

a['robin'] = '666';

a.length; // 4
a.robin; // '666'

類陣列 (Array-like)

什麼是類陣列?
就是長得跟陣列很像,但是他可能是串列(List)之類的,例如你在 DOM 操作之後回傳的其實是串列。
但是畢竟他不是真的 Array,所以你需要轉乘真正的 Array才能夠使用 indexOfconcatforEach 等的操作。
可以藉由使用 slice 或是 Array.from 來轉成真的 Array

字串

對於字串很多看法是由字元組成的 Array, 但是實質上他是,因為 JS 的 String 是不可變的,但是 Array是可變的

var a = 'foo';
var b = ['f','o','o'];

a[1]='g';
b[1]='g';

console.log(a); // 'foo'
console.log(b); // [['f','g','o']

在 JS 中我們可以直接更改陣列的內容,但是字串無法。
如果你很常需要將字串當陣列,那你應該實際直接將他們儲存為 array而不是字串,當你需要用到 string 表示時再加上 join('')就好了。

數字

JS 只有數值型別,並沒有整數的型別,所以 111111.0 對 JS 來說都是個整數。

十進位值前面的部分0他是選擇性的

const a = 0.42;
const b = .42;

a === b // true

後面的小數也是

const a = 42.0;
const b = 42;
const c = 42.0000000;

a === b // true
b === c // true

但以上都盡量不要使用,如果你不想要人要別人看你的 Code 會頭痛的話xD

奇妙的 "."

. 這個運算子是一個有效的數值字元,他會先被解讀成 number 字面的一部分,而不是解讀成特性存取器。
來看一下以下的範例

69.toFixed(2)

猜猜結果是什麼~

69.00

錯!

你會獲得一個 SyntaxError

但是當你使用以下的方式就可以正常 Work

69..toFixed(2)
69 .toFixed(2)
(69).toFixed(2)

Why?
因為如果 . 這個特性運算子不存在就無法取用 .toFixed

但還是不要使用有爭議的方式啦,畢竟程式碼是要靠人維護的 xD

指數形式的 number

主要是用來表示比較大的數字

const a = 1e4 // 10000 代表 1*10^4
const b = 1e8 // 100000000 代表 1*10^8

可用於其他基數的 number

例如 二進制、八進制、十六進制,這些格式都可以使用,但是注意盡量使用小寫的前綴,例如 0x0o 以及 0b,來避免 0O這類型造成的混淆。

超級蛋疼的小的十進位值


為什麼蛋疼...?
來!

0.1 + 0.2 //?

請各位評評理正常小學算法,大家都知道是 0.3 對吧!?
實際上...

對!沒錯你沒看錯。

對於使用 IEEE 754 的所有語言都是。
很多人提出過替代的方法,
但是總是沒有被選用,可能不是像我們用說的用想的那麼容易,
如果可以那早就已經被修復了,甚至其他語言也會使用,
但是事實就是沒有 QQ

結論

由此可知,我們不能相信 Number 的值是精確的,所以我們都不要使用它。

...

當然不可能啊 xDDD
我們可以放心的使用 JS 來處理整數,但是不要大於數百萬或數兆的數字。
另外如果如果真的有需要使用 JS 來比較兩種值的大小,
最廣為接受的是約整誤差,也就是容許很小很小的誤差值作為容許值。

ES6 中已定義好這個常數 Number.EPSILON 其值為 2^-52

使用方式如下

function closeEqual(n1, n2) {
  return Math.abs(n1 - n2) < Number.EPSILON;
}

const a = 0.1 + 0.2;
const b = 0.3;

closeEqual(a, b); // true
closeEqual(0.0000001, 0.0000002); // false

能夠被表示的最大浮點數值是 1.798e+308,並已經被定義好為 Number.MAX_VALUE
極小值則是 5e-324,非常趨近於零而且不是負的。

安全的整數範圍

那知道了蛋疼的小數之後,整數的安全範圍在 ES6 中已經定義了
最大值定義為了 Number.MAX_SAFE_INTEGER9007199254740991
最小值 Number.MIN_SAFE_INTEGER-9007199254740991

測試整數

測試一個值是否為整數

Number.isInteger(69); // true
Number.isInteger(69.0000); // true
Number.isInteger(69.0003); // false

測試一個值是否為安全整數

Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // true
Number.isSafeInteger(Number.MIN_SAFE_INTEGER); // true
Number.isSafeInteger(Math.pow(2,53)); // false

特殊值

非值的值

undefined 的型別中,只會有一個值就是 undefined
null 的型別中,只會有一個值就是 null
(聽起來有點繞口)
undefinednull 中細微的差異在於說

  • null 是一個空值或是曾經有一個值但是現在沒有了
  • undefined 是缺少值或還沒有值

Undefined

其實在非 strict 模式中你可以將 undefined 指定一個值給 undefined 這個全域變數。
聽起來就是一個非常母湯的做法,千萬不要這樣做 xD

特殊數字

非數字的數字

就是指 NaN 啦,之前提過 NaN 就是指 Not a number
什麼時候會出現?
比方說 const a = 1 / 'foo'; // NaN
還記得上次型態 typeof NaN // 'number' 嗎?
這一直以來都還免強說得過去,但是...

NaN === NaN // false 
NaN !== NaN // true

他不等於自己...
那我該如何測試他是不是 NaN?
可以使用 isNaN(..)這個內建的函式工具,這樣就解決了。

const a = 1 / 'foo';
windows.isNaN(a) // true

...

才怪他還有另外一個 Bug
就是...

windows.isNaN('foo') // true

而在 ES6 終於提供了一個替代的工具就是 Number.isNaN(..),能讓你安全無疑慮的使用 xD

Number.isNaN('foo') // false

感謝天感謝地。

無限


對!就是你小學所學的那種無限

const a = 1/0 // Infinity
const b = -1/0 // -Infinity

主要可以拿到無限這個值有兩種方法,一種就是上面看到的除以 0
另一種則是溢位( overflow )

在 ES6 中定義了正無限為 Number.POSITIVE_INFINITY,負無限為Number.NEGATIVE_INFINITY

無限除以無限會是什麼?
可能是 1 或是 無限,聽起來合理。
但是在 JS 他會回你 NaN

正的有限 number 除以 無限 會是 0
負的有限 number 除以 無限 則會是 -0

負零是什麼鬼!? 馬上來說

JS 提供了兩種零,一種是正零另一種則是負零,
除了直接指定 -0 之外,你也可以透過以下的方式產生

const a = 0/-5 // -0
const a = 0*-5 // -0

但是如果你將他 strinify 他卻會回傳給你 "0",如果反向從 string轉回來就正常。
比較運算也是會一個宇智波騙你

const a = 0/-5 // -0
const b = 0 //0

a == b; // true
-0 == 0; // true

a === b; // true
-0 === 0; //true

-0 < 0;  // false
a < b;  // false

那如果想分辨 0-0 該怎麼做?
可以使用以下的方式先判斷是否為0,再除以 n 比對是否是-Infinity

function isNegZero(n){
    n = Number(n)
    return (n === 0) && (1/n === -Infinity) 
}

isNegZero(-0); // true
isNegZero(0 / -5 ); // true
isNegZero(0); // false

但這些我個人覺得都不算重點,重點是為何需要?
書中寫道是因為除了學術上的需求外,特定的應用會需要,比方說動畫美影格的移動速率,就會以 number 的正負號來表示移動方向,如果沒有了正負號就好像失去了方向 xD
大概懂他的感覺,但是我沒有實際遇過所以感觸不深。

特殊相等性

在上面我們提到了 NaN 不等於自己, 0-0 又相等,那我們有沒有一個工具可以測試兩個值是否絕對相等?
有的!
ES6 中出現了一個工具叫做 Object.is(..),並且不會發生剛剛上面所說的那些特殊的情況 XD

const a = 1 / 'foo';
const b = -2 * 0;

Object.is(a, NaN); // true
Object.is(b, 0); // true
Object.is(b, 0); // false 

由此可知就算是使用 === ,也無法避免掉剛剛所說的那些特殊值,如果要比較特殊的情況使用 Object.is(..) 可能相對會來的安全些。

值和參考

JS 的基本型別中永遠都是藉由值的拷貝來指定或是傳遞,
看不懂沒關係我們看看範例

const a = 1;
const b = a; 

b++;
console.log(a)  // 1
console.log(b)  // 2

主要是因為前面所說的 因為 1 是一個純量的基型值,a 拿到了最初的一份拷貝,而 b 拿到的是該值的另一份拷貝,所以b的改變不會造成a的值產生變動。

複合值的 object 永遠都是會在指定或傳遞時建立參考的一份拷貝。
來也來看看範例

const a = [0,1,2,3];
const b = a;

b.push(4)
console.log(a)  // [0,1,2,3,4]
console.log(b)  // [0,1,2,3,4]

這邊的 ab 都是對同一個共有值 [0,1,2,3] 的個別參考,
不是 a 或是 b 擁有該值,
都不是!
而是他們一起去參考 [0,1,2,3] ,所以會造成單邊修改其實就是改動該參考值所以才會兩邊同時改變。

這邊提供一下我自己在網路上找到的文章。

JavaScript 的「傳值」與「傳址」

我個人覺得滿不錯的推薦一下~


以上是今天的內容
有點多,這篇我花比前幾篇還多的時間去研究和思索,
感覺力量提升(?
如果內容有錯或是觀念有錯麻煩再跟我說,有可能是我理解錯誤
歡迎討論
感謝你

我們明天見


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)


上一篇
【這些年我似是非懂的 Javascript】Day 5 - 飽受爭議的型別
下一篇
【這些年我似是非懂的 Javascript】Day 7 - Natives 原生功能
系列文
【這些年我似是非懂的 Javascript】34

尚未有邦友留言

立即登入留言