还在担心不会常用的 JS 代码规范吗?看这篇就够了。

727 阅读16分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

JS 规范

1 前言

JavaScript一直有着广泛的应用,特别是在浏览器端的行为管理。本文档的目标是使JavaScript代码风格保持一致,容易被理解和被维护。

虽然本文档是针对JavaScript设计的,但是在使用各种JavaScript的预编译语言时(如TypeScript等)时,适用的部分也应尽量遵循本文档的约定。

2 代码规范

统一团队的编码规范,有助于代码的维护。本章是传统意义上的 Style Guideline,目的是统一一些相对主观化的代码风格。

2.1 单行代码块

特别说明:

在单行代码块中使用空格

//good
function foo () { return true }
if (foo) { bar = 0 }

//bad
function foo () {return true}
if (foo) {bar = 0}

2.2 大括号风格

特别说明:

在编程过程中,大括号风格与缩进风格紧密联系,用来描述大括号相对代码块位置的方法有很多。在 JavaScript 中,主要有三种风格,如下:

  • One True Brace Style

    if (foo) {
      bar()
    } else {
      baz()
    }
    
  • Stroustrup

    if (foo) {
      bar()
    }
    else {
      baz()
    }
    
  • Allman

    if (foo)
    {
      bar()
    }
    else
    {
      baz()
    }
    

我们团队约定使用 One True Brace Style 风格

2.3 变量命名

特别说明:

当命名变量时,主流分为驼峰式命名(constiableName)和下划线命名(constiable_name)两大阵营。

团队约定使用驼峰式命名

2.4 拖尾逗号

特别说明:

在 ECMAScript5 里面,对象字面量中的拖尾逗号是合法的,但在 IE8(非 IE8 文档模式)下,当出现拖尾逗号,则会抛出错误。

拖尾逗号的例子:

const foo = {
  name: 'foo',
  age: '22',
}

拖尾逗号的好处是,简化了对象和数组添加或删除元素,我们只需要修改新增的行即可,并不会增加差异化的代码行数。

因为拖尾逗号有好也有不好,所以团队约定允许在最后一个元素或属性与闭括号 ]} 在不同行时,可以(但不要求)使用拖尾逗号。当在同一行时,禁止使用拖尾逗号。

2.5 逗号空格

特别说明:

逗号前后的空格可以提高代码的可读性,团队约定在逗号后面使用空格,逗号前面不加空格。

//good
const foo = 1, bar = 2

//bad
const foo = 1,bar = 2
const foo = 1 , bar = 2
const foo = 1 ,bar = 2

2.6 逗号风格

特别说明:

逗号分隔列表时,在 JavaScript 中主要有两种逗号风格:

  • 标准风格,逗号放置在当前行的末尾
  • 逗号前置风格,逗号放置在下一行的开始位置

团队约定使用标准风格

//good
const foo = 1,
    bar = 2

const foo = ['name',
            'age']

//bad
const foo = 1
,
bar = 2

const foo = 1
, bar = 2

const foo = ['name'
          , 'age']

2.7 计算属性的空格

特别说明:

团队约定在对象的计算属性内,禁止使用空格

//good
obj['foo']

//bad
obj['foo' ]
obj[ 'foo']
obj[ 'foo' ]

2.8 拖尾换行

特别说明:

在非空文件中,存在拖尾换行是一个常见的 UNIX 风格,它的好处是可以方便在串联和追加文件时不会打断 Shell 的提示。在日常的项目中,保留拖尾换行的好处是,可以减少版本控制时的代码冲突。

//good
function func () {
  // do something
}
  // 此处是新的一行

//bad
function func () {
  // do something
}

可以通过 .editorconfig 添加 EOL

2.9 函数调用

特别说明:

为了避免语法错误,团队约定在函数调用时,禁止使用空格

//good
fn()

//bad
fn ()
fn
()

2.10 缩进

特别说明:

代码保持一致的缩进,是作为工程师的职业素养。但缩进用两个空格,还是四个空格,是用 Tab 还是空格呢?这样的争论太多了,也得不出答案。本规范结合了市面上优秀的开源项目,姑且约定使用 空格 来缩进,而且缩进使用四个空格。

