避免 JavaScript 語言中毒瘤和糟粕

昨天看了《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哗蜈。

undefined and void

最后

本文內(nèi)容參考自《JavaScript 語言精粹》,可能是書有點(diǎn)老了属划,很多知識有了新的解決方案恬叹。所以我也在抄書的同時添加了不少我對于 JavaScript 的認(rèn)知候生。如果有任何問題或者疑問同眯,歡迎評論交流。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唯鸭,一起剝皮案震驚了整個濱河市须蜗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌目溉,老刑警劉巖明肮,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缭付,居然都是意外死亡柿估,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門陷猫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秫舌,“玉大人,你說我怎么就攤上這事绣檬∽阍桑” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵娇未,是天一觀的道長墨缘。 經(jīng)常有香客問我,道長零抬,這世上最難降的妖魔是什么镊讼? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮平夜,結(jié)果婚禮上蝶棋,老公的妹妹穿的比我還像新娘。我一直安慰自己褥芒,他們只是感情好嚼松,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布嫡良。 她就那樣靜靜地躺著,像睡著了一般献酗。 火紅的嫁衣襯著肌膚如雪寝受。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天罕偎,我揣著相機(jī)與錄音很澄,去河邊找鬼。 笑死颜及,一個胖子當(dāng)著我的面吹牛甩苛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俏站,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼讯蒲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肄扎?” 一聲冷哼從身側(cè)響起墨林,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎犯祠,沒想到半個月后旭等,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衡载,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年搔耕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痰娱。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡弃榨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猜揪,到底是詐尸還是另有隱情惭墓,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布而姐,位于F島的核電站腊凶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拴念。R本人自食惡果不足惜钧萍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望政鼠。 院中可真熱鬧风瘦,春花似錦、人聲如沸公般。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞬雹,卻和暖如春昧谊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酗捌。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工呢诬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胖缤。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓尚镰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哪廓。 傳聞我的和親對象是個殘疾皇子狗唉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容