前端編碼規(guī)范(3)JavaScript 規(guī)范

2018-08-09 19:26 更新

JavaScript 規(guī)范


全局命名空間污染與 IIFE

總是將代碼包裹成一個(gè) IIFE(Immediately-Invoked Function Expression),用以創(chuàng)建獨(dú)立隔絕的定義域。這一舉措可防止全局命名空間被污染。

IIFE 還可確保你的代碼不會(huì)輕易被其它全局命名空間里的代碼所修改(i.e. 第三方庫(kù),window 引用,被覆蓋的未定義的關(guān)鍵字等等)。

不推薦

  1. var x = 10,
  2. y = 100;
  3.  
  4. // Declaring variables in the global scope is resulting in global scope pollution. All variables declared like this
  5. // will be stored in the window object. This is very unclean and needs to be avoided.
  6. console.log(window.x + ' ' + window.y);

推薦

  1. // We declare a IIFE and pass parameters into the function that we will use from the global space
  2. (function(log, w, undefined){
  3. 'use strict';
  4.  
  5. var x = 10,
  6. y = 100;
  7.  
  8. // Will output 'true true'
  9. log((w.x === undefined) + ' ' + (w.y === undefined));
  10.  
  11. }(window.console.log, window));

IIFE(立即執(zhí)行的函數(shù)表達(dá)式)

無(wú)論何時(shí),想要?jiǎng)?chuàng)建一個(gè)新的封閉的定義域,那就用 IIFE。它不僅避免了干擾,也使得內(nèi)存在執(zhí)行完后立即釋放。

所有腳本文件建議都從 IIFE 開(kāi)始。

立即執(zhí)行的函數(shù)表達(dá)式的執(zhí)行括號(hào)應(yīng)該寫(xiě)在外包括號(hào)內(nèi)。雖然寫(xiě)在內(nèi)還是寫(xiě)在外都是有效的,但寫(xiě)在內(nèi)使得整個(gè)表達(dá)式看起來(lái)更像一個(gè)整體,因此推薦這么做。

不推薦

  1. (function(){})();

推薦

  1. (function(){}());

so,用下列寫(xiě)法來(lái)格式化你的 IIFE 代碼:

  1. (function(){
  2. 'use strict';
  3.  
  4. // Code goes here
  5.  
  6. }());

如果你想引用全局變量或者是外層 IIFE 的變量,可以通過(guò)下列方式傳參:

  1. (function($, w, d){
  2. 'use strict';
  3.  
  4. $(function() {
  5. w.alert(d.querySelectorAll('div').length);
  6. });
  7. }(jQuery, window, document));

嚴(yán)格模式

ECMAScript 5 嚴(yán)格模式可在整個(gè)腳本或獨(dú)個(gè)方法內(nèi)被激活。它對(duì)應(yīng)不同的 javascript 語(yǔ)境會(huì)做更加嚴(yán)格的錯(cuò)誤檢查。嚴(yán)格模式也確保了 javascript 代碼更加的健壯,運(yùn)行的也更加快速。

嚴(yán)格模式會(huì)阻止使用在未來(lái)很可能被引入的預(yù)留關(guān)鍵字。

你應(yīng)該在你的腳本中啟用嚴(yán)格模式,最好是在獨(dú)立的 IIFE 中應(yīng)用它。避免在你的腳本第一行使用它而導(dǎo)致你的所有腳本都啟動(dòng)了嚴(yán)格模式,這有可能會(huì)引發(fā)一些第三方類(lèi)庫(kù)的問(wèn)題。

不推薦

  1. // Script starts here
  2. 'use strict';
  3.  
  4. (function(){
  5.  
  6. // Your code starts here
  7.  
  8. }());

推薦

  1. (function(){
  2. 'use strict';
  3.  
  4. // Your code starts here
  5.  
  6. }());

變量聲明

總是使用 var 來(lái)聲明變量。如不指定 var,變量將被隱式地聲明為全局變量,這將對(duì)變量難以控制。如果沒(méi)有聲明,變量處于什么定義域就變得不清(可以是在 Document 或 Window 中,也可以很容易地進(jìn)入本地定義域)。所以,請(qǐng)總是使用 var 來(lái)聲明變量。

采用嚴(yán)格模式帶來(lái)的好處是,當(dāng)你手誤輸入錯(cuò)誤的變量名時(shí),它可以通過(guò)報(bào)錯(cuò)信息來(lái)幫助你定位錯(cuò)誤出處。