那是不是不能使用 Tab 进行缩进了?我们可以通过配置 .editorconfig ,将 Tab 自动转换为空格。

2.11 对象字面量的键值缩进

特别说明:

团队约定对象字面量的键和值之间不能存在空格,且要求对象字面量的冒号和值之间存在一个空格

//good
const obj = { 'foo': 'haha' }

//bad
const obj = { 'foo' : 'haha' }

2.12 构造函数首字母大写

特别说明:

在 JavaScript 中 new 操作符用来创建某个特定类型的对象的一个实例,该类型的对象是由一个构造函数表示的。由于构造函数只是常规函数,唯一区别是使用 new 来调用。所以我们团队约定构造函数的首字母要大小,以此来区分构造函数和普通函数。

//good
const fooItem = new Foo()

//bad
const fooItem = new foo()

2.13 构造函数的参数

特别说明:

在 JavaScript 中,通过 new 调用构造函数时,如果不带参数,可以省略后面的圆括号。但这样会造成与整体的代码风格不一致,所以团队约定使用圆括号

//good
const person = new Person()

//bad
const person = new Person

2.14 链式调用

特别说明:

链式调用如果放在同一行,往往会造成代码的可读性差,但有些时候,短的链式调用并不会影响美观。所以本规范约定一行最多只能有四个链式调用,超过就要求换行。

2.15 空行

特别说明:

空白行对于分离代码逻辑有帮助,但过多的空行会占据屏幕的空间,影响可读性。团队约定最大连续空行数为 2。

//good
const a = 1


const b = 2

//bad
const a = 1



const b = 2

2.16 链式赋值

特别说明:

链式赋值容易造成代码的可读性差,所以团队约定禁止使用链式赋值。

//good
const a = 1
const b = 1
const c = 1

//bad
const a = b = c = 1

2.17 变量声明

特别说明:

JavaScript 允许在一个声明中,声明多个变量。团队约定在声明变量时,一个声明只能有一个变量。

//good
const a
const b
const c

//bad
const a, b, c

2.18 分号

特别说明:

JavaScript 在所有类 C 语言中是比较独特的,它不需要在每个语句的末尾有分号。在很多情况下,JavaScript 引擎可以确定一个分号应该在什么位置然后自动添加它。此特征被称为 自动分号插入 (ASI),被认为是 JavaScript 中较为有争议的特征。

团队中对于是否应该使用分号,也有许多争论,本规范强制推荐使用分号。(并且会在jshint检测中体现)

相关参考 :semi

2.19 代码块空格

特别说明:

一致性是任何风格指南的重要组成部分。虽然在哪里放置块的开括号纯属个人偏好,但在整个项目中应该保持一致。不一致的风格将会分散读者阅读代码的注意力。

团队约定代码块前要添加空格

//good
if (a) {
  b()
}

function a () {}

//bad
if (a){
  b()
}

function a (){}

2.20 函数声明的空格

特别说明:

当格式化一个函数,函数名或 function 关键字与左括号之间允许有空白。命名函数要求函数名和 function 关键字之间有空格,但是匿名函数要求不加空格。

团队约定函数括号前要加空格

//good
function func (x) {
  // ...
}

//bad
function func(x) {
  // ...
}

2.21 操作符的空格

特别说明:

团队约定操作符前后都需要添加空格

//good
const sum = 1 + 2

//bad
const sum = 1+2

2.22 BOM

特别说明:

Unicode 字节顺序标记 (BOM) 用来指定代码单元是高字节序还是低字节序。也就是说,是高位在前还是低位在前。UTF-8 不需要 BOM 来表明字节顺序,因为单个字节并不影响字节顺序。

相信不少同学遇到过 BOM 的坑,这里不多说了,切记不要使用 windows 的记事本改代码!

2.23 引号

特别说明:

团队约定 js 中统一用单引号

//good
let userName = 'Tony'

//bad
let userName = "Tony"

2.24 函数行数

特别说明:

团队约定单一函数的代码行数应不超过 50 行

3 语言规范

JavaScript 是一种客户端脚本语言,这里列出了编写 JavaScript 时需要遵守的规则。

