26个写出简洁优雅JavaScript代码的技巧

写在前面

在编程世界中,代码不仅仅是让事情正常运转。 它就像一件讲述故事的艺术品。 当代码干净时,它就像一个美丽的、精心制作的雕塑,既美观又运行良好。

但在急于按期完成任务的过程中,有时团队不会太注意保持代码的整洁。 这可能会导致项目变得混乱、复杂,变得更加难以开展。 随着情况变得更糟,生产力也会下降。 然后,公司需要引进更多的人来提供帮助,这使得一切都变得更加昂贵。

那么,干净的代码是什么样的呢? 它的代码易于理解,没有多余的部分,简单,并且可以通过测试。 换句话说,它是可读的、可重用的,并且在需要时易于更改。

为了帮助你编写出色的 JavaScript 代码,我将在今天的内容中与你分享 26 个写干净代码的技巧,这些技巧将指导你编写既优雅又高效的代码。

一、变量

1.使用有意义且易于发音的变量名

// Bad
const yyyymmdstr = moment().format("YYYY/MM/DD");


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

2. 同一类型的变量使用相同的词汇表

// Bad
getUserInfo();
getClientData();
getCustomerRecord();


// Good
getUser();

3. 使用可搜索的名称

我们将阅读比我们编写的更多的代码,我们编写的代码可读且可搜索,这一点很重要。

// Bad
// What the heck is 86400000 for?
setTimeout(blastOff, 86400000); 


// Good
// Declare them as capitalized named constants.
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;


setTimeout(blastOff, MILLISECONDS_PER_DAY);

4. 使用解释变量

// 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);

5. 避免心理映射

显式的比隐式的好。

// Bad
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // Wait, what is `l` for again?
  dispatch(l);
});


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

6. 不要添加不需要的上下文

如果您的类/对象名称告诉您一些信息,请不要在变量名称中重复该信息。

// Bad
const Car = {
  carMake: "Honda",
  carModel: "Accord",
  carColor: "Blue"
};


function paintCar(car, color) {
  car.carColor = color;
}


// Good
const Car = {
  make: "Honda",
  model: "Accord",
  color: "Blue"
};


function paintCar(car, color) {
  car.color = color;
}

7. 使用默认参数代替短路或条件

默认参数通常比短路更清晰。 请注意,如果您使用它们,您的函数将只为未定义的参数提供默认值。 其他“假”值(例如 ''、""、false、null、0 和 NaN)不会被默认值替换。

// Bad
function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}


// Good
function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}

二、功能

8. 函数参数(理想情况下为 2 个或更少)

限制函数参数的数量非常重要,因为它使测试函数变得更加容易。 超过三个会导致组合爆炸,您必须使用每个单独的参数来测试大量不同的情况。

// Bad
function createMenu(title, body, buttonText, cancellable) {
  // ...
}


createMenu("Foo", "Bar", "Baz", true);


//Good
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}


createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});

9.函数应该做一件事

这是迄今为止软件工程中最重要的规则。 当函数做不止一件事时,它们就更难编写、测试和推理。 当您可以将一个函数隔离为一个操作时,就可以轻松重构它,并且您的代码读起来会更清晰。 如果您除了本指南之外没有任何其他内容,您将领先于许多开发人员。

// 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();
}

10.函数名称应该说明它们的作用

// Bad
function addToDate(date, month) {
  // ...
}


const date = new Date();


// It's hard to tell from the function name what is added
addToDate(date, 1);


// Good
function addMonthToDate(month, date) {
  // ...
}


const date = new Date();
addMonthToDate(1, date);

11.函数应该只是一层抽象

当你有多个抽象级别时,你的函数通常会做太多事情。 拆分功能可以实现可重用性和更容易的测试。

// 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 parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}


function tokenize(code) {
  const REGEXES = [
    // ...
  ];


  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      tokens.push(/* ... */);
    });
  });


  return tokens;
}


function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token => {
    syntaxTree.push(/* ... */);
  });


  return syntaxTree;
}

12. 删除重复代码

尽最大努力避免重复代码。 重复的代码是不好的,因为这意味着如果您需要更改某些逻辑,则需要在多个地方进行更改。

