如何寫出優美的 JavaScript 代碼?

一、變量相關

(1)變量數量的定義

NO:濫用變量

let kpi = 4;  // 定義好了之後再也沒用過
function example() {
    var a = 1;
    var b = 2;
    var c = a+b;
    var d = c+1;
    var e = d+a;
    return e;
}

YES: 數據只使用一次或不使用就無需裝到變量中

let kpi = 4;  // 沒用的就刪除掉,不然過三個月自己都不敢刪,怕是不是那用到了
function example() {
    var a = 1;
    var b = 2;
    return 2a+b+1;
}

(2)變量的命名

NO:自我感覺良好的縮寫

let fName = 'jackie'; // 看起來命名挺規範,縮寫,駝峯法都用上,ESlint各種檢測規範的工具都通過,But,fName是啥?這時候,你是不是想說What are you 弄啥呢?
let lName = 'willen'; // 這個問題和上面的一樣

YES: 無需對每個變量都寫註釋,從名字上就看懂

let kpi = 4;  // 沒用的就刪除掉,不然過三個月自己都不敢刪,怕是不是那用到了
 let firstName = 'jackie'; // 怎麼樣,是不是一目瞭然。少被噴了一次
 let lastName = 'willen';

(3)特定的變量

NO:無說明的參數

if (value.length < 8) { // 爲什麼要小於8,8表示啥?長度,還是位移,還是高度?Oh,my God!!
    ....
}

YES: 添加變量

const MAX_INPUT_LENGTH = 8;
if (value.length < MAX_INPUT_LENGTH) { // 一目瞭然,不能超過最大輸入長度
    ....
}

(4)變量的命名

NO:命名過於囉嗦

let nameString;
let theUsers;
}

YES: 做到簡潔明瞭

let name;
let users;

(5)使用說明性的變量(即有意義的變量名)

NO:長代碼不知道啥意思

const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  address.match(cityZipCodeRegex)[1], // 這個公式到底要幹嘛,對不起,原作者已經離職了。自己看代碼
  address.match(cityZipCodeRegex)[2], // 這個公式到底要幹嘛,對不起,原作者已經離職了。自己看代碼
);

YES: 用變量名來解釋長代碼的含義

const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

(6)避免使用太多的全局變量

NO:在不同的文件不停的定義全局變量

name.js
window.name = 'a';
hello.js
window.name = 'b';
time.js
window.name = 'c';  //三個文件的先後加載順序不同,都會使得window.name的值不同,同時,你對window.name的修改了都有可能不生效,因爲你不知道你修改過之後別人是不是又在別的說明文件中對其的值重置了。所以隨着文件的增多,會導致一團亂麻。

YES: 少用或使用替代方案

你可以選擇只用局部變量。通過(){}的方法。
如果你確實用很多的全局變量需要共享,你可以使用vuex,redux或者你自己參考flux模式寫一個也行。

(7)變量的賦值

NO:對於求值獲取的變量,沒有兜底

const MIN_NAME_LENGTH = 8;
let lastName = fullName[1];
if(lastName.length > MIN_NAME_LENGTH) { // 這樣你就給你的代碼成功的埋了一個坑,你有考慮過如果fullName = ['jackie']這樣的情況嗎?這樣程序一跑起來就爆炸。要不你試試。
    ....
}

YES: 對於求值變量,做好兜底

const MIN_NAME_LENGTH = 8;
let lastName = fullName[1] || ''; // 做好兜底,fullName[1]中取不到的時候,不至於賦值個undefined,至少還有個空字符,從根本上講,lastName的變量類型還是String,String原型鏈上的特性都能使用,不會報錯。不會變成undefined。
if(lastName.length > MIN_NAME_LENGTH) {
    ....
}
其實在項目中有很多求值變量,對於每個求值變量都需要做好兜底。
let propertyValue = Object.attr || 0; // 因爲Object.attr有可能爲空,所以需要兜底。
但是,賦值變量就不需要兜底了。
let a = 2; // 因爲有底了,所以不要兜着。
let myName = 'Tiny'; // 因爲有底了,所以不要兜着。

二、函數相關

(1)函數命名

NO:從命名無法知道返回值類型

function showFriendsList() {....} // 現在問,你知道這個返回的是一個數組,還是一個對象,還是true or false。你能答的上來否?你能答得上來我請你吃松鶴樓的滿漢全席還請你不要當真。
);

YES: 對於返回true or false的函數,最好以should/is/can/has開頭