3.1 类型

  • 原始类型: 存取原始类型直接作用于值本身

    • 布尔类型
    • Null 类型
    • Undefined 类型
    • 数字类型
    • BigInt 类型
    • 字符串类型
    • 符号类型 Symbol
    const foo = 1
    let bar = foo
    
    bar = 9
    
    console.log(foo, bar) // 1, 9
    
  • 复杂类型: 访问复杂类型作用于值的引用

    • object
    • array
    • function
    const foo = [1, 2, 3]
    const bar = foo
    
    bar[0] = 9
    
    console.log(foo[0], bar[0]) // 9, 9
    

3.2 引用

  • 请记得 constlet 都是块级作用域,var 是函数级作用域
// const and let only exist in the blocks they are defined in.
{
  let a = 1
  const b = 1
}
console.log(a) // ReferenceError
console.log(b) // ReferenceError

原因:这样做可以确保你无法重新分配引用,以避免出现错误和难以理解的代码

// good
const a = 1
const b = 2

// bad
var a = 1
var b = 2
  • 如果引用是可变动的,使用 let 代替 var,eslint: no-var

原因:let 是块级作用域的,而不像 var 属于函数级作用域

// good
let count = 1
if (count < 10) {
  count += 1
}

// bad
var count = 1
if (count < 10) {
  count += 1
}

3.3 对象

  • 请使用字面量值创建对象,eslint: no-new-object

    // good
    const a = {}
    
    // bad
    const a = new Object{}
    
  • 别使用保留字作为对象的键值,这样在 IE8 下不会运行

    // good
    const a = {
      defaults: {},
      common: {}
    }
    
    // bad
    const a = {
      default: {},  // default 是保留字
      common: {}
    }
    
  • 当使用动态属性名创建对象时,请使用对象计算属性名来进行创建

原因:因为这样做就可以让你在一个地方定义所有的对象属性

function getKey(k) {
  return `a key named ${k}`
}

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true
};

// bad
const obj = {
  id: 5,
  name: 'San Francisco'
};
obj[getKey('enabled')] = true
  • 请使用对象方法的简写方式,eslint: object-shorthand

    // good
    const item = {
      value: 1,
    
      addValue (val) {
        return item.value + val
      }
    }
    
    // bad
    const item = {
      value: 1,
    
      addValue: function (val) {
        return item.value + val
      }
    }
    
  • 请使用对象属性值的简写方式,eslint: object-shorthand

原因:这样更简短且描述更清楚

const job = 'FrontEnd'

// good
const item = {
  job
}

// bad
const item = {
  job: job
}
  • 将简写的对象属性分组后统一放到对象声明的开头

原因:这样更容易区分哪些属性用了简写的方式

const job = 'FrontEnd'
const department = 'JDC'

// good
const item = {
  job,
  department,
  sex: 'male',
  age: 25
}

// bad
const item = {
  sex: 'male',
  job,
  age: 25,
  department
}
  • 只对非法标识符的属性使用引号,eslint: quote-props

原因:因为通常来说我们认为这样主观上会更容易阅读,这样会带来代码高亮上的提升,同时也更容易被主流 JS 引擎优化

// good
const good = {
  foo: 3,
  bar: 4,
  'data-blah': 5
}

// bad
const bad = {
  'foo': 3,
  'bar': 4,
  'data-blah': 5
}
  • 不要直接使用 Object.prototype 的方法, 例如 hasOwnProperty, propertyIsEnumerableisPrototypeOf 方法,eslint: no-prototype-builtins

原因:这些方法可能会被对象自身的同名属性覆盖 - 比如 { hasOwnProperty: false } 或者对象可能是一个 null 对象(Object.create(null))


// good
console.log(Object.prototype.hasOwnProperty.call(object, key))

// best
const has = Object.prototype.hasOwnProperty // cache the lookup once, in module scope.
console.log(has.call(object, key))
/* or */
import has from 'has' // https://www.npmjs.com/package/has
console.log(has(object, key))

// bad
console.log(object.hasOwnProperty(key))
  • 优先使用对象展开运算符 ... 来做对象浅拷贝而不是使用 Object.assign,使用对象剩余操作符来获得一个包含确定的剩余属性的新对象
// good
const original = { a: 1, b: 2 }
const copy = { ...original, c: 3 } // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy // noA => { b: 2, c: 3 }

