如何写出优雅的 JS 代码,变量和函数的正确写法

做者:ryanmcdermott 译者:前端小智 来源:githubjavascript

点赞再看,养成习惯前端

本文 GitHub github.com/qq449245884… 上已经收录,更多往期高赞文章的分类,也整理了不少个人文档,和教程资料。欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。java

在开发中,变量名,函数名通常要作到清晰明了,尽可能作到看名字就能让人知道你的意图,因此变量和函数命名是挺重要,今天来看看若是较优雅的方式给变量和函数命名。node

变量

使用有意义和可发音的变量名

// 很差的写法
const yyyymmdstr = moment().format("YYYY/MM/DD");

// 好的写法
const currentDate = moment().format("YYYY/MM/DD");
复制代码

对同一类型的变量使用相同的词汇

// 很差的写法
getUserInfo();
getClientData();
getCustomerRecord();

// 好的写法
getUser();
复制代码

使用可搜索的名字

咱们读的会比咱们写的多得多,因此若是命名太过随意不只会给后续的维护带来困难,也会伤害了读咱们代码的开发者。让你的变量名可被读取,像 buddy.jsESLint 这样的工具能够帮助识别未命名的常量。git

// 很差的写法
// 86400000 的用途是什么?
setTimeout(blastOff, 86400000);

// 好的写法
const MILLISECONDS_IN_A_DAY = 86_400_000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
复制代码

使用解释性变量

// 很差的写法
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  address.match(cityZipCodeRegex)[1],
  address.match(cityZipCodeRegex)[2]
);


// 好的写法
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
复制代码

避免费脑的猜想

显式用于隐式github

// 很差的写法
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 等等,“l”又是什么?
  dispatch(l);

// 好的写法
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});
复制代码

你们都说简历没项目写,我就帮你们找了一个项目,还附赠【搭建教程】面试

无需添加没必要要的上下文

若是类名/对象名已经说明了,就无需在变量名中重复。编程

// 很差的写法
const Car = {
  carMake: "Honda",
  carModel: "Accord",
  carColor: "Blue"
};

function paintCar(car) {
  car.carColor = "Red";
}
// 好的写法
const Car = {
  make: "Honda",
  model: "Accord",
  color: "Blue"
};

function paintCar(car) {
  car.color = "Red";
}

复制代码

使用默认参数代替逻辑或(与)运算

// 很差的写法
function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}
// 好的写法
function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}
复制代码

函数

函数参数(理想状况下为2个或更少)

限制函数参数的数量是很是重要的,由于它使测试函数变得更容易。若是有三个以上的参数,就会致使组合爆炸,必须用每一个单独的参数测试大量不一样的状况。设计模式

一个或两个参数是理想的状况,若是可能,应避免三个参数。 除此以外,还应该合并。大多数状况下,大于三个参数能够用对象来代替。数组

// 很差的写法
function createMenu(title, body, buttonText, cancellable) {
  // ...
}

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

// 好的写法
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

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

函数应该只作一件事

这是目前为止软件工程中最重要的规则。当函数作不止一件事时,它们就更难组合、测试和推理。能够将一个函数隔离为一个操做时,就能够很容易地重构它,代码也会读起来更清晰。

// 很差的写法
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

// 好的写法

function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}
复制代码

函数名称应说明其做用

// 很差的写法
function addToDate(date, month) {
  // ...
}

const date = new Date();

// 从函数名称很难知道添加什么
addToDate(date, 1);

// 好的写法
function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);
复制代码

函数应该只有一个抽象层次

当有一个以上的抽象层次函数,意味该函数作得太多了,须要将函数拆分能够实现可重用性和更简单的测试。

// 很差的写法
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...
  });
}

// 好的写法
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;
}
复制代码

你们都说简历没项目写,我就帮你们找了一个项目,还附赠【搭建教程】

删除重复的代码

尽可能避免重复的代码,重复的代码是很差的,它意味着若是咱们须要更改某些逻辑,要改不少地方。

一般,有重复的代码,是由于有两个或多个稍有不一样的事物,它们有不少共同点,可是它们之间的差别迫使咱们编写两个或多个独立的函数来完成许多相同的事情。 删除重复的代码意味着建立一个仅用一个函数/模块/类就能够处理这组不一样事物的抽象。

得到正确的抽象是相当重要的,这就是为何咱们应该遵循类部分中列出的 SOLID原则。糟糕的抽象可能比重复的代码更糟糕,因此要当心!说了这么多,若是你能作一个好的抽象,那就去作吧!不要重复你本身,不然你会发现本身在任什么时候候想要改变一件事的时候都要更新多个地方。

设计模式的六大原则有:

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • Dependence Inversion Principle:依赖倒置原则

把这六个原则的首字母联合起来(两个 L 算作一个)就是 SOLID (solid,稳定的),其表明的含义就是这六个原则结合使用的好处:创建稳定、灵活、健壮的设计。下面咱们来分别看一下这六大设计原则。

很差的写法

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);
  });
}
复制代码

好的写法

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);
  });
}
复制代码

使用Object.assign设置默认对象

很差的写法

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);
复制代码

好的写法

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 now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);
复制代码