不推薦

  1. x = 10;
  2. y = 100;

推薦

  1. var x = 10,
  2. y = 100;

理解 JavaScript 的定義域和定義域提升

在 JavaScript 中變量和方法定義會(huì)自動(dòng)提升到執(zhí)行之前。JavaScript 只有 function 級(jí)的定義域,而無(wú)其他很多編程語(yǔ)言中的塊定義域,所以使得你在某一 function 內(nèi)的某語(yǔ)句和循環(huán)體中定義了一個(gè)變量,此變量可作用于整個(gè) function 內(nèi),而不僅僅是在此語(yǔ)句或循環(huán)體中,因?yàn)樗鼈兊穆暶鞅?JavaScript 自動(dòng)提升了。

我們通過(guò)例子來(lái)看清楚這到底是怎么一回事:

原 function

  1. (function(log){
  2. 'use strict';
  3.  
  4. var a = 10;
  5.  
  6. for(var i = 0; i < a; i++) {
  7. var b = i * i;
  8. log(b);
  9. }
  10.  
  11. if(a === 10) {
  12. var f = function() {
  13. log(a);
  14. };
  15. f();
  16. }
  17.  
  18. function x() {
  19. log('Mr. X!');
  20. }
  21. x();
  22.  
  23. }(window.console.log));

被 JS 提升過(guò)后

  1. (function(log){
  2. 'use strict';
  3. // All variables used in the closure will be hoisted to the top of the function
  4. var a,
  5. i,
  6. b,
  7. f;
  8. // All functions in the closure will be hoisted to the top
  9. function x() {
  10. log('Mr. X!');
  11. }
  12.  
  13. a = 10;
  14.  
  15. for(i = 0; i < a; i++) {
  16. b = i * i;
  17. log(b);
  18. }
  19.  
  20. if(a === 10) {
  21. // Function assignments will only result in hoisted variables but the function body will not be hoisted
  22. // Only by using a real function declaration the whole function will be hoisted with its body
  23. f = function() {
  24. log(a);
  25. };
  26. f();
  27. }
  28.  
  29. x();
  30.  
  31. }(window.console.log));

根據(jù)以上提升過(guò)程,你是否可理解以下代碼?

有效代碼

  1. (function(log){
  2. 'use strict';
  3.  
  4. var a = 10;
  5.  
  6. i = 5;
  7.  
  8. x();
  9.  
  10. for(var i; i < a; i++) {
  11. log(b);
  12. var b = i * i;
  13. }
  14.  
  15. if(a === 10) {
  16. f = function() {
  17. log(a);
  18. };
  19. f();
  20.  
  21. var f;
  22. }
  23.  
  24. function x() {
  25. log('Mr. X!');
  26. }
  27.  
  28. }(window.console.log));

正如你所看到的這段令人充滿困惑與誤解的代碼導(dǎo)致了出人意料的結(jié)果。只有良好的聲明習(xí)慣,也就是下一章節(jié)我們要提到的聲明規(guī)則,才能盡可能的避免這類(lèi)錯(cuò)誤風(fēng)險(xiǎn)。


提升聲明

為避免上一章節(jié)所述的變量和方法定義被自動(dòng)提升造成誤解,把風(fēng)險(xiǎn)降到最低,我們應(yīng)該手動(dòng)地顯示地去聲明變量與方法。也就是說(shuō),所有的變量以及方法,應(yīng)當(dāng)定義在 function 內(nèi)的首行。

只用一個(gè) var 關(guān)鍵字聲明,多個(gè)變量用逗號(hào)隔開(kāi)。

不推薦

  1. (function(log){
  2. 'use strict';
  3.  
  4. var a = 10;
  5. var b = 10;
  6.  
  7. for(var i = 0; i < 10; i++) {
  8. var c = a * b * i;
  9. }
  10.  
  11. function f() {
  12.  
  13. }
  14.  
  15. var d = 100;
  16. var x = function() {
  17. return d * d;
  18. };
  19. log(x());
  20.  
  21. }(window.console.log));

推薦

  1. (function(log){
  2. 'use strict';
  3.  
  4. var a = 10,
  5. b = 10,
  6. i,
  7. c,
  8. d,
  9. x;
  10.  
  11. function f() {
  12.  
  13. }
  14.  
  15. for(i = 0; i < 10; i++) {
  16. c = a * b * i;
  17. }
  18.  
  19.  
  20.  
  21. d = 100;
  22. x = function() {
  23. return d * d;
  24. };
  25. log(x());
  26.  
  27. }(window.console.log));