// bad
const original = { a: 1, b: 2 }
const copy = Object.assign({}, original, { c: 3 }) // copy => { a: 1, b: 2, c: 3 }

// very bad
const original = { a: 1, b: 2 }
const copy = Object.assign(original, { c: 3 }) // this mutates `original` ಠ_ಠ
delete copy.a // so does this

3.4 数组

  • 请使用字面量值创建数组,eslint: no-array-constructor

    // good
    const items = []
    
    // bad
    const items = new Array()
    
  • 向数组中添加元素时,请使用 push 方法

    const items = []
    
    // good
    items.push('test')
    
    // bad
    items[items.length] = 'test'
    
  • 使用展开运算符 ... 复制数组

    // good
    itemsCopy = [...items]
    
    // bad
    const items = []
    const itemsCopy = []
    const len = items.length
    let i
    
    // bad
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i]
    }
    
  • 把一个可迭代的对象转换为数组时,使用展开运算符 ... 而不是 Array.from

const foo = document.querySelectorAll('.foo')

// good
const nodes = Array.from(foo)

// best
const nodes = [...foo]
  • 使用 Array.from 来将一个类数组对象转换为数组
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }

// good
const arr = Array.from(arrLike)

// bad
const arr = Array.prototype.slice.call(arrLike)
  • 遍历迭代器进行映射时使用 Array.from 代替扩展运算符 ..., 因为这可以避免创建中间数组
// good
const baz = Array.from(foo, bar)

// bad
const baz = [...foo].map(bar)
  • 使用数组的 map 等方法时,请使用 return 声明,如果是单一声明语句的情况,可省略 return

    // good
    [1, 2, 3].map(x => {
      const y = x + 1
      return x * y
    })
    
    // good
    [1, 2, 3].map(x => x + 1)
    
    // bad
    const flat = {}
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item)
      flat[index] = flatten
    })
    
    // good
    const flat = {}
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item)
      flat[index] = flatten
      return flatten
    })
    
    // bad
    inbox.filter((msg) => {
      const { subject, author } = msg
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee'
      } else {
        return false
      }
    })
    
    // good
    inbox.filter((msg) => {
      const { subject, author } = msg
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee'
      }
    
      return false
    })
    
  • 如果一个数组有多行则要在数组的开括号后和闭括号前使用新行

// good
const arr = [[0, 1], [2, 3], [4, 5]]

const objectInArray = [
  {
    id: 1
  },
  {
    id: 2
  }
]

const numberInArray = [
  1,
  2
]

// bad
const arr = [
  [0, 1], [2, 3], [4, 5]
]

const objectInArray = [{
  id: 1
}, {
  id: 2
}]

const numberInArray = [
  1, 2
]

3.5 解构赋值

愿意:解构可以避免创建属性的临时引用

// good
function getFullName (user) {
  const { firstName, lastName } = user

  return `${firstName} ${lastName}`
}

// better
function getFullName ({ firstName, lastName }) {
  return `${firstName} ${lastName}`
}

// bad
function getFullName (user) {
  const firstName = user.firstName
  const lastName = user.lastName

  return `${firstName} ${lastName}`
}
  • 当需要使用数组的多个值时,请同样使用解构赋值,eslint: prefer-destructuring

    const arr = [1, 2, 3, 4]
    
    // good
    const [first, second] = arr
    
    // bad
    const first = arr[0]
    const second = arr[1]
    
  • 函数需要回传多个值时,请使用对象的解构,而不是数组的解构

原因:可以非破坏性地随时增加或者改变属性顺序

// bad
function doSomething () {
  return [top, right, bottom, left]
}

// 如果是数组解构,那么在调用时就需要考虑数据的顺序
const [top, xx, xxx, left] = doSomething()

// good
function doSomething () {
  return { top, right, bottom, left }
}

// 此时不需要考虑数据的顺序
const { top, left } = doSomething()

3.6 字符串

  • 字符串统一使用单引号的形式 '',eslint: quotes

    // good
    const department = 'JDC'
    
    // bad
    const department = "JDC"
    
  • 字符串太长的时候,请不要使用字符串连接符换行 \,而是使用 +

    const str = '公司 公司 公司' +
      '公司 公司 公司' +
      '公司 公司'
    
  • 程序化生成字符串时,请使用模板字符串,eslint: prefer-template template-curly-spacing

    const test = 'test'
    
    // good
    const str = `ab${test}`
    
    // bad
    const str = ['a', 'b', test].join()
    
    // bad
    const str = 'a' + 'b' + test
    
  • 不要对字符串使用eval(),会导致太多漏洞, eslint: no-eval

  • 不要在字符串中使用不必要的转义字符, eslint: no-useless-escape

