编写简洁的 JavaScript 代码

作者:尹锋
链接:https://www.zhihu.com/question/20635785/answer/223515216
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

避免使用 js 糟粕和鸡肋

这些年来,随着 HTML5 和 Node.js 的发展,JavaScript 在各个领域遍地开花,已经从“世界上最被误解的语言”变成了“世界上最流行的语言”。但是由于历史原因,JavaScript 语言设计中还是有一些糟粕和鸡肋,比如:全局变量、自动插入分号、typeof、NaN、假值、==、eval 等等,并不能被语言移除,开发者一定要避免使用这些特性,还好下文中的 ESLint 能够检测出这些特性,给出错误提示(如果你遇到面试官还在考你这些特性的话,那就需要考量一下,他们的项目中是否仍在使用这些特性,同时你也应该知道如何回答这类问题了)。

编写简洁的 JavaScript 代码

下这些准则来自 Robert C. Martin 的书 “Clean Code”,适用于 JavaScript。整儿列长 很长,我选取了我认为最重要的一部分,也是我在项目用的最多的一部分,但是还是推荐大家看一下原文。这不是风格指南,而是 使用 JavaScript 生产可读、可重用和可重构的软件的指南

变量

使用有意义,可读性好的变量名

Bad:

var yyyymmdstr = moment().format(‘YYYY/MM/DD‘)

Good:

var yearMonthDay = moment().format(‘YYYY/MM/DD‘)

使用 ES6 的 const 定义常量

反例中使用"var"定义的"常量"是可变的,在声明一个常量时,该常量在整个程序中都应该是不可变的。

Bad:

var FIRST_US_PRESIDENT = "George Washington"

Good:

const FIRST_US_PRESIDENT = "George Washington"

使用易于检索的名称

我们要阅读的代码比要写的代码多得多, 所以我们写出的代码的可读性和可检索性是很重要的。使用没有意义的变量名将会导致我们的程序难于理解,将会伤害我们的读者, 所以请使用可检索的变量名。 类似 buddy.js 和ESLint的工具可以帮助我们找到未命名的常量。

Bad:

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

Good:

// Declare them as capitalized `const` globals.
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:

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()

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

Good:

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

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

使用默认变量替代短路运算或条件

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 Smell 中排在第一位,所以,竭尽你的全力去避免重复代码。因为它意味着当你需要修改一些逻辑时会有多个地方需要修改。

重复代码通常是因为两个或多个稍微不同的东西, 它们共享大部分,但是它们的不同之处迫使你使用两个或更多独立的函数来处理大部分相同的东西。 移除重复代码意味着创建一个可以处理这些不同之处的抽象的函数/模块/类。

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

避免副作用

当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。比如写文件、修改全局变量或将你的钱全转给了一个陌生人等。程序在某些情况下确实需要副作用这一行为,这时应该将这些功能集中在一起,不要用多个函数/类修改某个文件。用且只用一个 service 完成这一需求。

Bad:

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

Good:

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

避免条件判断

这看起来似乎不太可能。大多人听到这的第一反应是:“怎么可能不用 if 完成其他功能呢?”许多情况下通过使用多态(polymorphism)可以达到同样的目的。第二个问题在于采用这种方式的原因是什么。答案是我们之前提到过的:保持函数功能的单一性。

Bad:

class Airplane {
  // ...
  getCruisingAltitude() {
    switch (this.type) {
      case ‘777‘:
        return getMaxAltitude() - getPassengerCount()
      case ‘Air Force One‘:
        return getMaxAltitude()
      case ‘Cessna‘:
        return getMaxAltitude() - getFuelExpenditure()
    }
  }
}

Good:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return getMaxAltitude() - getPassengerCount()
  }
}

class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return getMaxAltitude()
  }
}

class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return getMaxAltitude() - getFuelExpenditure()
  }
}

使用 ES6/ES7 新特性

箭头函数

Bad:

function foo() {
  // code
}

Good:

let foo = () => {
  // code
}

模板字符串

Bad:

var message = ‘Hello ‘ + name + ‘, it\‘s ‘ + time + ‘ now‘

Good:

const message = `Hello ${name}, it‘s ${time} now`

解构

Bad:

var data = { name: ‘dys‘, age: 1 }
var name = data.name,
    age = data.age

Good:

const data = {name:‘dys‘, age:1}
const {name, age} = data

使用 ES6 的 classes 而不是 ES5 的 Function

典型的 ES5 的类(function)在继承、构造和方法定义方面可读性较差,当需要继承时,优先选用 classes。

Bad:

// 那个复杂的原型链继承就不贴代码了

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() {
    /* ... */
  }
}

Async/Await 是比 Promise 和回调更好的选择