把賦值盡量寫(xiě)在變量申明中。

不推薦

  1. var a,
  2. b,
  3. c;
  4.  
  5. a = 10;
  6. b = 10;
  7. c = 100;

推薦

  1. var a = 10,
  2. b = 10,
  3. c = 100;

總是使用帶類(lèi)型判斷的比較判斷

總是使用 === 精確的比較操作符,避免在判斷的過(guò)程中,由 JavaScript 的強(qiáng)制類(lèi)型轉(zhuǎn)換所造成的困擾。

如果你使用 === 操作符,那比較的雙方必須是同一類(lèi)型為前提的條件下才會(huì)有效。

如果你想了解更多關(guān)于強(qiáng)制類(lèi)型轉(zhuǎn)換的信息,你可以讀一讀 Dmitry Soshnikov 的這篇文章

在只使用 == 的情況下,JavaScript 所帶來(lái)的強(qiáng)制類(lèi)型轉(zhuǎn)換使得判斷結(jié)果跟蹤變得復(fù)雜,下面的例子可以看出這樣的結(jié)果有多怪了:

  1. (function(log){
  2. 'use strict';
  3.  
  4. log('0' == 0); // true
  5. log('' == false); // true
  6. log('1' == true); // true
  7. log(null == undefined); // true
  8.  
  9. var x = {
  10. valueOf: function() {
  11. return 'X';
  12. }
  13. };
  14.  
  15. log(x == 'X');
  16.  
  17. }(window.console.log));

明智地使用真假判斷

當(dāng)我們?cè)谝粋€(gè) if 條件語(yǔ)句中使用變量或表達(dá)式時(shí),會(huì)做真假判斷。if(a == true) 是不同于 if(a) 的。后者的判斷比較特殊,我們稱(chēng)其為真假判斷。這種判斷會(huì)通過(guò)特殊的操作將其轉(zhuǎn)換為 true 或 false,下列表達(dá)式統(tǒng)統(tǒng)返回 false:false, 0, undefined, null, NaN, ''(空字符串).

這種真假判斷在我們只求結(jié)果而不關(guān)心過(guò)程的情況下,非常的有幫助。

以下示例展示了真假判斷是如何工作的:

  1. (function(log){
  2. 'use strict';
  3.  
  4. function logTruthyFalsy(expr) {
  5. if(expr) {
  6. log('truthy');
  7. } else {
  8. log('falsy');
  9. }
  10. }
  11.  
  12. logTruthyFalsy(true); // truthy
  13. logTruthyFalsy(1); // truthy
  14. logTruthyFalsy({}); // truthy
  15. logTruthyFalsy([]); // truthy
  16. logTruthyFalsy('0'); // truthy
  17.  
  18. logTruthyFalsy(false); // falsy
  19. logTruthyFalsy(0); // falsy
  20. logTruthyFalsy(undefined); // falsy
  21. logTruthyFalsy(null); // falsy
  22. logTruthyFalsy(NaN); // falsy
  23. logTruthyFalsy(''); // falsy
  24.  
  25. }(window.console.log));

變量賦值時(shí)的邏輯操作

邏輯操作符 ||&& 也可被用來(lái)返回布爾值。如果操作對(duì)象為非布爾對(duì)象,那每個(gè)表達(dá)式將會(huì)被自左向右地做真假判斷?;诖瞬僮?,最終總有一個(gè)表達(dá)式被返回回來(lái)。這在變量賦值時(shí),是可以用來(lái)簡(jiǎn)化你的代碼的。

不推薦

  1. if(!x) {
  2. if(!y) {
  3. x = 1;
  4. } else {
  5. x = y;
  6. }
  7. }

推薦

  1. x = x || y || 1;

這一小技巧經(jīng)常用來(lái)給方法設(shè)定默認(rèn)的參數(shù)。

  1. (function(log){
  2. 'use strict';
  3.  
  4. function multiply(a, b) {
  5. a = a || 1;
  6. b = b || 1;
  7.  
  8. log('Result ' + a * b);
  9. }
  10.  
  11. multiply(); // Result 1
  12. multiply(10); // Result 10
  13. multiply(3, NaN); // Result 3
  14. multiply(9, 5); // Result 45
  15.  
  16. }(window.console.log));