你们都说简历没项目写,我就帮你们找了一个项目,还附赠【搭建教程】

不要使用标志做为函数参数

标志告诉使用者,此函数能够完成多项任务,函数应该作一件事。 若是函数遵循基于布尔的不一样代码路径,请拆分它们。

// 很差的写法
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

// 好的写法
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}
复制代码

避免反作用(第一部分)

若是函数除了接受一个值并返回另外一个值或多个值之外,不执行任何其余操做,都会产生反作用。 反作用多是写入文件,修改某些全局变量,或者不当心将你的全部资金都汇给了陌生人。

很差的写法

let name = "Ryan McDermott";

function splitIntoFirstAndLastName() {
  name = name.split(" ");
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];
复制代码

好的写法

function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}

const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
复制代码

避免反作用(第二部分)

JavaScript中,原始类型值是按值传递,而对象/数组按引用传递。 对于对象和数组,若是有函数在购物车数组中进行了更改(例如,经过添加要购买的商品),则使用该购物车数组的任何其余函数都将受到此添加的影响。 那可能很棒,可是也可能很差。 来想象一个糟糕的状况:

用户单击“购买”按钮,该按钮调用一个purchase 函数,接着,该函数发出一个网络请求并将cart数组发送到服务器。因为网络链接很差,purchase函数必须不断重试请求。如今,若是在网络请求开始以前,用户不当心点击了他们实际上不须要的项目上的“添加到购物车”按钮,该怎么办?若是发生这种状况,而且网络请求开始,那么购买函数将发送意外添加的商品,由于它有一个对购物车数组的引用,addItemToCart函数经过添加修改了这个购物车数组。

一个很好的解决方案是addItemToCart老是克隆cart数组,编辑它,而后返回克隆。这能够确保购物车引用的其余函数不会受到任何更改的影响。

关于这种方法有两点须要注意:

1.可能在某些状况下,咱们确实须要修改输入对象,可是当咱们采用这种编程实践时,会发现这种状况很是少见,大多数东西均可以被改形成没有反作用。

2.就性能而言,克隆大对象可能会很是昂贵。 幸运的是,在实践中这并非一个大问题,由于有不少很棒的库使这种编程方法可以快速进行,而且不像手动克隆对象和数组那样占用大量内存。

// 很差的写法
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

// 好的写法
const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};
复制代码

不要写全局函数

污染全局变量在 JS 中是一种很差的作法,由于可能会与另外一个库发生冲突,而且在他们的生产中遇到异常以前,API 的用户将毫无用处。 让咱们考虑一个示例:若是想扩展 JS 的原生Array方法以具备能够显示两个数组之间差别的diff方法,该怎么办? 能够将新函数写入Array.prototype,但它可能与另外一个尝试执行相同操做的库发生冲突。 若是其余库仅使用diff来查找数组的第一个元素和最后一个元素之间的区别怎么办? 这就是为何只使用 ES6 类并简单地扩展Array全局会更好的缘由。

// 很差的写法
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

// 好的写法
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}
复制代码

尽可能使用函数式编程而非命令式

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

很差的写法

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;
}
复制代码

好的写法

const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0
);
复制代码

封装条件

// 很差的写法
if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

// 好的写法
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}
复制代码

你们都说简历没项目写,我就帮你们找了一个项目,还附赠【搭建教程】

避免使用非条件

// 很差的写法
function isDOMNodeNotPresent(node) {
  // ...
}

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

// 好的写法
function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}
复制代码

避免使用过多条件

这彷佛是一个不可能完成的任务。一听到这个,大多数人会说,“没有if语句,我怎么能作任何事情呢?”答案是,你能够在许多状况下使用多态性来实现相同的任务。

第二个问题一般是,“那很好,可是我为何要那样作呢?”答案是上面讲过一个概念:一个函数应该只作一件事。当具备if语句的类和函数时,这是在告诉你的使用者该函数执行不止一件事情。

很差的写法

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();
    }
  }
}
复制代码

好的写法

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();
  }
}
复制代码

避免类型检查

JavaScript 是无类型的,这意味着函数能够接受任何类型的参数。 有时q咱们会被这种自由所困扰,而且很想在函数中进行类型检查。 有不少方法能够避免这样作。 首先要考虑的是一致的API。

// 很差的写法
function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location("texas"));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location("texas"));
  }
}

// 好的写法
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}
复制代码

不要过分优化

现代浏览器在运行时作了大量的优化工做。不少时候,若是你在优化,那么你只是在浪费时间。有很好的资源能够查看哪里缺少优化,咱们只须要针对须要优化的地方就好了。

// 很差的写法

// 在旧的浏览器上,每一次使用无缓存“list.length”的迭代都是很昂贵的
// 会为“list.length”从新计算。在现代浏览器中,这是通过优化的
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

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

复制代码

代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

原文:github.com/ryanmcdermo…


交流

文章每周持续更新,能够微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub github.com/qq449245884… 已经收录,整理了不少个人文档,欢迎Star和完善,你们面试能够参照考点复习,另外关注公众号,后台回复福利,便可看到福利,你懂的。

相关文章
相关标签/搜索