回调不够整洁,并会造成大量的嵌套,ES6 内嵌了 Promises,但 ES7 中的 async 和 await 更胜过 Promises。

Promise 代码的意思是:“我想执行这个操作,然后(then)在其他操作中使用它的结果”。await 有效地反转了这个意思,使得它更像:“我想要取得这个操作的结果”。我喜欢,因为这听起来更简单,所以尽可能的使用 async/await。

Bad:

require(‘request-promise‘)
  .get(‘https://en.wikipedia.org/wiki/Robert_Cecil_Martin‘)
  .then(function(response) {
    return require(‘fs-promise‘).writeFile(‘article.html‘, response)
  })
  .then(function() {
    console.log(‘File written‘)
  })
  .catch(function(err) {
    console.error(err)
  })

Good:

async function getCleanCodeArticle() {
  try {
    var request = await require(‘request-promise‘)
    var response = await request.get(
      ‘https://en.wikipedia.org/wiki/Robert_Cecil_Martin‘,
    )
    var fileHandle = await require(‘fs-promise‘)

    await fileHandle.writeFile(‘article.html‘, response)
    console.log(‘File written‘)
  } catch (err) {
    console.log(err)
  }
}

Babel

ES6 标准发布后,前端人员也开发渐渐了解到了ES6,但是由于兼容性的问题,仍然没有得到广泛的推广,不过业界也用了一些折中性的方案来解决兼容性和开发体系问题。其中最有名的莫过于 Babel 了,Babel 是一个广泛使用的转码器,他的目标是使用 Babel 可以转换所有 ES6 新语法,从而在现有环境执行。

Use next generation JavaScript, today

Babel 不仅能够转换 ES6 代码,同时还是 ES7 的试验场。比如已经支持 async/await,使开发者更容易编写异步代码,代码逻辑和可读性简直不能太好了。虽然主流浏览器可能还需要一段时间才能支持这个异步编码方式,但是基于 Babel,开发者现在就可以在生产环境使用上它。这得益于 Babel 与 JavaScript 技术委员会保持高度一致,能够在 ECMAScript 新特性在标准化之前提供现实世界可用的实现。因此开发者能 在生产环境大量使用未发布或未广泛支持的语言特性,ECMAScript 也能够在规范最终定稿前获得现实世界的反馈,这种正向反馈又进一步推动了 JavaScript 语言向前发展。

Babel 最简单的使用方式如下:

# 安装 babel-cli 和 babel-preset-es2015 插件
npm install -g babel-cli
npm install --save babel-preset-es2015

在当前目录下建立文件.babelrc,写入:

{
  "presets": [‘es2015‘]
}

ESLint

一个高质量的项目必须包含完善的 lint,如果一个项目中还是 tab、两个空格、四个空格各种混搭风,一个函数动不动上百行,各种 if、嵌套、回调好几层。加上前面提到的各种 JavaScript 糟粕和鸡肋,一股浓厚的城乡结合部风扑面而来,这还怎么写代码,每天调调代码格式好了。

这又怎么行呢,拿工资就得好好写代码,因此 lint 非常有必要,特别是对于大型项目,他可以保证代码符合一定的风格,有起码的可读性,团队里的其他人可以尽快掌握他人的代码。对于 JavaScript 项目而言,目前 ESLint 将是一个很好的选择。ESLint 的安装过程就不介绍了,请参考官网,下面讲一个非常严格的 ESLint 的配置,这是对上面编写简洁的 JavaScript 代码一节最好的回应。

{
  parser: ‘babel-eslint‘,
  extends: [‘airbnb‘, ‘plugin:react/recommended‘, ‘prettier‘, ‘prettier/react‘],
  plugins: [‘react‘, ‘prettier‘],
  rules: {
    // prettier 配置用于自动化格式代码
    ‘prettier/prettier‘: [
      ‘error‘,
      {
        singleQuote: true,
        semi: false,
        trailingComma: ‘all‘,
        bracketSpacing: true,
        jsxBracketSameLine: true,
      },
    ],
    // 一个函数的复杂性不超过 10,所有分支、循环、回调加在一起,在一个函数里不超过 10 个
    complexity: [2, 10],
    // 一个函数的嵌套不能超过 4 层,多个 for 循环,深层的 if-else,这些都是罪恶之源
    ‘max-depth‘: [2, 4],
    // 一个函数最多有 3 层 callback,使用 async/await
    ‘max-nested-callbacks‘: [2, 3],
    // 一个函数最多 5 个参数。参数太多的函数,意味着函数功能过于复杂,请拆分
    ‘max-params‘: [2, 5],
    // 一个函数最多有 50 行代码,如果超过了,请拆分之,或者精简之
    ‘max-statements‘: [2, 50],
    // 坚定的 semicolon-less 拥护者
    semi: [2, ‘never‘],
  },
  env: {
    browser: true,
  },
}
时间: 2024-10-04 01:14:15

编写简洁的 JavaScript 代码的相关文章

编写高质量JavaScript代码的68个有效方法

简介: <Effective JavaScript:编写高质量JavaScript代码的68个有效方法>共分为7章,分别涵盖JavaScript的不同主题.第1章主要讲述最基本的主题,如版本.类型转换要点.运算符注意事项和分号局限等.第2章主要讲解变量作用域,介绍此方面的一些基本概念,以及一些最佳实践经验.第3章主要讲解函数的使用,深刻解析函数.方法和类,并教会读者在不同的环境下高效使用函数.第4章主要讲解原型和对象,分析JavaScript的继承机制以及原型和对象使用的最佳实践和原则.第5章

编写高质量javascript代码的基本要点

javascript入门比较快,基础方面也比较简单,但如果想写出高质量的javascript代码也绝非易事,下图是在下整理的编写高质量javascript代码的基本要点,希望能够对各位有所帮助.

深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点(转)

才华横溢的Stoyan Stefanov,在他写的由O’Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会是件很美妙的事情.具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以

认清Javascript的地位并编写合理的Javascript代码

作为前端程序员,一定要认清javascript的地位,不要被它乱七八糟的特点所迷惑.JavaScript主要是用来操控和重新调整DOM,通过修改DOM结构,从而达到修改页面效果的目的. 要用这个中心思想指导以后的所有js学习,包括它的框架. 所谓合理就是合乎道理.那么合理的javascript代码通常遵循哪些原则?笔者认为至少要遵循以下几点: 有意义的命名 无论选择驼峰命名法还是匈牙利命名法,见名知意是最重要也最基本的一条法则.同时也要避免会产生歧义的命名. 例如 变量“当前时间” var d;

&lt;深入理解JavaScript&gt;学习笔记(1)_编写高质量JavaScript代码的基本要点

注:本文是拜读了 深入理解JavaScript 之后深有感悟,故做次笔记方便之后查看. JQuery是一个很强大的JavaScript 类库,在我刚刚接触JavaScript的就开始用了. JQuery使用起来非常方便,以至于我这样的JS小白在任何网站上都会毫不犹豫的引入JQuery.....(我想这样做的不止我一个人吧,哈哈) 由于最近新项目中开始使用各种JavaScript 插件(JQueryUI, JQuery.dataTable,uploadify等....),需要进一步封装,以方便使用

《编写高质量JavaScript代码的68个有效方法》

第1章 让自己习惯JavaScript 第1条:了解你使用的JavaScript版本 决定你的应用程序支持JavaScript的哪些版本. 确保你使用的JavaScript的特性对于应用程序将要运行的所有环境都是支持的. 第2条:理解JavaScript的浮点数 JavaScript中的数字都是作为双精度的64位浮点数来储存的:而JavaScript的整数都仅仅是双精度浮点数的一个子集,不是单独的数据类型.而小数和小数相加有时候是不精确的,如果你用作货币计算,最好换算成最小的货币来计算. 第3条

0003.深入理解JavaScript系列学习:编写高质量JavaScript代码的基本要点

推荐 汤姆大叔博客园深入理解JavaScript系列 此文来源:http://www.cnblogs.com/TomXu/archive/2011/12/28/2286877.html 书写可维护的代码(Writing Maintainable Code ) 即 代码的可读写维护性 博客园<行者自若的技术笔记> 中参考代码规范与读写可维护性 可作为参考,我也转载了文章到自己的博客园 http://www.cnblogs.com/wolongjv/articles/5937898.html 概括

Effective JavaScript: 编写高质量JavaScript代码的68个有效方法(目录)

本书赞誉译者序序前言第 1 章 让自己习惯 JavaScript 1第 1 条: 了解你使用的 JavaScript版本 1第 2 条:理解 JavaScript 的浮点数 6第 3 条:当心隐式的强制转换 8第 4 条:原始类型优于封装对象 13第 5 条: 避免对混合类型使用== 运算符 14第 6 条:了解分号插入的局限 16第 7 条: 视字符串为 16 位的代码单元序列 21第 2 章 变量作用域 25第 8 条:尽量少用全局对象 25第 9 条:始终声明局部变量 27第 10 条:避

编写高质量javascript代码(1-10)

第1条:了解你使用的js版本 许多js引擎支持const关键字定义变量,但ECMAScript标准并没有定义任何关于const关键字的语义和行为.此外,在不同的实现之间,const关键字的行为也是不一样的. 在某些情况下,const关键字修饰的变量不能被更新: 1 const PI = 3.14.592653589793; 2 PI = "modified"; 3 PI; //3.14.592653589793 而其他的实现只是简单地将const视为var的代名词: const PI