分號(hào)

總是使用分號(hào),因?yàn)殡[式的代碼嵌套會(huì)引發(fā)難以察覺(jué)的問(wèn)題。當(dāng)然我們更要從根本上來(lái)杜絕這些問(wèn)題[1] 。以下幾個(gè)示例展示了缺少分號(hào)的危害:

  1. // 1.
  2. MyClass.prototype.myMethod = function() {
  3. return 42;
  4. } // No semicolon here.
  5.  
  6. (function() {
  7. // Some initialization code wrapped in a function to create a scope for locals.
  8. })();
  9.  
  10.  
  11. var x = {
  12. 'i': 1,
  13. 'j': 2
  14. } // No semicolon here.
  15.  
  16. // 2. Trying to do one thing on Internet Explorer and another on Firefox.
  17. // I know you'd never write code like this, but throw me a bone.
  18. [ffVersion, ieVersion][isIE]();
  19.  
  20.  
  21. var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here.
  22.  
  23. // 3. conditional execution a la bash
  24. -1 == resultOfOperation() || die();

So what happens?

  1. JavaScript 錯(cuò)誤 —— 首先返回 42 的那個(gè) function 被第二個(gè) function 當(dāng)中參數(shù)傳入調(diào)用,接著數(shù)字 42 也被“調(diào)用”而導(dǎo)致出錯(cuò)。
  2. 八成你會(huì)得到 ‘no such property in undefined’ 的錯(cuò)誤提示,因?yàn)樵谡鎸?shí)環(huán)境中的調(diào)用是這個(gè)樣子:x[ffVersion, ieVersion][isIE]().
  3. die 總是被調(diào)用。因?yàn)閿?shù)組減 1 的結(jié)果是 NaN,它不等于任何東西(無(wú)論 resultOfOperation 是否返回 NaN)。所以最終的結(jié)果是 die() 執(zhí)行完所獲得值將賦給 THINGS_TO_EAT.

Why?

JavaScript 中語(yǔ)句要以分號(hào)結(jié)束,否則它將會(huì)繼續(xù)執(zhí)行下去,不管換不換行。以上的每一個(gè)示例中,函數(shù)聲明或?qū)ο蠡驍?shù)組,都變成了在一句語(yǔ)句體內(nèi)。要知道閉合圓括號(hào)并不代表語(yǔ)句結(jié)束,JavaScript 不會(huì)終結(jié)語(yǔ)句,除非它的下一個(gè) token 是一個(gè)中綴符[2] 或者是圓括號(hào)操作符。

這真是讓人大吃一驚,所以乖乖地給語(yǔ)句末加上分號(hào)吧。

澄清:分號(hào)與函數(shù)

分號(hào)需要用在表達(dá)式的結(jié)尾,而并非函數(shù)聲明的結(jié)尾。區(qū)分它們最好的例子是:

  1. var foo = function() {
  2. return true;
  3. }; // semicolon here.
  4.  
  5. function foo() {
  6. return true;
  7. } // no semicolon here.

嵌套函數(shù)

嵌套函數(shù)是非常有用的,比如用在持續(xù)創(chuàng)建和隱藏輔助函數(shù)的任務(wù)中。你可以非常自由隨意地使用它們。


語(yǔ)句塊內(nèi)的函數(shù)聲明

切勿在語(yǔ)句塊內(nèi)聲明函數(shù),在 ECMAScript 5 的嚴(yán)格模式下,這是不合法的。函數(shù)聲明應(yīng)該在定義域的頂層。但在語(yǔ)句塊內(nèi)可將函數(shù)申明轉(zhuǎn)化為函數(shù)表達(dá)式賦值給變量。

不推薦

  1. if (x) {
  2. function foo() {}
  3. }

推薦

  1. if (x) {
  2. var foo = function() {};
  3. }

異常

基本上你無(wú)法避免出現(xiàn)異常,特別是在做大型開(kāi)發(fā)時(shí)(使用應(yīng)用開(kāi)發(fā)框架等等)。

在沒(méi)有自定義異常的情況下,從有返回值的函數(shù)中返回錯(cuò)誤信息一定非常的棘手,更別提多不優(yōu)雅了。不好的解決方案包括了傳第一個(gè)引用類(lèi)型來(lái)接納錯(cuò)誤信息,或總是返回一個(gè)對(duì)象列表,其中包含著可能的錯(cuò)誤對(duì)象。以上方式基本上是比較簡(jiǎn)陋的異常處理方式。適時(shí)可做自定義異常處理。