// good
const foo = '\'this\' is "quoted"'
const foo = `my name is '${name}'`

// bad
const foo = '\'this\' \i\s \"quoted\"'

3.7 函数

  • 不要使用Function构造函数创建函数, eslint: no-new-func

原因:此方式创建函数和对字符串使用 eval() 一样会产生漏洞

// bad
const add = new Function('a', 'b', 'return a + b')

// still bad
const subtract = Function('a', 'b', 'return a - b')
const f = function(){}
const g = function (){}
const h = function() {}

// good
const x = function b () {}
const y = function a () {}
  • 使用具名函数表达式而非函数声明(非强制),eslint: func-style

原因:这样做会导致函数声明被提升,这意味着很容易在文件中定义此函数之前引用它,不利于可读性和可维护性。如果你发现函数定义既庞大又复杂以至于不能理解文件的其他部分,或许你应该将它拆分成模块!别忘记要显式命名表达式,而不用管名字是否是从包含的变量(通常出现在现代浏览器中或者使用 Babel 编译器的时候)中推断的。这样会消除错误调用堆栈中的任何假设。 (讨论)

// bad
function foo () {
  // ...
}

// bad
const foo = function () {
  // ...
}

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo () {
  // ...
}
  • 用圆括号包裹自执行匿名函数,eslint:wrap-iife

原因:一个立即执行匿名函数表达式是一个单一的单元,将其及其调用括号包装在括号中,能够清楚地表达这一点。注意,在到处都是模块的世界中几乎不需要 IIFE。

// immediately-invoked function expression (IIFE)
(function () {
  console.log('Welcome to the Internet. Please follow me.')
}())
  • 不要在非函数代码块(if , while 等)中声明函数,eslint:no-loop-func

    // good
    let test
    if (isUse) {
      test = () => {
        // do something
      }
    }
    
    // bad
    if (isUse) {
      function test () {
        // do something
      }
    }
    
  • 不要将参数命名为 arguments,会导致该参数的优先级高于每个函数作用域内原先存在的 arguments 对象

// good
function foo (name, options, args) {
  // ...
}

// bad
function foo (name, options, arguments) {
  // ...
}
  • 不要使用 arguments,使用 剩余运算符 ...

    arguments 只是一个类数组,而 ... 是一个真正的数组

    // good
    function test (...args) {
      return args.join('')
    }
    
    // bad
    function test () {
      const args = Array.prototype.slice.call(arguments)
      return args.join('')
    }
    
  • 使用参数默认值语法而不是修改函数参数

// really bad
function handleThings (opts) {
  // No! We shouldn't mutate function arguments.
  // Double bad: if opts is falsy it'll be set to an object which may
  // be what you want but it can introduce subtle bugs.
  opts = opts || {}
  // ...
}

// still bad
function handleThings (opts) {
  if (opts === void 0) {
    opts = {}
  }
  // ...
}

// good
function handleThings (opts = { }) {
  // ...
}
  • 避免参数默认值的副作用
let b = 1
// bad
function count (a = b++) {
  console.log(a)
}
count()  // 1
count()  // 2
count(3) // 3
count()  // 3
  • 将参数默认值放在最后
// good
function handleThings (name, opts = {}) {
  // ...
}

// bad
function handleThings (opts = {}, name) {
  // ...
}

原因:操作作为参数传入的对象可能在原始调用中造成意想不到的变量副作用

// good
function f2 (obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1
}

// bad
function f1 (obj) {
  obj.key = 1
}

原因:参数重新赋值可能会导致无法预期的行为,尤其是当操作 arguments 对象时,也可能导致优化问题,尤其是在 V8 引擎中

// good
function f3 (a) {
  const b = a || 1
}

function f4 (a = 1) {
}

// bad
function f1 (a) {
  a = 1
}

