簡潔的javascript編碼(一)–變數、函數

  • 2019 年 12 月 4 日
  • 筆記

一、變數

  • 使用語義化的變數名稱 Bad
const yyyymmdstr = moment().format('YYYY/MM/DD');

Good

const currentDate = moment().format('YYYY/MM/DD');

  • 使用可搜索的名稱 通常我們讀程式碼比寫得多,所以程式碼都可讀性以及可搜索性非常重要。如果對變數不負於語義化、易理解的名字,程式碼的閱讀者將非常痛苦。buddy.js以及ESLint能夠幫助我們定位未命名的常量。 Bad
// 86400000 是什麼鬼?  setTimeout(blastOff, 86400000);

Good

// 通過大寫常量來定義.  const MILLISECONDS_IN_A_DAY = 86400000;  setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

  • 使用說明性的臨時變數 Bad
const address = 'One Infinite Loop, Cupertino 95014';  const cityZipCodeRegex = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;  saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);

Good

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

  • 避免費腦理解的臨時變數 在遍歷時,避免簡單無意義的變數命名 Bad
const locations = ['Austin', 'New York', 'San Francisco'];  locations.forEach((l) => {    doStuff();    doSomeOtherStuff();    // ...    // ...    // ...    // 等等 我忘記了l是啥了?    dispatch(l);  });

Good

const locations = ['Austin', 'New York', 'San Francisco'];  locations.forEach((location) => {    doStuff();    doSomeOtherStuff();    // ...    // ...    // ...    dispatch(location);  });

  • 不添加不需要的內容 如果你的類名/對象名已經能夠表述某些資訊,那麼在類/對象名的屬性中就不需要重複命名。 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(breweryName = 'Hipster Brew Co.') {    // ...  }

二、函數

  • 函數參數最好不超過2個 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 emailClients(clients) {    clients      .filter(isClientActive)      .forEach(email);  }  function isClientActive(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 parseBetterJSAlternative(code) {    const REGEXES = [      // ...    ];    const statements = code.split(' ');    const tokens = [];    REGEXES.forEach((REGEX) => {      statements.forEach((statement) => {        // ...      });    });    const ast = [];    tokens.forEach((token) => {      // lex...    });    ast.forEach((node) => {      // parse...    });  }

Good:

function tokenize(code) {    const REGEXES = [      // ...    ];    const statements = code.split(' ');    const tokens = [];    REGEXES.forEach((REGEX) => {      statements.forEach((statement) => {        tokens.push( /* ... */ );      });    });    return tokens;  }  function lexer(tokens) {    const ast = [];    tokens.forEach((token) => {      ast.push( /* ... */ );    });    return ast;  }  function parseBetterJSAlternative(code) {    const tokens = tokenize(code);    const ast = lexer(tokens);    ast.forEach((node) => {      // parse...    });  }

  • 移除重複的程式碼 你是否儘可能地在避免重複程式碼呢?在你將要修改某處邏輯時,重複程式碼將會非常噁心導致你不止修改一處地方。 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();      let portfolio = employee.getGithubLink();      if (employee.type === 'manager') {        portfolio = employee.getMBAProjects();      }      const data = {        expectedSalary,        experience,        portfolio      };      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',    // User did not include 'body' key    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);

  • 避免在函數參數使用flags 在函數使用flags說明你的函數不滿足單一職責原則。 Bad:
function createFile(name, temp) {    if (temp) {      fs.create(`./temp/${name}`);    } else {      fs.create(name);    }  }

Good:

function createFile(name) {    fs.create(name);  }  function createTempFile(name) {    createFile(`./temp/${name}`);  }

  • 避免副作用 如果某個函數除了接收輸入值與返回值之外還做了其他事,那麼就稱其具有副作用。典型的副作用譬如寫文件、修改某些全局變數、修改記憶體參數等等。 在編程中我們不可避免的需要產生副作用,譬如上面例子中我們需要寫入到某個外部文件。而你應當做的就是將所有的寫文件操作由某個服務統一處理,而不應該將寫文件的操作分散到數個類或者函數中。這一點最大的優勢在於避免了不同對象之間共享狀態。 Bad:
// 定義全局變數  // 如果我們有其他的函數引用了該變數,那麼我們就無法預測該變數類型  let name = 'Ryan McDermott';  function splitIntoFirstAndLastName() {    name = name.split(' ');  }  splitIntoFirstAndLastName();  console.log(name); // ['Ryan', 'McDermott'];

Good:

function splitIntoFirstAndLastName(name) {    return name.split(' ');  }  const name = 'Ryan McDermott';  const newName = splitIntoFirstAndLastName(name);  console.log(name); // 'Ryan McDermott';  console.log(newName); // ['Ryan', 'McDermott'];

Bad:

const addItemToCart = (cart, item) => {    cart.push({ item, date: Date.now() });  };

Good:

const addItemToCart = (cart, item) => {    return [...cart, { item, date : Date.now() }];  };

  • 避免污染全局變數 由於你的修改導致污染全局變數,可能導致另外一個庫的使用者在不知情的情況下出現生產環境異常。例如,你想擴展Array方法diff,用於區分2個數組的不同。你可能通過Array的原型鏈擴展,但可能導致其他庫的的diff方法失效,例如找出一個數組第一跟最後一個元素的不同的方法。所以這就是為什麼更建議通過ES6的classes,並且使用繼承方式去添加新的功能函數。 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:
const programmerOutput = [    {      name: 'Uncle Bobby',      linesOfCode: 500    }, {      name: 'Suzie Q',      linesOfCode: 1500    }, {      name: 'Jimmy Gosling',      linesOfCode: 150    }, {      name: 'Gracie Hopper',      linesOfCode: 1000    }  ];  let totalOutput = 0;  for (let i = 0; i < programmerOutput.length; i++) {    totalOutput += programmerOutput[i].linesOfCode;  }

Good:

const programmerOutput = [    {      name: 'Uncle Bobby',      linesOfCode: 500    }, {      name: 'Suzie Q',      linesOfCode: 1500    }, {      name: 'Jimmy Gosling',      linesOfCode: 150    }, {      name: 'Gracie Hopper',      linesOfCode: 1000    }  ];  const INITIAL_VALUE = 0;  const totalOutput = programmerOutput    .map((programmer) => programmer.linesOfCode)    .reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE);

  • 封裝條件判斷 Bad:
if (fsm.state === 'fetching' && isEmpty(listNode)) {    // ...  }

Good:

function shouldShowSpinner(fsm, listNode) {    return fsm.state === 'fetching' && isEmpty(listNode);  }  if (shouldShowSpinner(fsmInstance, listNodeInstance)) {    // ...  }

  • 避免反義條件函數 Bad:
function isDOMNodeNotPresent(node) {    // ...  }  if (!isDOMNodeNotPresent(node)) {    // ...  }

Good:

function isDOMNodePresent(node) {    // ...  }  if (isDOMNodePresent(node)) {    // ...  }

  • 避免條件選擇 這似乎聽起來是一個不可能的任務,沒有if條件選擇語句的話又該如何編程呢?在這裡我們推薦使用多態性來達成這一目標,因為如果在函數或類中嵌入過多的if語句,會導致該函數或者類破壞單一職責原則。 Bad:
class Airplane {    // ...    getCruisingAltitude() {      switch (this.type) {        case '777':          return this.getMaxAltitude() - this.getPassengerCount();        case 'Air Force One':          return this.getMaxAltitude();        case 'Cessna':          return this.getMaxAltitude() - this.getFuelExpenditure();      }    }  }

Good:

class Airplane {    // ...  }  class Boeing777 extends Airplane {    // ...    getCruisingAltitude() {      return this.getMaxAltitude() - this.getPassengerCount();    }  }  class AirForceOne extends Airplane {    // ...    getCruisingAltitude() {      return this.getMaxAltitude();    }  }  class Cessna extends Airplane {    // ...    getCruisingAltitude() {      return this.getMaxAltitude() - this.getFuelExpenditure();    }  }

  • 避免過度優化

現代瀏覽器已經在運行時做了很多的優化,因此很多時候如果我們要遵循那些流傳已久的優化策略不過是浪費時間。可以參考這個來獲取建議的優化要點。 Bad:

// On old browsers, each iteration with uncached `list.length` would be costly  // because of `list.length` recomputation. In modern browsers, this is optimized.  // 在老的瀏覽器,每次循環都會去計算list.lenth造成消耗  // 但在現在瀏覽器,已經被優化了  for (let i = 0, len = list.length; i < len; i++) {    // ...  }

Good:

for (let i = 0; i < list.length; i++) {    // ...  }