在復(fù)雜的環(huán)境中,你可以考慮拋出對(duì)象而不僅僅是字符串(默認(rèn)的拋出值)。

  1. if(name === undefined) {
  2. throw {
  3. name: 'System Error',
  4. message: 'A name should always be specified!'
  5. }
  6. }

標(biāo)準(zhǔn)特性

總是優(yōu)先考慮使用標(biāo)準(zhǔn)特性。為了最大限度地保證擴(kuò)展性與兼容性,總是首選標(biāo)準(zhǔn)的特性,而不是非標(biāo)準(zhǔn)的特性(例如:首選 string.charAt(3) 而不是 string[3];首選 DOM 的操作方法來(lái)獲得元素引用,而不是某一應(yīng)用特定的快捷方法)。


簡(jiǎn)易的原型繼承

如果你想在 JavaScript 中繼承你的對(duì)象,請(qǐng)遵循一個(gè)簡(jiǎn)易的模式來(lái)創(chuàng)建此繼承。如果你預(yù)計(jì)你會(huì)遇上復(fù)雜對(duì)象的繼承,那可以考慮采用一個(gè)繼承庫(kù),比如 Proto.js by Axel Rauschmayer.

簡(jiǎn)易繼承請(qǐng)用以下方式:

  1. (function(log){
  2. 'use strict';
  3.  
  4. // Constructor function
  5. function Apple(name) {
  6. this.name = name;
  7. }
  8. // Defining a method of apple
  9. Apple.prototype.eat = function() {
  10. log('Eating ' + this.name);
  11. };
  12.  
  13. // Constructor function
  14. function GrannySmithApple() {
  15. // Invoking parent constructor
  16. Apple.prototype.constructor.call(this, 'Granny Smith');
  17. }
  18. // Set parent prototype while creating a copy with Object.create
  19. GrannySmithApple.prototype = Object.create(Apple.prototype);
  20. // Set constructor to the sub type, otherwise points to Apple
  21. GrannySmithApple.prototype.constructor = GrannySmithApple;
  22.  
  23. // Calling a super method
  24. GrannySmithApple.prototype.eat = function() {
  25. // Be sure to apply it onto our current object with call(this)
  26. Apple.prototype.eat.call(this);
  27.  
  28. log('Poor Grany Smith');
  29. };
  30.  
  31. // Instantiation
  32. var apple = new Apple('Test Apple');
  33. var grannyApple = new GrannySmithApple();
  34.  
  35. log(apple.name); // Test Apple
  36. log(grannyApple.name); // Granny Smith
  37.  
  38. // Instance checks
  39. log(apple instanceof Apple); // true
  40. log(apple instanceof GrannySmithApple); // false
  41.  
  42. log(grannyApple instanceof Apple); // true
  43. log(grannyApple instanceof GrannySmithApple); // true
  44.  
  45. // Calling method that calls super method
  46. grannyApple.eat(); // Eating Granny Smith\nPoor Grany Smith
  47.  
  48. }(window.console.log));

使用閉包

閉包的創(chuàng)建也許是 JS 最有用也是最易被忽略的能力了。關(guān)于閉包如何工作的合理解釋。


切勿在循環(huán)中創(chuàng)建函數(shù)

在簡(jiǎn)單的循環(huán)語(yǔ)句中加入函數(shù)是非常容易形成閉包而帶來(lái)隱患的。下面的例子就是一個(gè)典型的陷阱:

不推薦

  1. (function(log, w){
  2. 'use strict';
  3.  
  4. // numbers and i is defined in the current function closure
  5. var numbers = [1, 2, 3],
  6. i;
  7.  
  8. for(i = 0; i < numbers.length; i++) {
  9. w.setTimeout(function() {
  10. // At the moment when this gets executed the i variable, coming from the outer function scope
  11. // is set to 3 and the current program is alerting the message 3 times
  12. // 'Index 3 with number undefined
  13. // If you understand closures in javascript you know how to deal with those cases
  14. // It's best to just avoid functions / new closures in loops as this prevents those issues
  15.  
  16. w.alert('Index ' + i + ' with number ' + numbers[i]);
  17. }, 0);
  18. }
  19.  
  20. }(window.console.log, window));