function f2 (a) {
  if (!a) { a = 1 }
}
  • 调用可变参数函数时建议使用展开运算符 ...., eslint: prefer-spread

原因:显然你无需使用上下文,很难结合 newapply

// good
const x = [1, 2, 3, 4, 5]
console.log(...x)

// bad
const x = [1, 2, 3, 4, 5]
console.log.apply(console, x)

// good
new Date(...[2016, 8, 5])

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]))

3.8 箭头函数

原因:它将创建在 this 上下文中执行的函数版本,通常是您想要的,并且语法更简洁

如果您有一个相当复杂的函数,则可以将该逻辑移到其自己的命名函数表达式中

// good
[1, 2, 3].map((x) => {
  const y = x + 1
  return x * y
})

// bad
[1, 2, 3].map(function (x) {
  const y = x + 1
  return x * y
})
  • 如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的 return, 否则保留花括号并使用 return 语句,eslint: arrow-parens, arrow-body-style
// bad
[1, 2, 3].map(number => {
  const nextNumber = number + 1
  `A string containing the ${nextNumber}.`
})

// good
[1, 2, 3].map(number => `A string containing the ${number}.`)

// good
[1, 2, 3].map((number) => {
  const nextNumber = number + 1
  return `A string containing the ${nextNumber}.`
})

// good
[1, 2, 3].map((number, index) => ({
  index: number
}))

// No implicit return with side effects
function foo(callback) {
  const val = callback()
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false

// good
foo(() => {
  bool = true
})

// bad
foo(() => bool = true)
  • 一旦表达式跨多行,使用圆括号包裹以便更好阅读
// good
['get', 'post', 'put'].map(httpMethod => (
  Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod
  )
))

// bad
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod
  )
)
  • 函数如果只接收一个参数并且没使用用花括号,则省略圆括号,否则为了清晰明确则使用圆括号包裹参数,注意:总是使用圆括号也是可以接受的,eslint 中的 "always" 选项,eslint: arrow-parens
// bad
[1, 2, 3].map((x) => x * x)

// good
[1, 2, 3].map(x => x * x)

// good
[1, 2, 3].map(number => (
  `A long string with the ${number}. It’s so long that we’ve broken it ` +
  'over multiple lines!'
))

// bad
[1, 2, 3].map(x => {
  const y = x + 1
  return x * y
})

// good
[1, 2, 3].map((x) => {
  const y = x + 1
  return x * y
})

3.9 类&构造函数

  • 使用 class,避免直接操作 prototype

    // good
    class Queue {
      constructor (contents = []) {
        this._queue = [...contents]
      }
    
      pop () {
        const value = this._queue[0]
        this._queue.splice(0, 1)
        return value
      }
    }
    
    // bad
    function Queue (contents = []) {
      this._queue = [..contents]
    }
    Queue.prototype.pop = function () {
      const value = this._queue[0]
      this._queue.splice(0, 1)
      return value
    }
    
  • 使用 extends 来实现继承

原因:这是一个不会破坏 instanceof 的内建实现原型式继承的方式

// good
class PeekableQueue extends Queue {
  peek () {
    return this.queue[0]
  }
}

// bad
const inherits = require('inherits')
function PeekableQueue(contents) {
  Queue.apply(this, contents)
}
inherits(PeekableQueue, Queue)
PeekableQueue.prototype.peek = function () {
  return this.queue[0]
}
  • 如果未声明构造函数,则类会有一个默认的构造函数,没必要用空的构造函数或者将其委托给父类,eslint: no-useless-constructor
// good
class Rey extends Jedi {
  constructor (...args) {
    super(...args)
    this.name = 'Rey'
  }
}

// bad
class Jedi {
  constructor () {}

  getName() {
    return this.name
  }
}

// bad
class Rey extends Jedi {
  constructor (...args) {
    super(...args)
  }
}

原因:重复的类成员声明会默认使用最后声明的,通常会导致 bug

// good
class Foo {
  bar () { return 1 }
}

// good
class Foo {
  bar () { return 2 }
}

// bad
class Foo {
  bar () { return 1 }
  bar () { return 2 }
}

3.10 模块

  • 使用标准的 ES6 模块语法 importexport

原因:模块是未来,让我们现在开始使用未来的特性

// bad
const util = require('./util')
module.exports = util