// 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 "manager":
        data.portfolio = employee.getMBAProjects();
        break;
      case "developer":
        data.githubLink = employee.getGithubLink();
        break;
    }


    render(data);
  });
}

13. 使用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) {
  let finalConfig = Object.assign(
    {
      title: "Foo",
      body: "Bar",
      buttonText: "Baz",
      cancellable: true
    },
    config
  );
  return finalConfig
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}


createMenu(menuConfig);

14. 不要使用标志作为函数参数

标志告诉你的用户这个函数不止做一件事。 函数应该做一件事。 如果函数遵循基于布尔值的不同代码路径,则拆分它们。

// 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}`);
}

15.不要写入全局函数

在 JavaScript 中污染全局变量是一种不好的做法,因为你可能会与另一个库发生冲突,并且 API 的用户在生产中遇到异常之前不会意识到这一点。

// 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));
  }
}

16. 优先使用函数式编程而不是命令式编程

JavaScript 不像 Haskell 那样是一种函数式语言,但它具有函数式风格。 函数式语言可以更简洁、更容易测试。 尽可能喜欢这种编程风格。

// 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  totalLines + output.linesOfCode,
  0
);

17.封装条件语句

// Bad
if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}


// Good
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}


if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

18.避免否定条件

// Bad
function isDOMNodeNotPresent(node) {
  // ...
}


if (!isDOMNodeNotPresent(node)) {
  // ...
}


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


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

三、并发性

19.使用 Promise,而不是回调

回调不干净,并且会导致过多的嵌套。 在 ES2015/ES6 中,Promise 是内置的全局类型。 使用它们!

// Bad
import { get } from "request";
import { writeFile } from "fs";


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


// Good
import { get } from "request-promise";
import { writeFile } from "fs-extra";


get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(body => {
    return writeFile("article.html", body);
  })
  .then(() => {
    console.log("File written");
  })
  .catch(err => {
    console.error(err);
  });

20. Async/Await 比 Promise 更简洁

Promise 是回调的一个非常干净的替代方案,但 ES2017/ES8 带来了 async 和 wait,它提供了更干净的解决方案。 

您所需要的只是一个以 async 关键字为前缀的函数,然后您可以命令式地编写逻辑,而无需 then 函数链。

// Bad
import { get } from "request-promise";
import { writeFile } from "fs-extra";


get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(body => {
    return writeFile("article.html", body);
  })
  .then(() => {
    console.log("File written");
  })
  .catch(err => {
    console.error(err);
  });


// Good
import { get } from "request-promise";
import { writeFile } from "fs-extra";


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


getCleanCodeArticle()

四、错误处理

抛出错误是一件好事! 它们意味着运行时已成功识别出程序中的某些问题,并且它会通过停止当前堆栈上的函数执行、终止进程(在 Node 中)并通过堆栈跟踪在控制台中通知您来通知您。

21. 不要忽略捕获的错误

对捕获的错误不采取任何措施并不能让您有能力修复或对所述错误做出反应。 将错误记录到控制台 (console.log) 也好不了多少,因为它常常会迷失在打印到控制台的大量内容中。 

如果您将任何代码包装在 try/catch 中,则意味着您认为那里可能会发生错误,因此您应该为错误发生时制定计划或创建代码路径。

// Bad
try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}


// Good
try {
  functionThatMightThrow();
} catch (error) {
  // One option (more noisy than console.log):
  console.error(error);
  // Another option:
  notifyUserOfError(error);
  // Another option:
  reportErrorToService(error);
  // OR do all three!
}

22. 不要忽视被拒绝的承诺

出于同样的原因,您不应该忽略 try/catch 中捕获的错误。

// Bad
getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    console.log(error);
  });


// Good
getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
  });

五、评论

23. 只评论具有业务逻辑复杂性的事物

评论是道歉,而不是要求。 好的代码主要是文档本身。

// Bad
function hashIt(data) {
// The hash
let hash = 0;

// Length of string
const length = data.length;

// Loop through every character in data
for (let i = 0; i < length; i++) {
// Get character code.
const char = data.charCodeAt(i);
// Make the hash
hash = (hash