接下來(lái)的改進(jìn)雖然已經(jīng)解決了上述例子中的問(wèn)題或 bug,但還是違反了不在循環(huán)中創(chuàng)建函數(shù)或閉包的原則。

不推薦

  1. (function(log, w){
  2. 'use strict';
  3.  
  4. // numbers and i is defined in the current function closure
  5. var numbers = [1, 2, 3],
  6. i;
  7.  
  8. for(i = 0; i < numbers.length; i++) {
  9. // Creating a new closure scope with an IIFE solves the problem
  10. // The delayed function will use index and number which are
  11. // in their own closure scope (one closure per loop iteration).
  12. // ---
  13. // Still this is not recommended as we violate our rule to not
  14. // create functions within loops and we are creating two!
  15.  
  16. (function(index, number){
  17. w.setTimeout(function() {
  18. // Will output as expected 0 > 1, 1 > 2, 2 > 3
  19. w.alert('Index ' + index + ' with number ' + number);
  20. }, 0);
  21. }(i, numbers[i]));
  22. }
  23.  
  24. }(window.console.log, window));

接下來(lái)的改進(jìn)已解決問(wèn)題,而且也遵循了規(guī)范??墒?,你會(huì)發(fā)現(xiàn)看上去似乎過(guò)于復(fù)雜繁冗了,應(yīng)該會(huì)有更好的解決方案吧。

不完全推薦

  1. (function(log, w){
  2. 'use strict';
  3.  
  4. // numbers and i is defined in the current function closure
  5. var numbers = [1, 2, 3],
  6. i;
  7.  
  8. // Create a function outside of the loop that will accept arguments to create a
  9. // function closure scope. This function will return a function that executes in this
  10. // closure parent scope.
  11. function alertIndexWithNumber(index, number) {
  12. return function() {
  13. w.alert('Index ' + index + ' with number ' + number);
  14. };
  15. }
  16.  
  17. // First parameter is a function call that returns a function.
  18. // ---
  19. // This solves our problem and we don't create a function inside our loop
  20. for(i = 0; i < numbers.length; i++) {
  21. w.setTimeout(alertIndexWithNumber(i, numbers[i]), 0);
  22. }
  23.  
  24. }(window.console.log, window));

將循環(huán)語(yǔ)句轉(zhuǎn)換為函數(shù)執(zhí)行的方式問(wèn)題能得到立馬解決,每一次循環(huán)都會(huì)對(duì)應(yīng)地創(chuàng)建一次閉包。函數(shù)式的風(fēng)格更加值得推薦,而且看上去也更加地自然和可預(yù)料。

推薦

  1. (function(log, w){
  2. 'use strict';
  3.  
  4. // numbers and i is defined in the current function closure
  5. var numbers = [1, 2, 3],
  6. i;
  7.  
  8. numbers.forEach(function(number, index) {
  9. w.setTimeout(function() {
  10. w.alert('Index ' + index + ' with number ' + number);
  11. }, 0);
  12. });
  13.  
  14. }(window.console.log, window));

eval 函數(shù)(魔鬼)

eval() 不但混淆語(yǔ)境還很危險(xiǎn),總會(huì)有比這更好、更清晰、更安全的另一種方案來(lái)寫(xiě)你的代碼,因此盡量不要使用 evil 函數(shù)。


this 關(guān)鍵字

只在對(duì)象構(gòu)造器、方法和在設(shè)定的閉包中使用 this 關(guān)鍵字。this 的語(yǔ)義在此有些誤導(dǎo)。它時(shí)而指向全局對(duì)象(大多數(shù)時(shí)),時(shí)而指向調(diào)用者的定義域(在 eval 中),時(shí)而指向 DOM 樹(shù)中的某一節(jié)點(diǎn)(當(dāng)用事件處理綁定到 HTML 屬性上時(shí)),時(shí)而指向一個(gè)新創(chuàng)建的對(duì)象(在構(gòu)造器中),還時(shí)而指向其它的一些對(duì)象(如果函數(shù)被 call()apply() 執(zhí)行和調(diào)用時(shí))。

正因?yàn)樗侨绱巳菀椎乇桓沐e(cuò),請(qǐng)限制它的使用場(chǎng)景:

  • 在構(gòu)造函數(shù)中
  • 在對(duì)象的方法中(包括由此創(chuàng)建出的閉包內(nèi))

首選函數(shù)式風(fēng)格