// good
import Util from './util'
export default Util

// better
import { Util } from './util'
export default Util
  • 不要使用 import 的通配符 *,这样可以确保你只有一个默认的 export

    // good
    import Util from './util'
    
    // bad
    import * as Util from './util'
    
  • 同个文件每个模块只允许 import 一次,有多个 import 请书写在一起,eslint: no-duplicate-imports

原因:这样可以让代码更易于维护

// good
import foo, { named1, named2 } from 'foo'

// good
import foo, {
  named1,
  named2
} from 'foo'

// bad
import foo from 'foo'
// … some other imports … //
import { named1, named2 } from 'foo'
// good
import foo from 'foo'
import bar from 'bar'

foo.init()

// bad
import foo from 'foo'
foo.init()

import bar from 'bar'
  • 多行导入应该像多行数组和对象文字一样缩进
// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE
} from 'path'

// bad
import { longNameA, longNameB, longNameC, longNameD, longNameE } from 'path'
// good
import fooSass from 'foo.scss'
import barCss from 'bar.css'

// bad
import fooSass from 'css!sass!foo.scss'
import barCss from 'style!css!bar.css'

3.11 迭代器

  • 不要使用 iterators,建议使用 JS 更高优先级的函数代替 for-in 或 for-of 循环,除非迫不得已,eslint: no-iterator no-restricted-syntax
const numbers = [1, 2, 3, 4, 5]

// bad
let sum = 0
for (let num of numbers) {
  sum += num
}

// good
let sum = 0
numbers.forEach(num => sum += num)

// better
const sum = numbers.reduce((total, num) => total + num, 0)

3.12 生成器

  • 现阶段请不要使用生成器 generator

原因:因为不能很好地翻译成 ES5 代码

3.13 对象属性

  • 使用 . 来访问对象属性

    const joke = {
      name: 'haha',
      age: 28
    }
    
    // good
    const name = joke.name
    
    // bad
    const name = joke['name']
    
  • 当访问的属性是变量时使用 []

const luke = {
  jedi: true,
  age: 28,
}

function getProp (prop) {
  return luke[prop]
}

const isJedi = getProp('jedi')

3.14 变量声明

  • 声明变量时,请使用 constlet 关键字,如果没有写关键字,变量就会暴露在全局上下文中,这样很可能会和现有变量冲突,另外,也很难明确该变量的作用域是什么。这里推荐使用 const 来声明变量,我们需要避免全局命名空间的污染。eslint: no-undef prefer-const

    // good
    const demo = new Demo()
    
    // bad
    demo = new Demo()
    
  • 将所有的 constlet 分组

    // good
    const b
    const d
    let a
    let c
    let e
    
    // bad
    let a
    const b
    let c
    const d
    let e
    
  • 变量不要进行链式赋值

原因:变量链式赋值会创建隐藏的全局变量

// good
(function example() {
  let a = 1
  let b = a
  let c = a
}())

console.log(a) // throws ReferenceError
console.log(b) // throws ReferenceError
console.log(c) // throws ReferenceError

// the same applies for `const`

// bad
(function example() {
  // JavaScript interprets this as
  // let a = ( b = ( c = 1 ) );
  // The let keyword only applies to variable a; variables b and c become
  // global variables.
  let a = b = c = 1
}())

console.log(a) // throws ReferenceError
console.log(b) // 1
console.log(c) // 1

原因:声明但未被使用的变量通常是不完全重构犯下的错误.这种变量在代码里浪费空间并会给读者造成困扰

// good
function getXPlusY (x, y) {
  return x + y
}

const x = 1
const y = a + 2

alert(getXPlusY(x, y))

// 'type' is ignored even if unused because it has a rest property sibling.
// This is a form of extracting an object that omits the specified keys.
const { type, ...coords } = data
// 'coords' is now the 'data' object without its 'type' property.

// bad
var some_unused_var = 42

// Write-only variables are not considered as used.
var y = 10
y = 5

// A read for a modification of itself is not considered as used.
var z = 0
z = z + 1

// Unused function arguments.
function getX (x, y) {
  return x
}

