[譯] 如何寫出漂亮的 JavaScript 程式碼

小生方勤發表於2019-06-25

原文:github.com/ryanmcdermo…
說明:本文翻譯自 github 上的一個專案,非全文搬運,只取部分精華。

[譯] 如何寫出漂亮的 JavaScript 程式碼

如何提高程式碼的可讀性、複用性、擴充套件性。我們將從以下四個方面討論:

  1. 變數
  2. 函式
  3. 非同步

一、變數

用有意義且常用的單詞命名

// Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');
// Good:
const currentDate = moment().format('YYYY/MM/DD');
複製程式碼

保持統一

對同一型別的變數使用相同的命名保持統一:

// Bad:
getUserInfo();
getClientData();
getCustomerRecord();
// Good:
getUser()
複製程式碼

每個常量(全大寫)都該命名

可以用 ESLint 檢測程式碼中未命名的常量。

// Bad:
// 其他人知道 86400000 的意思嗎?
setTimeout( blastOff, 86400000 );
// Good:
const MILLISECOND_IN_A_DAY = 86400000;
setTimeout( blastOff, MILLISECOND_IN_A_DAY );
複製程式碼

避免無意義的命名

既然建立了一個 car 物件,就沒有必要把它的顏色命名為 carColor。

// Bad:
const car = {
    carMake: 'Honda',
    carModel: 'Accord',
    carColor: 'Blue'
};
function paintCar( car ) {
    car.carColor = 'Red';
}
// Good:
const car = {
    make: 'Honda',
    model: 'Accord',
    color: 'Blue'
};
function paintCar( car ) {
    car.color = 'Red';
}
複製程式碼

傳參使用預設值

// Bad:
function createMicrobrewery( name ) {
    const breweryName = name || 'Hipster Brew Co.';
    // ...
}
// Good:
function createMicrobrewery( name = 'Hipster Brew Co.' ) {
    // ...
}
複製程式碼

二、函式

函式引數( 最好 2 個或更少 )

如果引數超過兩個,建議使用 ES6 的解構語法,不用考慮引數的順序。

// Bad:
function createMenu( title, body, buttonText, cancellable ) {
    // ...
}
// Good:
function createMenu( { title, body, buttonText, cancellable } ) {
    // ...
}
createMenu({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
});
複製程式碼

一個方法只做一件事情

這是一條在軟體工程領域流傳久遠的規則。嚴格遵守這條規則會讓你的程式碼可讀性更好,也更容易重構。如果違反這個規則,那麼程式碼會很難被測試或者重用。

// Bad:
function emailClients( clients ) {
    clients.forEach( client => {
        const clientRecord = database.lookup( client );
        if ( clientRecord.isActive() ) {
            email( client );
        }
    });
}
// Good:
function emailActiveClients( clients ) {
    clients
        .filter( isActiveClient )
        .forEach( email );
}
function isActiveClient( client ) {
    const clientRecord = database.lookup( client );    
    return clientRecord.isActive();
}
複製程式碼

函式名上體現它的作用

// Bad:
function addToDate( date, month ) {
    // ...
}
const date = new Date();
// 很難知道是把什麼加到日期中
addToDate( date, 1 );
// Good:
function addMonthToDate( month, date ) {
    // ...
}
const date = new Date();
addMonthToDate( 1, date );
複製程式碼

刪除重複程式碼,合併相似函式

很多時候雖然是同一個功能,但由於一兩個不同點,讓你不得不寫兩個幾乎相同的函式。

// Bad:
function showDeveloperList(developers) {
  developers.forEach((developer) => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };
    render(data);
  });
}
function showManagerList(managers) {
  managers.forEach((manager) => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };
    render(data);
  });
}
// Good:
function showEmployeeList(employees) {
  employees.forEach(employee => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();
    const data = {
      expectedSalary,
      experience,
    };
    switch(employee.type) {
      case 'develop':
        data.githubLink = employee.getGithubLink();
        break
      case 'manager':
        data.portfolio = employee.getMBAProjects();
        break
    }
    render(data);
  })
}
複製程式碼

使用 Object.assign 設定預設屬性

// Bad:
const menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true
};
function createMenu(config) {
  config.title = config.title || 'Foo';
  config.body = config.body || 'Bar';
  config.buttonText = config.buttonText || 'Baz';
  config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);
// Good:
const menuConfig = {
  title: 'Order',
  // 不包含 body
  buttonText: 'Send',
  cancellable: true
};
function createMenu(config) {
  config = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);
  // config : {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}
createMenu(menuConfig);
複製程式碼

儘量不要寫全域性方法

在 JavaScript 中,永遠不要汙染全域性,會在生產環境中產生難以預料的 bug。舉個例子,比如你在 Array.prototype 上新增一個 diff 方法來判斷兩個陣列的不同。而你同事也打算做類似的事情,不過他的 diff 方法是用來判斷兩個陣列首位元素的不同。很明顯你們方法會產生衝突,遇到這類問題我們可以用 ES2015/ES6 的語法來對 Array 進行擴充套件。

// Bad:
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};
// Good:
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));        
  }
}
複製程式碼

儘量別用“非”條件句

// Bad:
function isDOMNodeNotPresent(node) {
  // ...
}
if (!isDOMNodeNotPresent(node)) {
  // ...
}
// Good:
function isDOMNodePresent(node) {
  // ...
}
if (isDOMNodePresent(node)) {
  // ...
}
複製程式碼

不要過度優化

現代瀏覽器已經在底層做了很多優化,過去的很多優化方案都是無效的,會浪費你的時間。

// Bad:
// 現代瀏覽器已對此( 快取 list.length )做了優化。
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}
// Good:
for (let i = 0; i < list.length; i++) {
  // ...
}
複製程式碼

刪除棄用程式碼

這裡沒有例項程式碼,刪除就對了

三、類

使用 ES6 的 class

在 ES6 之前,沒有類的語法,只能用建構函式的方式模擬類,可讀性非常差。

// Good:
// 動物
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() {};
}
複製程式碼

使用鏈式呼叫

這種模式相當有用,可以在很多庫中都有使用。它讓你的程式碼簡潔優雅。

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}
// Bad:
const car = new Car('Ford','F-150','red');
car.setColor('pink');
car.save();

// Good: 
class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
    // NOTE: Returning this for chaining
    return this;
  }

  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }

  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // NOTE: Returning this for chaining
    return this;
  }
}

const car = new Car("Ford", "F-150", "red").setColor("pink").save();
複製程式碼

四、非同步

使用 promise 或者 Async/Await 代替回撥

// Bad:
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
  if (requestErr) {
    console.error(requestErr);
  } else {
    writeFile('article.html', response.body, (writeErr) => {
      if (writeErr) {
        console.error(writeErr);
      } else {
        console.log('File written');
      }
    });
  }
});
// Good:
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then((response) => {
    return writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

// perfect:
async function getCleanCodeArticle() {
  try {
    const response = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
    await writeFile('article.html', response);
    console.log('File written');
  } catch(err) {
    console.error(err);
  }
}

複製程式碼

最後

如果你想進【大前端交流群】,關注公眾號點選“交流加群”新增機器人自動拉你入群。關注我第一時間接收最新干貨。

[譯] 如何寫出漂亮的 JavaScript 程式碼

相關文章