function shouldShowFriendsList() {...}
function isEmpty() {...}
function canCreateDocuments() {...}
function hasLicense() {...}

(2)功能函數最好爲純函數

NO:不要讓功能函數的輸出變化無常

function plusAbc(a, b, c) {  // 這個函數的輸出將變化無常,因爲api返回的值一旦改變,同樣輸入函數的a,b,c的值,但函數返回的結果卻不一定相同。
        var c = fetch('../api');
        return a+b+c;
}

YES: 功能函數使用純函數,輸入一致,輸出結果永遠唯一

function plusAbc(a, b, c) {  // 同樣輸入函數的a,b,c的值,但函數返回的結果永遠相同。
        return a+b+c;
}

(3)函數傳參

NO:傳參無說明

page.getSVG(api, true, false); // true和false啥意思,一目不了然

YES: 傳參有說明

page.getSVG({
    imageApi: api,
    includePageBackground: true, // 一目瞭然,知道這些true和false是啥意思
    compress: false,
})

(4)動作函數要以動詞開頭

NO:無法辨別函數意圖

function emlU(user) {
    ....
}

YES: 動詞開頭,函數意圖就很明顯

function sendEmailToUser(user) {
    ....
}

(5)一個函數完成一個獨立的功能,不要一個函數混雜多個功能

NO:函數功能混亂,一個函數包含多個功能。最後就像能以一當百的老師傅一樣,被亂拳打死(亂拳(功能複雜函數)打死老師傅(老程序員))

function sendEmailToClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client)
    if (clientRecord.isActive()) {
      email(client)
    }
  })
}

YES: 功能拆解

function sendEmailToActiveClients(clients) {  //各個擊破,易於維護和複用
  clients.filter(isActiveClient).forEach(email)
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client)
  return clientRecord.isActive()
}

(6)優先使用命令式編程

NO:使用for循環編程

for(i = 1; i <= 10; i++) { // 一看到for循環讓人頓生不想看的情愫
   a[i] = a[i] +1;
}

YES: 使用命令式編程

let b = a.map(item => ++item) // 怎麼樣,是不是很好理解,就是把a的值每項加一賦值給b就可以了。現在在javascript中幾乎所有的for循環都可以被map,filter,find,some,any,forEach等命令式編成取代。

(7)函數中過多的採用if else

NO: if else過多

if (a === 1) {
    ...
} else if (a ===2) {
    ...
} else if (a === 3) {
    ...
} else {
   ...
}

YES: 可以使用switch替代或用數組替代

switch(a) {
   case 1:
           ....
   case 2:
           ....
   case 3:
           ....
  default:
       ....
}
Or
let handler = {
    1: () => {....},
    2: () => {....}.
    3: () => {....},
    default: () => {....}
}

handler[a]() || handler['default']()

三、儘量使用ES6,有可以能的話ES7中新語法

(只羅列最常用的新語法,說實話,有些新語法不怎麼常用)

(1)儘量使用箭頭函數

NO:採用傳統函數

function foo() {
  // code
}

YES: 使用箭頭函數

let foo = () => {
  // code
}

(2)連接字符串

NO:採用傳統+號

var message = 'Hello ' + name + ', it\'s ' + time + ' now'

YES: 採用模板字符

var message = `Hello ${name}, it's ${time} now`

(3)使用解構賦值

NO:使用傳統賦值

var data = { name: 'dys', age: 1 };
var name = data.name;
var age = data.age;

var fullName = ['jackie', 'willen'];
var firstName = fullName[0];
var lastName = fullName[1];

YES: 使用結構賦值

const data = {name:'dys', age:1};
const {name, age} = data;   // 怎麼樣,是不是簡單明瞭

var fullName = ['jackie', 'willen'];
const [firstName, lastName] = fullName;

(4) 儘量使用類class

NO:採用傳統的函數原型鏈實現繼承

典型的 ES5 的類(function)在繼承、構造和方法定義方面可讀性較差,當需要繼承時,優先選用 class。代碼太多,就省略了。

*YES: 採用ES6類實現繼承

class Animal {
  constructor(age) {
    this.age = age
  }

  move() {
    /* ... */
  }
}

class Mammal extends Animal {
  constructor(age, furColor) {
    super(age)
    this.furColor = furColor
  }

  liveBirth() {
    /* ... */
  }
}

class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor)
    this.languageSpoken = languageSpoken
  }

  speak() {
    /* ... */
  }
}

先寫到這了,後續有再補上…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章