3.15 Hoisting

  • var 存在变量提升的情况,即 var 声明会被提升至该作用域的顶部,但是他们的赋值并不会。而 constlet 并不存在这种情况,他们被赋予了 Temporal Dead Zones, TDZ, 了解 typeof 不再安全很重要

    function example () {
      console.log(notDefined)   // => throws a ReferenceError
    }
    
    function example () {
      console.log(declareButNotAssigned)  // => undefined
      var declaredButNotAssigned = true
    }
    
    function example () {
      let declaredButNotAssigned
      console.log(declaredButNotAssigned)   // => undefined
      declaredButNotAssigned = true
    }
    
    function example () {
      console.log(declaredButNotAssigned)   // => throws a ReferenceError
      console.log(typeof declaredButNotAssigned)  // => throws a ReferenceError
      const declaredButNotAssigned = true
    }
    
  • 匿名函数的变量名会提升,但函数内容不会

    function example () {
      console.log(anonymous)  // => undefined
    
      anonymous()
    
      var anonymous = function () {
        console.log('test')
      }
    }
    
  • 命名的函数表达式的变量名会被提升,但函数名和函数函数内容并不会

    function example() {
      console.log(named)  // => undefined
    
      named()   // => TypeError named is not a function
    
      superPower()  // => ReferenceError superPower is not defined
    
      var named = function superPower () {
        console.log('Flying')
      }
    }
    
    function example() {
      console.log(named)  // => undefined
    
      named()   // => TypeError named is not a function
    
      var named = function named () {
        console.log('named')
      }
    }
    

3.16 比较运算符&相等

  • 使用 ===!== 而非 ==!=,eslint: eqeqeq

  • 条件声明例如 if 会用 ToBoolean 这个抽象方法将表达式转成布尔值并遵循如下规则

    • Objects 等于 true
    • Undefined 等于 false
    • Null 等于 false
    • Booleans 等于 布尔值
    • Numbers+0, -0, 或者 NaN 的情况下等于 false, 其他情况是 true
    • Strings'' 时等于 false, 否则是 true
    if ([0] && []) {
      // true
      // 数组(即使是空数组)也是对象,对象等于true
    }
    

3.17 分号

  • 我们遵循 Standard 的规范,强制使用分号。

    关于应不应该使用分号的讨论有很多,本规范认为非必要的时候,应该使用分号,好的 JS 程序员应该清楚场景下是一定要加分号的,相信你也是名好的开发者。

    // good
    const test = 'good';
    (function () {
      const str = 'hahaha';
    })()
    
    // bad
    const test = 'good'
    ;(() => {
      const str = 'hahaha'
    })();
    

3.18 标准特性

特别说明:

为了代码的可移植性和兼容性,我们应该最大化的使用标准方法,例如优先使用 string.charAt(3) 而不是 string[3]

3.19 eval()

特别说明:

由于 eval 方法比较 evil,所以我们约定禁止使用该方法。

3.20 with() {}

特别说明:

由于 with 方法会产生神奇的作用域,所以我们也是禁止使用该方法的。

3.21 修改内置对象的原型

特别说明:

不要修改内置对象,如 ObjectArray

4 团队约定

4.1 代码注释

特别说明:

业务有着高复杂度和高耦合度,强制团队对方法和重要参数进行备注。

建议将某功能使用到的函数集中在一块,不要上下分散。

建议对难度系数大、业务复杂度高的需求实现功能后,编写Markdown文档,封存传代。

4.2 关键词谨慎书写

特别说明:

从过往中发现经常在代码中出现debugger,在此强制要求:

[强制] 除编译过程、渲染过程、事件响应&&回调方法无法在浏览器断点调试的情况,才能在代码中添加debugger,过后必须及时删除。

4.3 减少代码爆红频次

特别说明:

项目达到一个体量后,会发现很多功能的数据结构复杂且层级深,在相互关联之后,总会偶现一些error Not defined,在所难免。

[建议] 必要性的去使用try catch来抛出。

try {
    //TODO     
} catch (error) {
    console.log(error)
}

[建议] 对引用类型的变量进行操作时进行一层判断,避免直接报错。

cb && cb()

arr && arr.forEach(() => {

})

if(obj && obj.a){

}

4.4 业务编写强制要求

[强制] 项目内时间久远且无用的代码块,细思之后可直接删除。

[强制] 函数内大段业务代码需要隔行,方便阅读。