給初學者:JavaScript 的常見注意點
作者:CarterLi
連結:https://segmentfault.com/a/1190000012730162(點選尾部閱讀原文前往)
本文總結一下JavaScript初學者常見的其他易錯點。
寫立即執行函式時前置 void
立即執行函式(IIFE)在 JS 非常常用,作用就是構造一個函式級的變數作用域。常見的寫法如下:
(function () {
// code
})();
這樣寫可能會被 JS 理解成為一個函式呼叫
var a = 1
(function () { // Uncaught TypeError: 1 is not a function
})()
從今天改變習慣,這樣寫:
void function () {
// code
}();
有些人喜歡以 !
打頭,個人習慣問題。
在 standardjs 規範日益流行的今天,忽略行尾分號成為了主流(但是筆者不喜歡),更要改變這個習慣
注:standardjs 本身禁止行首括號(https://standardjs.com/readme-zhcn.html#user-content-細則)
檢查一個變數是否為物件之前,首先判斷其值是否為 null
雖然不願承認,JS 標準說:
typeof
毋庸置疑的, null
不具備作為物件型別的基本特徵,是原始型別。這是一個廣為人知的 JS 的 bug,,它從 JS 誕生開始就存在,從未、而且永遠不會被修復
我們不必去探究它的黑歷史,但是我們寫程式碼時判斷一個變數的型別時,首先需要判斷它是否為 null
if (someVal !== null && typeof someVal === 'object') {
// someVal 是一個物件
}
做數值計算時,注意 JS 數值型別的精度
在 JS 裡,所有的 number 原始值都是一個雙精度浮點數,對應 Java 的 double 型別,對應標準 IEEE754。小心它的精度問題。
做整數處理時,注意數值的大小
JS 最大可儲存的安全整數(不存在精度問題)為 9007199254740991 (16位,Number.MAXSAFEINTEGER ),注意比 Java 的 long 型別最大整數 9223372036854775807 (19位) 小几個數量級,所以有時 JS 的 number 型別是不能精確儲存 Java 的整數的(當然通常情況下不是問題)。
問題通常出在前後端資料傳輸上。資料庫中的主鍵通常是一個自增長的長整型數,有可能會超出 JS 的安全整數範圍,這時請考慮使用字串傳輸。
做小數計算時,注意浮點數的精度問題
例如:0.1+0.2 => 0.30000000000000004,0.4-0.3 => 0.10000000000000003
將小數轉化為字串時,永遠記得使用 toFixed 取小數點後若干位數字:
(0.1 + 0.2).toFixed(2) === '0.30'
比較小數相等時,切記不要直接使用 ===
,而要使用相減取絕對值的方式(表示兩數相差在一定範圍內即認為他們相等)。
0.1+0.2 === 0.3 // false
Math.abs(0.1+0.2 - 0.3) <= 1e-10 // true
NaN !== NaN
NaN 之所以 NB,因為它有一個獨一無二的特性。對!獨一無二!那就是:
NaN === NaN // false
var a = NaN; a === a // false
NaN
不等於它自己。你可以使用這個特性判斷一個變數是否為 NaN
,一個變數如果不等於它自己,這個變數一定是 NaN
。
還有一個方式是使用 Number.isNaN
。注意如果不已知這個變數的型別是數字時,不要使用 isNaN
做判斷,因為 isNaN 有個很詭異的特性:它會先將待判斷的變數轉換為數值型別。
isNaN('abc') // true
isNaN('123') // false
isNaN('') // false
isNaN([]) // false
isNaN({}) // true
永遠不要寫 someVal===NaN
正確使用 parseInt
首先parseInt接受兩個引數,第一個引數為待parse的字串(如果不是字串則會首先轉換為字串);第二個引數為使用的進位制數。
如果不傳第二個引數,則進位制由第一個引數決定。什麼意思呢?比如以 0x 開頭的字串,會被解析為16進位制數。
我們知道以數字 0
開頭的數字為8進位制數(非嚴格模式),比如 011 === 9,0 本身也是8進位制數。那麼問題來了, parseInt('011') = ?
答案是看瀏覽器。目前絕大多數瀏覽器都會作為10進位制數解析,結果為11。但是還有一些老舊的瀏覽器以8進位制數解析(例如IE8和一批老Android瀏覽器)
所以如果你非要用 parseInt:
parseInt
使用規則一:請傳入第二個引數
回到 parseInt 本身的含義。顧名思義這個函式是在parse,被parse的一定是個字串。如果第一個引數不是字串,那麼會首先被轉換為字串。
問: parseInt(0.0000000008)
=?
答:
String(0.0000000008) => '8e-10'
parseInt('8e-10') => 8
自己開啟偵錯程式去試
parseInt
使用規則二:永遠不要使用parseInt給小數取整
建議對於數值轉換一概使用強制轉換函式 Number,如果你JS用6了可以使用 +
(正號)。如果需要對某個數字取整,建議使用 Math.trunc。如果你能確定數值在 32 位以內,可以使用 x|0
或 ~~x
等方式
parseInt的用處在於轉換一些CSS裡帶單位的值: parseInt('10px',10)
=> 10。但這裡建議使用parseFloat,可以解析小數又沒有進位制問題。
除了用於比較 null 或 undefined,永遠不要使用非嚴格相等 ==
絕不要簡單的把非嚴格相等 ==
理解為兩者表示的數字一樣,它有一套非常複雜的轉換規則:它會先將 %%
轉換為 @@
,然後把 !!
轉換為 **
,如果 %%
是 ??
型別,還會 xx
一把……
看不懂對吧,我相信你就算看懂了也記不住的。不然請問:
'true' == true // => false
'true' == false // => false
[] == {} // => false
[] == [] // => false
關於非嚴格相等,你只需要記住這個規則:
null == null // => true
undefined == undefined // => true
null == undefined // => true
undefined == null // => true
x == null // => false (x 非 null 或 undefined)
x == undefined // => false (x 非 null 或 undefined)
簡言之:
x == null // 或 x == undefined
是最簡單的判斷 x 為 null 或 undefined 的方式,相對應的
x != null // 或 x != undefined
是最簡單的判斷 x 非 null 和 undefined 的方式。這就是 ==
存在的唯一意義。
日期處理
new Date(year, month, day) 注意其引數的數值範圍
由於可能的歷史傳承原因,JS 內建物件 Date 的建構函式比較特殊。
如果
year
是 0 ~ 99 之間,year 預設加 1900。比如 1 代表公元 1901 年,99 代表公元 1999 年,100 代表公元 100 年。(你問 -1 是幾?公元前 1 年。。。)month
從 0 開始算。0 代表一月,1 代表二月,以此類推。12 代表下一年的一月(自動進位)
第一點不知道也沒什麼,畢竟一般不會操作公元 99 年之前的時間。但第二點就很容易出錯,切記它是以 0 開始的數字。
這樣得到的日期物件是本地時間(採用客戶端時區)
new Date(dateString) 注意瀏覽器時區問題以及瀏覽器相容性
時常有後端介面返回一個日期字串的情況:
new Date('2018-01-01') // => "2018/1/1 08:00:00" 新版瀏覽器,IE 11
new Date('2018-01-01') // => "2018/1/1 00:00:00" 某些舊版安卓
new Date('2018-01-01') // => "Invalid Date" IE 8(這個忽略。。。)
可以看到,瀏覽器基本都是把日期字串當做 UTC 時間處理的。而
new Date('2018/01/01') // => "2018/1/1 00:00:00" 包括 IE 8 在內所有瀏覽器
所以對於日期字串,請注意字串中是使用橫槓還是斜槓。對於橫槓可以考慮將 -
替換成 /
,或者補全完整的帶時區的 ISO8601 字串。考慮到負數時區的問題,不推薦將小時數清零的做法。
PS:將日期物件取當天 0 點為 date.setHours(0,0,0,0)
PS2:取當前時間的 Unix 時間戳可以 Date.now()
補:慎用 ||
填充預設值
這反而是 JS 老鳥更容易犯的錯誤。給使用者傳入的物件填充預設值是很常見的行為,他們總是隨手就寫:
config.prop1 = config.prop1 || 233;
config.prop2 = config.prop2 || 'balabala';
expr1||expr2
的意思是:如果expr1能轉換成true則返回expr1,否則返回expr2
expr1||expr2<=>Boolean(expr1)?expr1:expr2
哪些值不能轉換為 true 呢?
null
undefined
NaN
0 !!!
空字串('') !!!
如果使用者指定了傳入引數的值為 0 或者是空字串的配置項,它的值就會被強制替換為預設值,然而實際上只有 undefined
應該被認為是使用者沒有指定其值(語義上可以這樣理解: null
表示 使用者讓你給他把這個位置空著
;而 undefined
表示 使用者沒發表意見
)
所以就應該是這樣:
config.prop1 = config.prop1 !== undefined ? config.prop1 : 233;
config.prop2 = config.prop2 !== undefined ? config.prop2 : 'balabala';
很長。。。你可以搞個全域性的函式簡化這一操作,或者考慮使用 lodash 的 defaults 方法