昨天看了《JavaScript 語言精粹》一書垢粮,其中收獲最大的就是附錄中對于 JavaScript 語言的毒瘤和糟粕的講解狂打。所以寫了篇博客自己學(xué)習(xí)下~
我們應(yīng)該盡量避免下面一些寫法的出現(xiàn)來保證更優(yōu)質(zhì)羹奉、更優(yōu)雅的代碼骑脱。
毒瘤
全局變量
全局變量是在所有作用域中都可見的變量渴庆。因?yàn)槿肿兞靠梢员怀绦虻娜魏尾糠衷谌我鈺r間修改厂镇,所以全局變量變得很難維護(hù)纤壁、很不靠譜。
有三種方式可以定義全局變量:
// 全局作用域中定義變量
var foo = value;
// 為 window 定義變量
window.foo = value;
// 隱式全局變量
foo = value;
其中第三種是最不合理也最難被發(fā)現(xiàn)的捺信。
所以摄乒,在寫代碼的時候一定要處理好全局變量的邏輯,盡量避免使用全局變量残黑。
作用域
在 ES6 之前馍佑,JavaScript 沒有真正意義上的塊作用域,所以導(dǎo)致在塊中創(chuàng)建的變量可以在外部訪問到梨水。
if (true) {
var a = 3;
}
a // 3
幸好拭荤,在 ES6 中添加了 const 和 let 命令可以創(chuàng)建塊作用域解決。所以疫诽,盡量使用 const 和 let 替代 var 來定義變量舅世。
自動插入分號
JavaScript 有一個自動修復(fù)機(jī)制,如果語句末尾沒有分號奇徒,將自動插入補(bǔ)全雏亚。但是這有時候會帶來問題:
function test() {
var a = 12;
return
{
123;
}
}
test() // undefined
所以,注意書寫規(guī)范不要隨意換行摩钙,否則可能會由于自動插入分號而導(dǎo)致奇怪的報錯罢低。
保留字
不要使用 JavaScript 的保留字來命名變量和參數(shù)。
Unicode
在 JavaScript 中,Unicode 編碼從 U+0000 到 U+FFFF 內(nèi)的字符長度為 1网持,而超出范圍的字符長度為 2宜岛。
typeof
typeof 運(yùn)算符返回一個用于識別其運(yùn)算數(shù)類型的字符串。但是 typeof 有一些 bug:
typeof null // object
這是 JavaScript 的一個 bug功舀,理論上應(yīng)該是返回 null 的萍倡。
另外,各種 JavaScript 中的 typeof 對于正則的返回結(jié)果不太一致辟汰。
typeof /a/
有的返回 function 有的返回 object列敲。這點(diǎn)在使用時需要注意~
parseInt
parseInt 是把字符串轉(zhuǎn)為數(shù)字的函數(shù),但是它有一些行為比較詭異帖汞。
parseInt('5 fans'); // 5
parseInt('0x123'); // 291
當(dāng)遇到字符串中有非數(shù)字時戴而,函數(shù)會停止解析并返回當(dāng)前結(jié)果。所以第一個表達(dá)式返回 5涨冀,而其實(shí)這串字符串表達(dá)的并非完全是數(shù)字填硕。
當(dāng)遇到二進(jìn)制、八進(jìn)制鹿鳖、十六進(jìn)制的字符串時扁眯,返回的數(shù)字也可能會有所不同,所以建議所有的 parseInt 函數(shù)都加上第二個參數(shù)表明當(dāng)前數(shù)字的進(jìn)制位翅帜。
parseInt('77', 10); // 77
parseInt('08', 8); // 0
parseInt('0x123', 16); // 291
+
- 運(yùn)算符可以用于加法運(yùn)算或者字符串連接姻檀。由于有兩種用途所以要注意加號兩邊的運(yùn)算值的類型。
var num = 1
var str = '3'
num + num // 2
str + str // '33'
str + num // '31'
注:個人認(rèn)為字符串的拼接避免使用 + 號而是用 ES6 的模板字符串來定義涝滴,讓 + 好回歸它數(shù)值相加的功能會更好绣版。
浮點(diǎn)數(shù)
一個挺有名的 JavaScript 浮點(diǎn)數(shù)問題是 0.1 + 0.2 不等于 0.3,這是因?yàn)?JavaScript 使用了二進(jìn)制浮點(diǎn)數(shù)運(yùn)算標(biāo)準(zhǔn)歼疮。所以浮點(diǎn)數(shù)會有很小的數(shù)值偏差杂抽。
解決方法是:雖然浮點(diǎn)數(shù)的計算有偏差,但是整數(shù)的運(yùn)算是不會偏差的韩脏。所以可以通過將值乘以某個數(shù)值 x 轉(zhuǎn)換為整數(shù)進(jìn)行計算缩麸,得到結(jié)果后再除以數(shù)值 x 就可以得到準(zhǔn)確結(jié)果了。
0.1 + 0.2 // 0.30000000000000004
(0.1 * 100 + 0.2 * 100) / 100 // 0.3
NaN
NaN 表示不是一個數(shù)字(not a number)赡矢,它的類型卻是 number杭朱。
typeof NaN // 'number'
首先要注意 NaN 是一個非自反值:
NaN === NaN // false
NaN !== NaN // true
判斷 NaN 的方法是使用 isNaN 函數(shù)。
isNaN(NaN) // true
由于 NaN 的類型是 number吹散,所以判斷值是否是數(shù)字就帶來了一些麻煩弧械,書中提供的判斷數(shù)值的方式是使用 isFinite 函數(shù)配合 typeof 運(yùn)算符。
var isNumber = function(value) {
return typeof value === "number" && isFinite(value);
};
偽數(shù)組
JavaScript 中的數(shù)組其實(shí)是一個對象空民,而并沒有傳統(tǒng)意義上的數(shù)組那樣需要設(shè)置維度刃唐。
問題來了,如何辨別對象是否為數(shù)組對象呢?
var arr = [ 1, 2, 3 ]
typeof arr // "Object"
Object.prototype.toString.apply(arr) // "[object Array]"
typeof 一如既往的不靠譜唁桩,它顯示數(shù)組 arr 是一個 Object闭树。正確的判斷方式是使用對象原型中的 toString 方法耸棒。
另外要注意荒澡,JavaScript 函數(shù)中的參數(shù) arguments 并非數(shù)組,它只是一個帶有 length 成員屬性的對象与殃。
function test() {
console.log(Object.prototype.toString.apply(arguments));
}
test(1, 2, 3, 4); // [object Arguments]
假值
在 JavaScript 中有很多假值:
值 | 類型(typeof) |
---|---|
0 | Number |
NaN | Number |
’‘ | String |
false | Boolean |
null | Object |
undefined | Undefined |
當(dāng)使用上表中的值作為布爾值時单山,將返回 false。如何轉(zhuǎn)為布爾值可以參考布爾值的強(qiáng)制類型轉(zhuǎn)換幅疼。下面舉幾個例子
!! 0 // false
Boolean(null) // false
if (NaN) {
// 不會到這里
}
另外注意一點(diǎn)米奸,在 JavaScript 中 undefined 和 NaN 并非常量,而是全局變量爽篷,所以這兩個值是可以修改的悴晰。這也是公認(rèn)的 JavaScript 的設(shè)計錯誤問題。
hasOwnProperty
對象的 hasOwnProperty 方法可以很好的判斷對象是否包含某個屬性逐工,但是如果在對象上修改 hasOwnProperty 屬性铡溪,原本的功能就失效了。
var obj = { a: 123 }
obj.hasOwnProperty('a') // true
obj.hasOwnProperty = null
obj.hasOwnProperty('a') // TypeError
為什么呢泪喊?因?yàn)閷ο?obj 的原型繼承了 Object棕硫,所以可以使用 Object 中的 hasOwnProperty 函數(shù),但是如果在 obj 上定義了 hasOwnProperty 那么就會屏蔽上層原型鏈內(nèi)的屬性了袒啼。
所以哈扮,不止是 hasOwnProperty,其他的屬性也可能被原型屏蔽覆蓋蚓再。所以盡量避免在對象上定義和 API 同名的屬性滑肉。
糟粕
==
復(fù)習(xí)一個知識點(diǎn) === 和 == 的區(qū)別在哪里?=== 判斷兩個對變量的值和類型是否都相等摘仅,而 == 在判斷相等時如果發(fā)現(xiàn)類型不同會進(jìn)行強(qiáng)制類型轉(zhuǎn)換后再對比靶庙。
相對應(yīng)的 !== 和 != 也是這樣的邏輯。
這種強(qiáng)制類型轉(zhuǎn)換的行為會造成很多奇怪行為:
'' == 0 // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
'\t\r\n' == 0 // true
== 的這種強(qiáng)制類型轉(zhuǎn)換簡直是魔鬼实檀,建議只使用 === 和 !== 來進(jìn)行數(shù)值比較惶洲。
with
with 這個冷門的關(guān)鍵詞通常被當(dāng)作重復(fù)引用同一個對象中的多個屬性的快捷方式,可以不需要重復(fù)引用對象本身膳犹。
var obj = {
a: 1,
b: 2,
c: 3
};
// 單調(diào)乏味的重復(fù) "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡單的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}
看似好意的寫法卻隱藏著兩個問題恬吕,一個是它的寫法看似方便卻難以追蹤變量的變化(相比于 obj.a 而言 with 里面定義的 a 很難理解,而且 with 中的作用域是類塊作用域的)须床。二則是如果定義了 obj 對象中沒有的值铐料,這個值會定義到全局變量上而非 obj 上(如下面的代碼),這也是一個很奇怪的行為。
var obj = {
a: 1,
b: 2,
c: 3
};
with (obj) {
d = 11;
}
console.log(obj) // { a: 1, b:2, c:3 }
console.log(d) // 11
不建議使用 with 語句钠惩。
eval
eval 函數(shù)傳遞一個字符串給 JavaScript 編譯器柒凉,并且執(zhí)行器結(jié)果。但 eval 的寫法讓代碼難以閱讀篓跛、還會顯著降低性能膝捞。
由于 eval 中的字符串可以被編譯,很有可能會執(zhí)行到一些外部傳遞的惡意代碼愧沟,非常不安全蔬咬。
eval('console.log(123)')
// 123
和 eval 函數(shù)同樣能夠編譯字符串的有: Function 構(gòu)造器、setTimeout 函數(shù)和 setInterval 函數(shù)沐寺。
不要使用 eval 和 Function林艘!使用 setTimeout 和 setInterval 函數(shù)時不要傳入字符串而是傳入函數(shù)。
// good
setTimeout(() => {
console.log("hello");
}, 1000);
// bad
setTimeout('console.log("hello")', 2000);
continue
continue 本身并無問題混坞,但是 continue 語句會影響性能狐援。
switch 穿越
缺少塊的語句
如果條件語句后面只有一行代碼,可以省略 {}
但是這樣的行為很容易出現(xiàn)錯誤究孕,建議全部加上大括號啥酱。
// bad
if (ok)
num = 4;
// good
if (ok) {
num = 5;
}
++ --
遞增和遞減運(yùn)算符雖然用著方便,但是這會使得代碼看著擁擠蚊俺、復(fù)雜和隱晦懈涛。所以不建議使用遞增和遞減運(yùn)算符。
// bad
a++;
a--;
// good
a += 1;
a -= 1;
在一些規(guī)范中推薦使用 += 和 -= 來替代遞增和遞減運(yùn)算符泳猬。
位運(yùn)算符
位運(yùn)算符是好東西批钠,可以減少代碼冗余。但是要注意不要寫錯得封。比如 & 寫成 && 運(yùn)算符埋心。
function 語句對比 function 表達(dá)式
考慮兩種情況:
var func = function() {};
function func2() {}
其實(shí)兩者的效果是一樣的,唯一不同的點(diǎn)就在于變量提升忙上。在變量提升完成后的代碼是醬紫的:
function func2() {}
var func;
func = function() {};
function 作為一等公民擁有函數(shù)優(yōu)先特性拷呆,優(yōu)先提升。然后是變量提升疫粥,最后才是賦值操作茬斧。
那么這兩種方式孰優(yōu)孰劣呢,書中認(rèn)為使用定義變量的方式會更好梗逮。因?yàn)楹芏鄷r候并不需要將 function 函數(shù)提升项秉,而且在 if 語句中只能使用定義變量的寫法定義函數(shù)。
類型的包裝對象
不要使用類型的包裝函數(shù)來創(chuàng)建類型慷彤,而是推薦使用直接創(chuàng)建的方式來創(chuàng)建不同類型的值娄蔼。
// bad
var bool = new Boolean(true) // Object
var str = new String('123') // Object
bool.valueOf() // true
str.valueOf() // '123'
// good
var bool = true
var str = '123'
var arr = []
new
在使用 new 運(yùn)算符創(chuàng)建一個繼承構(gòu)造器原型的新對象時怖喻,別忘了 new 運(yùn)算符。
如果可以岁诉,最好能夠不用 new 去創(chuàng)建對象锚沸。
function Func() {}
// 注意兩者的不同
var obj = new Func()
var obj2 = Func();
// 最好
var obj3 = {}
var obj4 = Object.create(obj3)
void
書中認(rèn)為 void 沒有什么用,不過也有一些規(guī)范認(rèn)為用 void 來替代 undefined 可以避免 undefined 是一個全局變量的問題涕癣,所以要求使用 void 替換 undefined哗蜈。
最后
本文內(nèi)容參考自《JavaScript 語言精粹》,可能是書有點(diǎn)老了属划,很多知識有了新的解決方案恬叹。所以我也在抄書的同時添加了不少我對于 JavaScript 的認(rèn)知候生。如果有任何問題或者疑問同眯,歡迎評論交流。