函數(shù)式編程讓你可以簡(jiǎn)化代碼并縮減維護(hù)成本,因?yàn)樗菀讖?fù)用,又適當(dāng)?shù)亟怦詈透俚囊蕾?lài)。

接下來(lái)的例子中,在一組數(shù)字求和的同一問(wèn)題上,比較了兩種解決方案。第一個(gè)例子是經(jīng)典的程序處理,而第二個(gè)例子則是采用了函數(shù)式編程和 ECMA Script 5.1 的數(shù)組方法。

例外:往往在重代碼性能輕代碼維護(hù)的情況之下,要選擇最優(yōu)性能的解決方案而非維護(hù)性高的方案(比如用簡(jiǎn)單的循環(huán)語(yǔ)句代替 forEach)。

不推薦

  1. (function(log){
  2. 'use strict';
  3.  
  4. var arr = [10, 3, 7, 9, 100, 20],
  5. sum = 0,
  6. i;
  7.  
  8.  
  9. for(i = 0; i < arr.length; i++) {
  10. sum += arr[i];
  11. }
  12.  
  13. log('The sum of array ' + arr + ' is: ' + sum)
  14.  
  15. }(window.console.log));

推薦

  1. (function(log){
  2. 'use strict';
  3.  
  4. var arr = [10, 3, 7, 9, 100, 20];
  5.  
  6. var sum = arr.reduce(function(prevValue, currentValue) {
  7. return prevValue + currentValue;
  8. }, 0);
  9.  
  10. log('The sum of array ' + arr + ' is: ' + sum);
  11.  
  12. }(window.console.log));

另一個(gè)例子通過(guò)某一規(guī)則對(duì)一個(gè)數(shù)組進(jìn)行過(guò)濾匹配來(lái)創(chuàng)建一個(gè)新的數(shù)組。

不推薦

  1. (function(log){
  2. 'use strict';
  3.  
  4. var numbers = [11, 3, 7, 9, 100, 20, 14, 10],
  5. numbersGreaterTen = [],
  6. i;
  7.  
  8.  
  9. for(i = 0; i < numbers.length; i++) {
  10. if(numbers[i] > 10) {
  11. numbersGreaterTen.push(numbers[i]);
  12. }
  13. }
  14.  
  15. log('From the list of numbers ' + numbers + ' only ' + numbersGreaterTen + ' are greater than ten');
  16.  
  17. }(window.console.log));

推薦

  1. (function(log){
  2. 'use strict';
  3.  
  4. var numbers = [11, 3, 7, 9, 100, 20, 14, 10];
  5.  
  6. var numbersGreaterTen = numbers.filter(function(element) {
  7. return element > 10;
  8. });
  9.  
  10. log('From the list of numbers ' + numbers + ' only ' + numbersGreaterTen + ' are greater than ten');
  11.  
  12. }(window.console.log));

使用 ECMA Script 5

建議使用 ECMA Script 5 中新增的語(yǔ)法糖和函數(shù)。這將簡(jiǎn)化你的程序,并讓你的代碼更加靈活和可復(fù)用。


數(shù)組和對(duì)象的屬性迭代

用 ECMA5 的迭代方法來(lái)迭代數(shù)組。使用 Array.forEach 或者如果你要在特殊場(chǎng)合下中斷迭代,那就用 Array.every

  1. (function(log){
  2. 'use strict';
  3.  
  4. // Iterate over an array and break at a certain condition
  5. [1, 2, 3, 4, 5].every(function(element, index, arr) {
  6. log(element + ' at index ' + index + ' in array ' + arr);
  7.  
  8. if(index !== 5) {
  9. return true;
  10. }
  11. });
  12.  
  13. // Defining a simple javascript object
  14. var obj = {
  15. a: 'A',
  16. b: 'B',
  17. 'c-d-e': 'CDE'
  18. };
  19.  
  20. // Iterating over the object keys
  21. Object.keys(obj).forEach(function(element, index, arr) {
  22. log('Key ' + element + ' has value ' + obj[element]);
  23. });
  24.  
  25. }(window.console.log));

不要使用 switch

switch 在所有的編程語(yǔ)言中都是個(gè)非常錯(cuò)誤的難以控制的語(yǔ)句,建議用 if else 來(lái)替換它。


數(shù)組和對(duì)象字面量

用數(shù)組和對(duì)象字面量來(lái)代替數(shù)組和對(duì)象構(gòu)造器。數(shù)組構(gòu)造器很容易讓人在它的參數(shù)上犯錯(cuò)。

