简洁的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++) {    // ...  }