不推薦

  1. // Length is 3.
  2. var a1 = new Array(x1, x2, x3);
  3.  
  4. // Length is 2.
  5. var a2 = new Array(x1, x2);
  6.  
  7. // If x1 is a number and it is a natural number the length will be x1.
  8. // If x1 is a number but not a natural number this will throw an exception.
  9. // Otherwise the array will have one element with x1 as its value.
  10. var a3 = new Array(x1);
  11.  
  12. // Length is 0.
  13. var a4 = new Array();

正因如此,如果將代碼傳參從兩個(gè)變?yōu)橐粋€(gè),那數(shù)組很有可能發(fā)生意料不到的長(zhǎng)度變化。為避免此類(lèi)怪異狀況,請(qǐng)總是采用更多可讀的數(shù)組字面量。

推薦

  1. var a = [x1, x2, x3];
  2. var a2 = [x1, x2];
  3. var a3 = [x1];
  4. var a4 = [];

對(duì)象構(gòu)造器不會(huì)有類(lèi)似的問(wèn)題,但是為了可讀性和統(tǒng)一性,我們應(yīng)該使用對(duì)象字面量。

不推薦

  1. var o = new Object();
  2.  
  3. var o2 = new Object();
  4. o2.a = 0;
  5. o2.b = 1;
  6. o2.c = 2;
  7. o2['strange key'] = 3;

應(yīng)該寫(xiě)成這樣:

推薦

  1. var o = {};
  2.  
  3. var o2 = {
  4. a: 0,
  5. b: 1,
  6. c: 2,
  7. 'strange key': 3
  8. };

修改內(nèi)建對(duì)象的原型鏈

修改內(nèi)建的諸如 Object.prototypeArray.prototype 是被嚴(yán)厲禁止的。修改其它的內(nèi)建對(duì)象比如 Function.prototype,雖危害沒(méi)那么大,但始終還是會(huì)導(dǎo)致在開(kāi)發(fā)過(guò)程中難以 debug 的問(wèn)題,應(yīng)當(dāng)也要避免。


自定義 toString() 方法

你可以通過(guò)自定義 toString() 來(lái)控制對(duì)象字符串化。這很好,但你必須保證你的方法總是成功并不會(huì)有其它副作用。如果你的方法達(dá)不到這樣的標(biāo)準(zhǔn),那將會(huì)引發(fā)嚴(yán)重的問(wèn)題。如果 toString() 調(diào)用了一個(gè)方法,這個(gè)方法做了一個(gè)斷言[3] ,當(dāng)斷言失敗,它可能會(huì)輸出它所在對(duì)象的名稱(chēng),當(dāng)然對(duì)象也需要調(diào)用 toString()。


圓括號(hào)

一般在語(yǔ)法和語(yǔ)義上真正需要時(shí)才謹(jǐn)慎地使用圓括號(hào)。不要用在一元操作符上,例如 delete, typeofvoid,或在關(guān)鍵字之后,例如 return, throw, case, new 等。


字符串

統(tǒng)一使用單引號(hào)(‘),不使用雙引號(hào)(“)。這在創(chuàng)建 HTML 字符串非常有好處:

  1. var msg = 'This is some HTML <div class="makes-sense"></div>';

三元條件判斷(if 的快捷方法)

用三元操作符分配或返回語(yǔ)句。在比較簡(jiǎn)單的情況下使用,避免在復(fù)雜的情況下使用。沒(méi)人愿意用 10 行三元操作符把自己的腦子繞暈。

不推薦

  1. if(x === 10) {
  2. return 'valid';
  3. } else {
  4. return 'invalid';
  5. }

推薦

  1. return x === 10 ? 'valid' : 'invalid';

[1]:作者指的是采用嚴(yán)格規(guī)范的語(yǔ)句寫(xiě)法,從根本上杜絕由分號(hào)缺失而引起的代碼歧義。

[2]:中綴符,指的是像 x + y 中的 +。

[3]:斷言一般指程序員在測(cè)試測(cè)序時(shí)的假設(shè),一般是一些布爾表達(dá)式,當(dāng)返回是 true 時(shí),斷言為真,代碼運(yùn)行會(huì)繼續(xù)進(jìn)行;如果條件判斷為 false,代碼運(yùn)行停止,你的應(yīng)用被終止。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)