共计 35630 个字符,预计需要花费 90 分钟才能阅读完成。
类型
基本类型
你可以直接获取到基本类型的值
string
number
boolean
null
undefined
symbol
const foo = 1; | |
let bar = foo; | |
bar = 9; | |
console.log(foo, bar); // => 1, 9 |
注意:Symbols 不能被完整的
polyfill
,所以,在不支持 Symbols 的环境下中,不应该使用symbol
类型。
复杂类型
复杂类型赋值就是获取到他的引用的值,相当于引用传递
object
array
function
const foo = [1, 2]; | |
const bar = foo; | |
bar[0] = 9; | |
console.log(foo[0], bar[0]); // => 9, 9 |
参考
永远都使用 const
为了确保你不会改变你的初始值,重复引用会导致一些不可预见的 bug
,还会让代码难以理解,所有的赋值都应该使用 const
,避免使用 var
。
eslint
- prefer-const
- no-const-assign
// bad | |
var a = 1; | |
var b = 2; | |
// good | |
const a = 1; | |
const b = 2; |
可以使用 let
如果你一定要对参数重新赋值,那就使用 let
,而不是 var
,let
是块级作用域,而 ver
是函数级作用域。
eslint
- no-var
// bad | |
var count = 1; | |
if (true) {count += 1;} | |
// good | |
let count = 1; | |
if (true) {count += 1;} |
注意 const
与 let
的块级作用域
const
与 let
声明的常量与变量都只存在于定义它们的那个块级作用域中。
{ | |
let a = 1; | |
const b = 1; | |
} | |
console.log(a); // ReferenceError | |
console.log(b); // ReferenceError |
对象
永远使用字面量创建对象
eslint
- no-new-object
// bad | |
const obj = new Object(); | |
// good | |
const obj = {}; |
使用计算属性名
当你需要创建一个带有 动态属性名 的对象时,请将所有的属性定义放在一起,可以使用 计算属性名。
function getKey(key) {return `a key named ${key}`; | |
} | |
// bad | |
const obj = { | |
id: 1, | |
name: 'Parc MG', | |
}; | |
obj[getKey('enabled')] = true; | |
// good | |
const obj = { | |
id: 1, | |
name: 'Parc MG', | |
[getKey('enabled')]: true | |
}; |
对象方法简写
eslint
- object-shorthand
// bad | |
const atom = { | |
value: 1, | |
add: function (value) {return atom.value + value;} | |
}; | |
// good | |
const atom = { | |
value: 1, | |
add(value) {return atom.value + value;} | |
}; |
属性值缩写
eslint
- object-shorthand
const name = 'Parc MG'; | |
// bad | |
const org = {name: name,}; | |
// good | |
const org = {name,}; |
将所有属性值缩写放在对象声明的最前面
const name = 'Parc MG'; | |
const url = 'https://parcmg.com'; | |
// bad | |
const org = { | |
email: 'contact@parcmg.com', | |
name, | |
created: new Date(), | |
url, | |
}; | |
// good | |
const org = { | |
name, | |
url, | |
email: 'contact@parcmg.com', | |
created: new Date(),}; |
若非必要,属性名不使用 ''
号
eslint
- quote-props
// bad | |
const bad = { | |
'foo': 1, | |
'bar': 2, | |
'foo-bar': 3, | |
}; | |
// good | |
const good = { | |
foo: 1, | |
bar: 2, | |
'foo-bar': 3, | |
}; |
不直接调用对象原型上的方法
不直接调用一个对象的 hasOwnProperty
、propertyIsEnumerable
、isPrototypeOf
等这些原型的方法,在某些情况下,这些方法可能会被屏蔽掉,比如 {hasOwnProperty: false}
或者是一个空对象 Object.create(null)
。
// bad | |
obj.hasOwnProperty(key); | |
// good | |
Object.prototype.hasOwnProperty.call(obj, key); | |
// best | |
const has = Object.prototype.hasOwnProperty; | |
has.call(obj, key); |
积极使用扩展及解构运算 ...
- 在对象的 浅拷贝 时,更推荐使用扩展运算
{...obj}
,而不是Object.assign
。 - 在获取对象指定的几个属性时,使用解构运算
{foo, bar, ...rest} = obj
eslint
- prefer-destructuring
// very bad | |
const original = {a: 1, b: 2}; | |
const copied = Object.assign(original, { c: 3}); // 这将导致 original 也被修改 | |
delete copied.a; // 这样操作之后会导致 original 也被修改 | |
console.log(original); // => {b: 2, c: 3} | |
// bad | |
const original = {a: 1, b: 2}; | |
const copied = Object.assign({}, original, { c: 3}}; | |
// good | |
const original = {a: 1, b: 2}; | |
const copied = {...original, c: 3}; | |
// 解构运算与 `rest` 赋值运算 | |
const obj = {a: 1, b: 2, c: 3}; | |
const {a, b} = obj; // 从对象 obj 中解构出 a, b 两个属性的值,并赋值给名为 a,b 的常量 | |
const {a, ...rest} = obj; // 从对象 obj 中解构出 a 的值,并赋值给名为 a 的常量,同时,创建一个由所有其它属性组成的名为 `rest` 的新对象 | |
console.log(rest); // => {b: 2, c: 3} | |
// bad | |
function getFullName(user) { | |
const firstName = user.firstName; | |
const lastName = user.lastName; | |
return `${firstName} ${lastName}`; | |
} | |
// good | |
function getFullName(user) {const { firstName, lastName} = user; | |
return `${firstName} ${lastName}`; | |
} | |
// best | |
function getFullName({firstName, lastName}) {return `${firstName} ${lastName}`; | |
} | |
// the most best | |
const getFullName = ({firstName, lastName}) => `${firstName} ${lastName}`; |
返回多值时,使用对象解构,而非数组结构
由于 JavaScript
不支持多值返回,当一个函数或者方法有多个值需要创建时,请为每一个值命名,并以所有值组成的对象为单一值返回,而不是以数组的形式返回。
// bad | |
function processInput(input) {return [left, right, top, bottom]; | |
} | |
const [left, _, top] = processInput(input); // 调用者需要在调用时,明确的知道每一个索引上的值是什么,且无法跳越前面的值取后面的值 | |
// good | |
function processInput(input) {return { left, right, top, bottom}; | |
} | |
const {left, top} = processInput(input); // 调用者可以明确的指定需要哪个值,而且不需要创建多余的变量 |
数组
使用字面量赋值
eslint
- no-array-constructor
// bad | |
const items = new Array(); | |
// good | |
const items = []; |
使用 .push
方法代替直接索引赋值
const items = []; | |
// bad | |
items[items.length] = 'new item'; | |
// good | |
items.push('new item'); |
使用扩展运算符进行浅拷贝
const items = [1, 2, 3, 4, 5]; | |
// bad | |
const length = items.length; | |
const copied = []; | |
let index; | |
for (index = 0; index < length; index += 1) {copied[index] = items[index]; | |
} | |
// good | |
const copied = [...items]; |
使用 ...
运算符代替 Array.from
当需要将一个可迭代的对象转换成数组时,推荐使用 ...
操作符。
const elements = document.querySelectorAll('.foobar'); | |
// not bad | |
const nodes = Array.from(elements); | |
// good | |
const nodes = [...elements]; |
使用 ...
解构数组
const array = [1, 2, 3, 4, 5]; | |
// bad | |
const first = array[0]; | |
const second = array[1]; | |
// good | |
const [first, second, ...rest] = array; | |
console.log(rest); // => [3, 4, 5] |
使用 Array.from
将类数组对象转成数组
参考:Typed Arrays
const arrayLike = {0: 'foo', 1: 'bar', 2: 'baz', length: 3} | |
// bad | |
const array = Array.prototype.slice.call(arrayLike); | |
// good | |
const array = Array.from(arrayLike); |
使用 Array.from
对类数组对象进行遍历
Array.from(arrayLike[, mapFn[, thisArg]])
方法,参考 Array.from
const arrayLike = {0: 'foo', 1: 'bar', 2: 'baz', length: 3} | |
// bad | |
const array = [...arrayLike].map(mapFn); | |
// good | |
const array = Array.from(arrayLike, mapFn); |
在数组方法的回调函数中,永远返回正确的值
// bad - 当第一次迭代完成之后,acc 就变成了 undefined 了 | |
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {const flatten = acc.concat(item); | |
acc[index] = flatten; | |
}); | |
// good | |
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {const flatten = acc.concat(item); | |
acc[index] = flatten; | |
return flatten; | |
}); | |
// bad | |
messages.filter(msg => {const { subject, author} = msg; | |
if (subject === 'ParcMG') {return author === 'MG';} else {return false;} | |
}); | |
// good | |
messages.filter(msg => {const { subject, author} = msg; | |
if (subject === 'ParcMG') {return author === 'MG';} | |
return false; | |
}); | |
// bad | |
[1, 2, 3].map(x => { | |
const y = x + 1; | |
return x * y; | |
} | |
// good | |
[1, 2, 3].map(x => x * (x + 1)); |
一个数组有多行时,在 [
与 ]
处断行
// bad | |
const array = [[0, 1], [2, 3], [4, 5], [6, 7] | |
]; | |
const objectArray = [{id: 1,}, {id: 2,}]; | |
const numberArray = [1, 2,]; | |
// good | |
const array = [[0, 1], [2, 3], [4, 5], [6, 7]]; | |
const objectArray = [ | |
{id: 1,}, | |
{id: 2,} | |
]; | |
const numberArray = [1, 2]; | |
const numberArray = [ | |
1, | |
2, | |
]; |
字符串
对 string
永远使用单引号 ''
:
eslint
- quotes
// bad | |
const name = "Parc M.G"; | |
const name = `Parc M.G`; | |
// good | |
const name = 'Parc M.G'; |
超长的字符串,不应该使用多行串联
// bad | |
const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第 \ | |
一章的前二三个字作为该篇的篇名。《学而》一篇包括 16 章,内容涉及诸多方面。其中重 \ | |
点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、\ | |
信等道德范畴。'; | |
const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第' + | |
'一章的前二三个字作为该篇的篇名。《学而》一篇包括 16 章,内容涉及诸多方面。其中重' + | |
'点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、' + | |
'信等道德范畴。'; | |
// good | |
const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第 \ 一章的前二三个字作为该篇的篇名。《学而》一篇包括 16 章,内容涉及诸多方面。其中重点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、信等道德范畴。'; |
使用模板而非拼接来组织可编程字符串
eslint
- prefer-template
- template-curly-spacing
// bad | |
function hello(name) {return '你好,' + name + '!';} | |
function hello(name) {return ['你好,', name, '!'].join(''); | |
} | |
function hello(name) {return ` 你好,${ name}!`; | |
} | |
// good | |
function hello(name) {return ` 你好,${name}!`; | |
} |
永远不使用 eval()
eslint
- no-eval
若非必要,不使用转义字符
eslint
- no-useless-escape
// bad | |
const foo = '\'this\'\i\s \"quoted\"'; | |
// good | |
const foo = '\this\' is "quoted"'; | |
// best | |
const foo = `'this' is "quoted"`; |
函数
使用命名函数表达式,而不是函数声明
eslint
- func-styl
使用函数声明,它的作用域会被提前,这意味着很容易在一个文件里面引用一个还未被定义的函数,这样大大伤害了代码的可读性和可维护性,若一个函数很大很复杂,那么应该考虑将该函数单独提取到一个文件中,抽离成一个模块,同时不要忘记给表达式显示的命名,这消除了由匿名函数在错误调用栈中产生的所有假设。
// bad | |
function foo() {// ...} | |
// bad | |
const foo = function () {// ...} | |
// good | |
const foo = function foo() {// ...} | |
// best | |
const foo = function longUniqueMoreDescriptiveLexicalFoo() {// ...} |
把立即执行函数包裹在圆括号里
eslint
- wrap-iife
(function () {console.log('Welcome to the ParcMG world.'); | |
}()); |
不要在非函数块内声明函数
虽然运行环境允许你这样做,但是不同环境的解析方式不一样。
eslint
- no-loop-func
//bad | |
for (var i=10; i; i--) {(function() {return i;})();} | |
while(i) {var a = function() {return i;}; | |
a();} | |
do {function a() {return i;}; | |
a();} while (i); | |
let foo = 0; | |
for (let i = 0; i < 10; ++i) { | |
// Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop | |
setTimeout(() => console.log(foo)); | |
foo += 1; | |
} | |
for (let i = 0; i < 10; ++i) { | |
// Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop | |
setTimeout(() => console.log(foo)); | |
} | |
foo = 100; | |
// good | |
var a = function() {}; | |
for (var i=10; i; i--) {a(); | |
} | |
for (var i=10; i; i--) {var a = function() {}; // OK, no references to variables in the outer scopes. | |
a();} | |
for (let i=10; i; i--) {var a = function() {return i;}; // OK, all references are referring to block scoped variables in the loop. | |
a();} | |
var foo = 100; | |
for (let i=10; i; i--) {var a = function() {return foo;}; // OK, all references are referring to never modified variables. | |
a();} |
注意 :在
ECMA-262
中, 块(block
) 的定义是:一系列语句,但函数声明不是一个语句,命名函数表达式是一个语句。
// bad if (currentUser) {function test() {console.log('Nope.'); } } // good let test; if (currentUser) {test = () => {console.log('Yup.'); }; }
不允许使用 arguments
命名参数
arguments
的优先级高于高于每个函数作用域自带的 arguments
对象,这会导致函数自带的 arguments
值被覆盖。
// bad | |
function foo(name, options, arguments) {// ...} | |
// good | |
function foo(name, options, args) {// ...} |
不要在函数体内使用 arguments
,使用 ...rest
代替
eslint
- prefer-rest-params
...
明确出你想用那个参数,同时,rest
是一个真数组,而不是一个类数组的arguments
// bad | |
function concatenateAll() {const args = Array.prototype.slice.call(arguments); | |
return args.join(''); | |
} | |
// good | |
function concatenateAll(...args) {return args.join(''); | |
} |
使用默认参数,而不是在函数体内对参数重新赋值
// really bad | |
function handleThings(options) {options = options || {}; | |
} | |
// still bad | |
function handleTings(options) {if (options === void 0) {options = {}; | |
} | |
} | |
// good | |
function handleThings(options = {}) {} |
默认参数要避免副作用
// bad | |
let v = 1; | |
const count = function count(a = v++) {console.log(a); | |
} | |
count(); // => 1 | |
count(); // => 2 | |
count(3); // => 3 | |
count(); // => 3 | |
// maybe | |
const v = 1; | |
const count = function count(a = v) {console.log(a); | |
} |
把默认参数放在最后
// bad | |
function handleTings(options = {}, name) {// ...} | |
// good | |
function handleTings(name, options = {}) {// ...} |
不要使用函数构造器构造函数
eslint
- no-new-func
// bad | |
var add = new Function('a', 'b', 'return a + b'); | |
// still bad | |
var subtract = Function('a', 'b', 'return a - b'); | |
// good | |
const subtract = (a, b) => a + b; |
函数签名部分要有空格
eslint
- space-before-function-paren
- space-before-blocks
// bad | |
const f = function(){}; | |
const g = function (){}; | |
const h = function() {}; | |
// good | |
const f = function a() {}; |
不修改参数
eslint
- no-param-reassign
函数签名时定义的参数,在函数体内不允许被重新赋值(包含参数本身,若参数为对象,还包括该对象所有属性的值),
一个函数应该是没有任何副作用的。
// bad | |
function f1 (obj) {obj.key = 1;}; | |
function f2 (a) { | |
a = 1; | |
// ... | |
} | |
function f3 (a) {if (!a) {a = 1;} | |
// ... | |
} | |
// good | |
function f4(obj) {const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; | |
}; | |
function f5(a) { | |
const b = a || 1; | |
// ... | |
} | |
function f6(a = 1) {// ...} |
使用 spread
操作符 ...
调用多变参数函数
eslint
- prefer-spread
// bad | |
const x = [1, 2, 3, 4, 5]; | |
console.log.apply(console, x); | |
// good | |
const x = [1, 2, 3, 4, 5]; | |
console.log(...x); | |
// bad | |
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); | |
// good | |
new Date(...[2016, 8, 5]); |
若函数签名包含多个参数需要使用多行,那就每行有且仅有一个参数
// bad | |
function foo(bar, | |
baz, | |
quux) {// ...} | |
// good 缩进不要太过分 | |
function foo( | |
bar, | |
baz, | |
quux, | |
) {// ...} | |
// bad | |
console.log(foo, | |
bar, | |
baz); | |
// good | |
console.log( | |
foo, | |
bar, | |
baz, | |
); |
箭头函数
当你一定要用函数表达式的时候,就使用箭头函数
eslint
- prefer-array-callback
- arrow-spacing
// bad | |
[1, 2, 3].map(function (x) { | |
const y = x + 1; | |
return x * y; | |
}); | |
// good | |
[1, 2, 3].map((x) => { | |
const y = x + 1; | |
return x * y; | |
}); |
如果函数体有且仅有一个没有副作用的表达式,那么删除大括号和 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 | |
})); | |
// 表达式有副作用就不要用隐式 return | |
function foo(callback) {const val = callback(); | |
if (val === true) {// Do something if callback returns true} | |
} | |
let bool = false; | |
// bad | |
// 这种情况会 return bool = true, 不好 | |
foo(() => bool = true); | |
// good | |
foo(() => {bool = true;}); |
若表达式包含多行,用圆括号包裹起来
// bad | |
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( | |
httpMagicObjectWithAVeryLongName, | |
httpMethod | |
) | |
); | |
// good | |
['get', 'post', 'put'].map(httpMethod => ( | |
Object.prototype.hasOwnProperty.call( | |
httpMagicObjectWithAVeryLongName, | |
httpMethod | |
) | |
)); |
若函数只有一个参数,且没有大括号,那就删除圆括号,否则,参数总是放在圆括号里。
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 don’t want it to take up space on the .map line!` | |
)); | |
// 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; | |
}); |
避免箭头函数 (=>) 和比较操作符(<=, >=)混淆.
eslint
- no-confusing-arrow
// bad | |
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize; | |
// bad | |
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize; | |
// good | |
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize); | |
// good | |
const itemHeight = (item) => {const { height, largeSize, smallSize} = item; | |
return height > 256 ? largeSize : smallSize; | |
}; |
在隐式 return 中强制约束函数体的位置,就写在箭头后面
eslint
- implicit-arrow-linebreak
// bad | |
(foo) => | |
bar; | |
(foo) => | |
(bar); | |
// good | |
(foo) => bar; | |
(foo) => (bar); | |
(foo) => (bar) |
类与构造器
使用构造器,而不是 prototype
// bad | |
function Queue(contents = []) {this.queue = [...contents]; | |
} | |
Queue.prototype.pop = function () {const value = this.queue[0]; | |
this.queue.splice(0, 1); | |
return value; | |
}; | |
// good | |
class Queue {constructor(contents = []) {this.queue = [...contents]; | |
} | |
pop() {const value = this.queue[0]; | |
this.queue.splice(0, 1); | |
return value; | |
} | |
} |
使用 extends
实现继承
它是一种内置的方法来继承原型功能而不打破 instanceof
。
// bad | |
const inherits = require('inherits'); | |
function PeekableQueue(contents) {Queue.apply(this, contents); | |
} | |
inherits(PeekableQueue, Queue); | |
PeekableQueue.prototype.peek = function () {return this._queue[0]; | |
} | |
// good | |
class PeekableQueue extends Queue {peek() {return this._queue[0]; | |
} | |
} |
方法可以返回 this
实现方法链
// bad | |
Jedi.prototype.jump = function () { | |
this.jumping = true; | |
return true; | |
}; | |
Jedi.prototype.setHeight = function (height) {this.height = height;}; | |
const luke = new Jedi(); | |
luke.jump(); // => true | |
luke.setHeight(20); // => undefined | |
// good | |
class Jedi {jump() { | |
this.jumping = true; | |
return this; | |
} | |
setHeight(height) { | |
this.height = height; | |
return this; | |
} | |
} | |
const luke = new Jedi(); | |
luke.jump() | |
.setHeight(20); |
只要保证可以正常工作且没有副作用,可以自已定制 toString
方法
class Jedi {constructor(options = {}) {this.name = options.name || 'no name';} | |
getName() {return this.name;} | |
toString() {return `Jedi - ${this.getName()}`; | |
} | |
} |
不要写无用的构造函数
eslint
- no-useless-constructor
// bad | |
class Jedi {constructor() {} | |
getName() {return this.name;} | |
} | |
// bad | |
class Rey extends Jedi { | |
// 这种构造函数是不需要写的 | |
constructor(...args) {super(...args); | |
} | |
} | |
// good | |
class Rey extends Jedi {constructor(...args) {super(...args); | |
this.name = 'Rey'; | |
} | |
} |
避免重复类成员
eslint
- no-dupe-class-members
// bad | |
class Foo {bar() {return 1;} | |
bar() { return 2;} | |
} | |
// good | |
class Foo {bar() {return 1;} | |
} | |
// good | |
class Foo {bar() {return 2;} | |
} |
模块
使用 import
/ export
// bad | |
const Button = require('./Button'); | |
module.exports = Button.es6; | |
// ok | |
import Button from './Button'; | |
export default Button.es6; | |
// best | |
import {es6} from './Button'; | |
export default es6; |
不要 import
通配符
// bad | |
import * as Component from './Component'; | |
// good | |
import Component from './Component'; |
不要直接从 import
中 export
虽然一行是简洁的,有一个明确的方式进口和一个明确的出口方式来保证一致性。
// bad | |
export {es6 as default} from './Component'; | |
// good | |
import {es6} from './Component'; | |
export default es6; |
一个路径只 import
一次
eslint
- no-duplicate-imports
从同一个路径下 import 多行会使代码难以维护
// bad | |
import foo from 'foo'; | |
// … some other imports … // | |
import {named1, named2} from 'foo'; | |
// good | |
import foo, {named1, named2} from 'foo'; | |
// good | |
import foo, { | |
named1, | |
named2, | |
} from 'foo'; |
若非必要,不要 export
可变量
eslint
- import/no-mutable-exports
变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
// bad | |
let foo = 3; | |
export {foo} | |
// good | |
const foo = 3; | |
export {foo} |
在一个单一导出模块里,使用 export default
eslint
- import/prefer-default-export
鼓励使用更多文件,每个文件只做一件事情并导出,这样可读性和可维护性更好。
// bad | |
export function foo() {} | |
// good | |
export default function foo() {} |
import
应该放在所有其它语句之前
eslint
- import/first
// bad | |
import foo from 'foo'; | |
foo.init(); | |
import bar from 'bar'; | |
// good | |
import foo from 'foo'; | |
import bar from 'bar'; | |
foo.init(); |
多行 import 应该缩进,就像多行数组和对象字面量
花括号与样式指南中每个其他花括号块遵循相同的缩进规则,逗号也是。
// bad | |
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; | |
// good | |
import { | |
longNameA, | |
longNameB, | |
longNameC, | |
longNameD, | |
longNameE, | |
} from 'path'; |
若使用 webpack
,不允许在 import
中使用 webpack loader
语法
eslint
- import/no-webpack-loader-syntax
一旦用 Webpack 语法在
import
里会把代码耦合到模块绑定器。最好是在webpack.config.js
里写webpack loader
语法
// bad | |
import fooSass from 'css!sass!foo.scss'; | |
import barCss from 'style!css!bar.css'; | |
// good | |
import fooSass from 'foo.scss'; | |
import barCss from 'bar.css'; |
迭代器与生成器
不要使用遍历器
eslint
- no-iterator
- no-restricted-syntax
用 JavaScript 高级函数代替
for-in
、for-of
- 这强调了我们不可变的规则。处理返回值的纯函数比副作用更容易。
- 用数组的这些迭代方法:
map()
、every()
、filter()
、find()
、findIndex()
、reduce()
、some()
……- 用对象的这些方法
Object.keys()
、Object.values()
、Object.entries
去产生一个数组,这样你就能去遍历对象了。
const numbers = [1, 2, 3, 4, 5]; | |
// bad | |
let sum = 0; | |
for (let num of numbers) {sum += num;} | |
sum === 15; | |
// good | |
let sum = 0; | |
numbers.forEach(num => sum += num); | |
sum === 15; | |
// best (use the functional force) | |
const sum = numbers.reduce((total, num) => total + num, 0); | |
sum === 15; | |
// bad | |
const increasedByOne = []; | |
for (let i = 0; i < numbers.length; i++) {increasedByOne.push(numbers[i] + 1); | |
} | |
// good | |
const increasedByOne = []; | |
numbers.forEach(num => increasedByOne.push(num + 1)); | |
// best (keeping it functional) | |
const increasedByOne = numbers.map(num => num + 1); |
不要用 generator
eslint
- generator-star-spacing
它在 es5 上支持的不好
如果一定要用,那么一定需要注意一点:function
与 *
是同一概念关键字,*
并不是 function
的修饰符,function*
是一个与 function
完全不一样的独特结构。
// bad | |
function * foo() {// ...} | |
// bad | |
const bar = function * () {// ...} | |
// bad | |
const baz = function *() {// ...} | |
// bad | |
const quux = function*() {// ...} | |
// bad | |
function*foo() {// ...} | |
// bad | |
function *foo() {// ...} | |
// very bad | |
function | |
* | |
foo() {// ...} | |
// very bad | |
const wat = function | |
* | |
() {// ...} | |
// good | |
function* foo() {// ...} | |
// good | |
const foo = function* () {// ...} |
属性
访问属性使用 .
号
eslint
- dot-notation
这条,涉及一个曾经阿里出过一个看似简单,实则很难的面试题,你就算猜对一个,你也不一定能说出原理:
a.b.c.d 和 a ’b'[‘d’],哪个性能更高
到这里,突然想起这个梗,有兴趣的可以翻看一下 这里。
const luke = { | |
jedi: true, | |
age: 28, | |
}; | |
// bad | |
const isJedi = luke['jedi']; | |
// good | |
const isJedi = luke.jedi; |
当获取属性名称本身是一个变量是,使用 []
访问
const luke = { | |
jedi: true, | |
age: 28, | |
}; | |
function getProp(prop) {return luke[prop]; | |
} | |
const isJedi = getProp('jedi'); |
幂等使用 **
操作符
eslint
- no-restricted-properties
// bad | |
const binary = Math.pow(2, 10); | |
// good | |
const binary = 2 ** 10; |
变量
永远使用 const
或者 let
,不使用 var
eslint
- no-undef
- prefer-const
// bad | |
superPower = new SuperPower(); | |
// good | |
const superPower = new SuperPower(); |
每一个变量都用一个 const
或者 let
eslint
- one-var
扯蛋的理由:这种方式很容易去声明新的变量,你不用去考虑把; 调换成,,或者引入一个只有标点的不同的变化。
真正的理由:做法也可以是你在调试的时候单步每个声明语句,而不是一下跳过所有声明。
// bad | |
const items = getItems(), | |
goSportsTeam = true, | |
dragonball = 'z'; | |
const items = getItems(), | |
goSportsTeam = true; | |
dragonball = 'z'; | |
// good | |
const items = getItems(); | |
const goSportsTeam = true; | |
const dragonball = 'z'; |
尘归尘,土归土
在同一个块中,所有的 const
放在一起,所有的 let
放在一起
// bad | |
let i, len, dragonball, | |
items = getItems(), | |
goSportsTeam = true; | |
// bad | |
let i; | |
const items = getItems(); | |
let dragonball; | |
const goSportsTeam = true; | |
let len; | |
// good | |
const goSportsTeam = true; | |
const items = getItems(); | |
let dragonball; | |
let i; | |
let length; |
在你需要的地方声明变量,但要放在合理的位置
// bad | |
function checkName(hasName) {const name = getName(); | |
if (hasName === 'test') {return false;} | |
if (name === 'test') {this.setName(''); | |
return false; | |
} | |
return name; | |
} | |
// good | |
function checkName(hasName) {if (hasName === 'test') {return false;} | |
// 在需要的时候分配 | |
const name = getName(); | |
if (name === 'test') {this.setName(''); | |
return false; | |
} | |
return name; | |
} |
不使用链接变量分配
eslint
- no-multi-assign
链接变量分配会隐匿创建全局变量
// bad | |
(function example() { | |
// JavaScript 将这一段解释为 | |
// let a = (b = ( c = 1) ); | |
// let 只对变量 a 起作用; 变量 b 和 c 都变成了全局变量 | |
let a = b = c = 1; | |
}()); | |
console.log(a); // undefined | |
console.log(b); // 1 | |
console.log(c); // 1 | |
// good | |
(function example() { | |
let a = 1; | |
let b = a; | |
let c = a; | |
}()); | |
console.log(a); // undefined | |
console.log(b); // undefined | |
console.log(c); // undefined | |
// `const` 也是如此 |
不使用一元自增自减运算(++
、--
)
eslint
- no-plusplus
根据 eslint
文档,一元增量和减量语句受到自动分号插入的影响,并且可能会导致应用程序中的值递增或递减的无声错误。使用 num + = 1
而不是 num ++
或 num ++
语句来表达你的值也是更有表现力的。禁止一元增量和减量语句还会阻止您无意地预增 / 预减值,这也会导致程序出现意外行为。
// bad | |
let array = [1, 2, 3]; | |
let num = 1; | |
num++; | |
--num; | |
let sum = 0; | |
let truthyCount = 0; | |
for(let i = 0; i < array.length; i++){let value = array[i]; | |
sum += value; | |
if (value) {truthyCount++;} | |
} | |
// good | |
let array = [1, 2, 3]; | |
let num = 1; | |
num += 1; | |
num -= 1; | |
const sum = array.reduce((a, b) => a + b, 0); | |
const truthyCount = array.filter(Boolean).length; |
赋值时不换行
eslint
- operator-linebreak
如果赋值语句超出了 max-len 配置,那么给值前面加上括号。
// bad | |
const foo = | |
superLongLongLongLongLongLongLongLongFunctionName(); | |
// bad | |
const foo | |
= 'superLongLongLongLongLongLongLongLongString'; | |
// good | |
const foo = (superLongLongLongLongLongLongLongLongFunctionName() | |
); | |
// good | |
const foo = 'superLongLongLongLongLongLongLongLongString'; |
不允许声明不使用的变量
eslint
- no-unused-vars
// bad | |
var some_unused_var = 42; | |
// 写了没用 | |
var y = 10; | |
y = 5; | |
// 变量改了自己的值,也没有用这个变量 | |
var z = 0; | |
z = z + 1; | |
// 参数定义了但未使用 | |
function getX(x, y) {return x;} | |
// good | |
function getXPlusY(x, y) {return x + y;} | |
var x = 1; | |
var y = a + 2; | |
alert(getXPlusY(x, y)); | |
// 'type' 即使没有使用也可以可以被忽略,因为这个有一个 rest 取值的属性。// 这是从对象中抽取一个忽略特殊字段的对象的一种形式 | |
var {type, ...coords} = data; | |
// 'coords' 现在就是一个没有 'type' 属性的 'data' 对象 |
变量提升
永远不要使用 var
var
声明会将变量声明提升到作用域的最前面,但是他的值却只有在运行到代码行时才会被赋值,永远都使用 const
与 let
,了解 时效区(Temporal Dead Zones)的相关知识,也还要知道为什么 typeof 不再安全。
// 我们知道这个不会工作,假设没有定义全局的 notDefined | |
function example() {console.log(notDefined); // => throws a ReferenceError | |
} | |
// 在你引用的地方之后声明一个变量,他会正常输出是因为变量作用域上升。// 注意:declaredButNotAssigned 的值没有上升 | |
function example() {console.log(declaredButNotAssigned); // => undefined | |
var declaredButNotAssigned = true; | |
} | |
// 解释器把变量声明提升到作用域最前面,// 可以重写成如下例子,二者意义相同 | |
function example() { | |
let declaredButNotAssigned; | |
console.log(declaredButNotAssigned); // => undefined | |
declaredButNotAssigned = true; | |
} | |
// 用 const,let 就不一样了 | |
function example() {console.log(declaredButNotAssigned); // => throws a ReferenceError | |
console.log(typeof declaredButNotAssigned); // => throws a ReferenceError | |
const declaredButNotAssigned = true; | |
} |
匿名函数表达式与 var
的情况一样
function example() {console.log(anonymous); // => undefined | |
anonymous(); // => TypeError anonymous is not a function | |
var anonymous = function () {console.log('anonymous function expression'); | |
}; | |
} |
已命名函数表达式只提升变量名,而不是函数名或者函数体
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'); | |
}; | |
} |
函数声明则提升了函数名和函数体
function example() {superPower(); // => Flying | |
function superPower() {console.log('Flying'); | |
} | |
} |
比较操作符
永远使用 ===
与 !==
,而不是 ==
与 !=
eslint
- eqeqeq
if
条件语句的强制 toBoolean
if
条件语句的强制 toBoolean
总是遵循以下规则:
-
Objects
总是计算成true
-
Undefined
总是计算 成false
-
Null
总是计算成false
-
Booleans
计算成它本身的布尔值 -
Numbers
-
+0
、-0
或者NaN
总是计算成false
- 其它的全部为
true
-
-
Strings
- ” 计算成
false
- 其它全部为
true
- ” 计算成
注意:
NaN
是不等于NaN
的,请使用isNaN()
检测。
if ([0] && []) { | |
// true | |
// 数组(即使是空数组)是对象,对象会计算成 true | |
} | |
console.log(NaN === NaN) // => false | |
console.log(isNaN(NaN)) // => true |
布尔值要使用缩写,但是字符串与数字要明确比较对象
// bad | |
if (isValid === true) {// ...} | |
// good | |
if (isValid) {// ...} | |
// bad | |
if (name) {// ...} | |
// good | |
if (name !== '') {// ...} | |
// bad | |
if (collection.length) {// ...} | |
// good | |
if (collection.length > 0) {// ...} |
在 switch
的 case
与 default
分句中使用大括号创建语法声明区域
eslint
- no-case-declarations
语法声明在整个 switch
的代码块里都可见,但是只有当其被分配后才会初始化,他的初始化时当这个 case
被执行时才产生。当多个 case
分句试图定义同一个事情时就出问题了
// bad | |
switch (foo) { | |
case 1: | |
let x = 1; | |
break; | |
case 2: | |
const y = 2; | |
break; | |
case 3: | |
function f() {// ...} | |
break; | |
default: | |
class C {}} | |
// good | |
switch (foo) { | |
case 1: { | |
let x = 1; | |
break; | |
} | |
case 2: { | |
const y = 2; | |
break; | |
} | |
case 3: {function f() {// ...} | |
break; | |
} | |
case 4: | |
bar(); | |
break; | |
default: {class C {} | |
} | |
} |
三元运算符不能被嵌套
eslint
- no-nested-ternary
// bad | |
const foo = maybe1 > maybe2 | |
? "bar" | |
: value1 > value2 ? "baz" : null; | |
// better | |
const maybeNull = value1 > value2 ? 'baz' : null; | |
const foo = maybe1 > maybe2 | |
? 'bar' | |
: maybeNull; | |
// best | |
const maybeNull = value1 > value2 ? 'baz' : null; | |
const foo = maybe1 > maybe2 ? 'bar' : maybeNull; |
避免不必要的三元表达式
// bad | |
const foo = a ? a : b; | |
const bar = c ? true : false; | |
const baz = c ? false : true; | |
// good | |
const foo = a || b; | |
const bar = !!c; | |
const baz = !c; |
除非优先级显而易见,否则使用圆括号来混合操作符
eslint
- no-mixed-operators
开发者需要以最显而易见的方式明确自己的意图与逻辑
// bad | |
const foo = a && b < 0 || c > 0 || d + 1 === 0; | |
// bad | |
const bar = a ** b - 5 % d; | |
// bad | |
// 别人会陷入(a || b) && c 的迷惑中 | |
if (a || b && c) {return d;} | |
// good | |
const foo = (a && b < 0) || c > 0 || (d + 1 === 0); | |
// good | |
const bar = (a ** b) - (5 % d); | |
// good | |
if (a || (b && c)) {return d;} | |
// good | |
const bar = a + b / c * d; |
区块
用大括号包裹多行代码
eslint
- nonblock-statement-body-position
// bad | |
if (test) | |
return false; | |
// good | |
if (test) return false; | |
// good | |
if (test) {return false;} | |
// bad | |
function foo() { return false;} | |
// good | |
function bar() {return false;} |
if
以及 else
与 if
的关闭大括号在同一行
eslint
- brace-style
// bad | |
if (test) {thing1(); | |
thing2();} | |
else {thing3(); | |
} | |
// good | |
if (test) {thing1(); | |
thing2();} else {thing3(); | |
} |
if
语句中的 return
eslint
- no-else-return
- 如果
if
语句中总是需要用return
返回,那么后续的else
就不需要写了 - 如果
if
块中包含return
,它后面的else if
也包含了return
,那就应该把else if
的return
分到多个if
语句块中去。
// bad | |
function foo() {if (x) {return x;} else {return y;} | |
} | |
// bad | |
function cats() {if (x) {return x;} else if (y) {return y;} | |
} | |
// bad | |
function dogs() {if (x) {return x;} else {if (y) {return y;} | |
} | |
} | |
// good | |
function foo() {if (x) {return x;} | |
return y; | |
} | |
// good | |
function cats() {if (x) {return x;} | |
if (y) {return y;} | |
} | |
// good | |
function dogs(x) {if (x) {if (z) {return y;} | |
} else {return z;} | |
} |
控制语句
当你的控制语句 (if
、while
)等太长,或者超过最大长度限制时,把每一个判断条件都放到单独一行去,逻辑操作符放在行首
// bad | |
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {thing1(); | |
} | |
// bad | |
if (foo === 123 && | |
bar === 'abc') {thing1(); | |
} | |
// bad | |
if (foo === 123 | |
&& bar === 'abc') {thing1(); | |
} | |
// bad | |
if ( | |
foo === 123 && | |
bar === 'abc' | |
) {thing1(); | |
} | |
// good | |
if ( | |
foo === 123 | |
&& bar === 'abc' | |
) {thing1(); | |
} | |
// good | |
if ((foo === 123 || bar === 'abc') | |
&& doesItLookGoodWhenItBecomesThatLong() | |
&& isThisReallyHappening()) {thing1(); | |
} | |
// good | |
if (foo === 123 && bar === 'abc') {thing1(); | |
} |
不要用选择操作符代替控制语句
// bad | |
!isRunning && startRunning(); | |
// good | |
if (!isRunning) {startRunning(); | |
} |
注释
多行注释使用 /** ... */
// bad | |
// make() 基于传入的 `tag` 名返回一个新元素 | |
// | |
// @param {String} 标签名 | |
// @return {Element} 新元素 | |
function make(tag) { | |
// ... | |
return element; | |
} | |
// good | |
/** | |
* make() 基于传入的 `tag` 名返回一个新元素 | |
* @param {String} 标签名 | |
* @param {Element} 新元素 | |
*/ | |
function make(tag) { | |
// ... | |
return element; | |
} |
单行注释用 //
将单行注释放在被注释区域上面。如果注释不是在第一行,那么注释前面就空一行
// bad | |
const active = true; // is current tab | |
// good | |
// 当前激活状态的 tab | |
const active = true; | |
// bad | |
function getType() {console.log('fetching type...'); | |
// 设置默认 `type` 为 'no type' | |
const type = this._type || 'no type'; | |
return type; | |
} | |
// good | |
function getType() {console.log('fetching type...'); | |
// 设置默认 `type` 为 'no type' | |
const type = this._type || 'no type'; | |
return type; | |
} | |
// also good | |
function getType() { | |
// 设置默认 `type` 为 'no type' | |
const type = this._type || 'no type'; | |
return type; | |
} |
所有注释开头空一个,方便阅读
eslint
- space-comment
// bad | |
// 当前激活的 tab | |
const active = true; | |
// good | |
// 当前激活的 tab | |
const active = true; | |
// bad | |
/** | |
*make() 基于传入的 `tag` 名返回一个新元素 | |
*@param {String} 标签名 | |
*@param {Element} 新元素 | |
*/ | |
function make(tag) { | |
// ... | |
return element; | |
} | |
// good | |
/** | |
* make() 基于传入的 `tag` 名返回一个新元素 | |
* @param {String} 标签名 | |
* @param {Element} 新元素 | |
*/ | |
function make(tag) { | |
// ... | |
return element; | |
} |
积极使用 FIXME
与 TODO
当你的注释需要向注释阅读者或者代码的后继开发者明确的表述一种期望时,应该积极使用 FIXME
以及 TODO
前缀,这有助于其他的开发理解你指出的需要重新访问的问题,也方便自己日后有时间的时候再次回顾当时没有解决或者计划去做而没有做的事情。
-
FIXME
:这里有一个问题,现在还没有影响大局,但是更希望解决这个问题或者找到一个更优雅的方式去实现 -
TODO
:计划在这里去实现某些功能,现在还没有实现
// 使用 FIXME: | |
class Calculator extends Abacus {constructor() {super(); | |
// FIXME: 不应该在此处使用全局变量 | |
total = 0; | |
} | |
} | |
// 使用 TODO: | |
class Calculator extends Abacus {constructor() {super(); | |
// TODO: total 应该应该从一个参数中获取并初始化 | |
this.total = 0; | |
} | |
} |
空格
代码缩进总是使用两个空格
eslint
- indent
// bad | |
function foo() {∙∙∙∙const name;} | |
// bad | |
function bar() {∙const name;} | |
// good | |
function baz() {∙∙const name;} |
在大括号前空一格
eslint
- space-before-blocks
// bad | |
function test(){console.log('test'); | |
} | |
// good | |
function test() {console.log('test'); | |
} | |
// bad | |
dog.set('attr',{ | |
age: '1 year', | |
breed: 'Bernese Mountain Dog', | |
}); | |
// good | |
dog.set('attr', { | |
age: '1 year', | |
breed: 'Bernese Mountain Dog', | |
}); |
关键字空格
eslint
- keyword-spacing
在控制语句 (if
, while
等) 的圆括号前空一格。在函数调用和定义时,参数列表和函数名之间不空格。
// bad | |
if(isJedi) {fight (); | |
} | |
// good | |
if (isJedi) {fight(); | |
} | |
// bad | |
function fight () {console.log ('Swooosh!'); | |
} | |
// good | |
function fight() {console.log('Swooosh!'); | |
} |
用空格来隔开运算符
eslint
- space-infix-ops
// bad | |
const x=y+5; | |
// good | |
const x = y + 5; |
文件结尾加一个换行
eslint
- eol-last
// bad | |
function doSmth() {var foo = 2;} |
// bad | |
function doSmth() {var foo = 2;}\n |
使用多行缩进的方式进行一个长方法链调用
eslint
- newline-per-chained-call
- no-whitespace-before-property
// bad | |
$('#items').find('.selected').highlight().end().find('.open').updateCount(); | |
// bad | |
$('#items'). | |
find('.selected'). | |
highlight(). | |
end(). | |
find('.open'). | |
updateCount(); | |
// good | |
$('#items') | |
.find('.selected') | |
.highlight() | |
.end() | |
.find('.open') | |
.updateCount(); | |
// bad | |
const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) | |
.attr('width', (radius + margin) * 2).append('svg:g') | |
.attr('transform', `translate(${radius + margin},${radius + margin})`) | |
.call(tron.led); | |
// good | |
const leds = stage.selectAll('.led') | |
.data(data) | |
.enter().append('svg:svg') | |
.classed('led', true) | |
.attr('width', (radius + margin) * 2) | |
.append('svg:g') | |
.attr('transform', `translate(${radius + margin},${radius + margin})`) | |
.call(tron.led); | |
// good | |
const leds = stage.selectAll('.led').data(data); |
在一个代码块后下一条语句前空一行
// bad | |
if (foo) {return bar;} | |
return baz; | |
// good | |
if (foo) {return bar;} | |
return baz; | |
// bad | |
const obj = {foo() { }, | |
bar() {}, | |
}; | |
return obj; | |
// good | |
const obj = {foo() { }, | |
bar() {}, | |
}; | |
return obj; | |
// bad | |
const arr = [function foo() { }, | |
function bar() {}, | |
]; | |
return arr; | |
// good | |
const arr = [function foo() { }, | |
function bar() {}, | |
]; | |
return arr; |
不要用空白行填充块
eslint
- padded-blocks
// bad | |
function bar() {console.log(foo); | |
} | |
// also bad | |
if (baz) {console.log(qux); | |
} else {console.log(foo); | |
} | |
// good | |
function bar() {console.log(foo); | |
} | |
// good | |
if (baz) {console.log(qux); | |
} else {console.log(foo); | |
} |
圆括号里不加空格
eslint
- space-in-parens
// bad | |
function bar(foo) {return foo;} | |
// good | |
function bar(foo) {return foo;} | |
// bad | |
if (foo) {console.log(foo); | |
} | |
// good | |
if (foo) {console.log(foo); | |
} |
方括号里,首尾都不要加空格与元素分隔
eslint
- array-bracket-spacing
// bad | |
const foo = [1, 2, 3]; | |
console.log(foo[ 0]); | |
// good,逗号分隔符还是要空格的 | |
const foo = [1, 2, 3]; | |
console.log(foo[0]); |
花括号里要加空格
eslint
- object-curly-spacing
// bad | |
const foo = {clark: 'kent'}; | |
// good | |
const foo = {clark: 'kent'}; |
避免一行代码超过 100 个字符(包含空格)
eslint
- max-len
为了确保代码的人类可读性与可维护性,代码行应避免超过一定的长度
// bad | |
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; | |
// bad | |
$.ajax({method: 'POST', url: 'https://parcmg.com/', data: { name: 'John'} }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); | |
// good | |
const foo = jsonData | |
&& jsonData.foo | |
&& jsonData.foo.bar | |
&& jsonData.foo.bar.baz | |
&& jsonData.foo.bar.baz.quux | |
&& jsonData.foo.bar.baz.quux.xyzzy; | |
// good | |
$.ajax({ | |
method: 'POST', | |
url: 'https://apis.parcmg.com/', | |
data: {name: 'John'}, | |
}) | |
.done(() => console.log('Congratulations!')) | |
.fail(() => console.log('You have failed this city.')); |
作为语句的花括号里不应该加空格
eslint
- block-spacing
// bad | |
function foo() {return true;} | |
if (foo) {bar = 0;} | |
// good | |
function foo() { return true;} | |
if (foo) {bar = 0;} |
,
前不要空格,,
后需要空格
eslint
- comma-spacing
// bad | |
var foo = 1,bar = 2; | |
var arr = [1 , 2]; | |
// good | |
var foo = 1, bar = 2; | |
var arr = [1, 2]; |
计算属性内要空格
eslint
- computed-property-spacing
// bad | |
obj[foo] | |
obj['foo'] | |
var x = {[b]: a} | |
obj[foo[ bar]] | |
// good | |
obj[foo] | |
obj['foo'] | |
var x = {[b]: a } | |
obj[foo[bar]] |
调用函数时,函数名和小括号之间不要空格
eslint
- func-call-spacing
// bad | |
func (); | |
func | |
(); | |
// good | |
func(); |
在对象的字面量属性中,key
与 value
之间要有空格
eslint
- key-spacing
// bad | |
var obj = {"foo" : 42}; | |
var obj2 = {"foo":42}; | |
// good | |
var obj = {"foo": 42}; |
行末不要空格
eslint
- no-trailing-spaces
避免出现连续多个空行,文件末尾只允许空一行
eslint
- no-multiple-empty-lines
// bad | |
var x = 1; | |
var y = 2; | |
// good | |
var x = 1; | |
var y = 2; |
逗号
不要前置逗号
eslint
- comma-style
// bad | |
const story = [ | |
once | |
, upon | |
, aTime | |
]; | |
// good | |
const story = [ | |
once, | |
upon, | |
aTime, | |
]; | |
// bad | |
const hero = { | |
firstName: 'Ada' | |
, lastName: 'Lovelace' | |
, birthYear: 1815 | |
, superPower: 'computers' | |
}; | |
// good | |
const hero = { | |
firstName: 'Ada', | |
lastName: 'Lovelace', | |
birthYear: 1815, | |
superPower: 'computers', | |
}; |
额外结尾逗号
eslint
- comma-dangle
就算项目有可能运行在旧版本的浏览器中,但是像
Babel
这样的转换器都会在转换代码的过程中删除这些多余逗号,所以,大胆使用它,完全不会有副作用产生,相反的,他能让我们更方便的给对象或者多行数组增加、删除属性或者元素,同时,还能让我们的git diffs
更清洁。
// bad - 没有结尾逗号的 git diff | |
const hero = { | |
firstName: 'Florence', | |
- lastName: 'Nightingale' | |
+ lastName: 'Nightingale', | |
+ inventorOf: ['coxcomb chart', 'modern nursing'] | |
}; | |
// good - 有结尾逗号的 git diff | |
const hero = { | |
firstName: 'Florence', | |
lastName: 'Nightingale', | |
+ inventorOf: ['coxcomb chart', 'modern nursing'], | |
}; |
// bad | |
const hero = { | |
firstName: 'Dana', | |
lastName: 'Scully' | |
}; | |
const heroes = [ | |
'Batman', | |
'Superman' | |
]; | |
// good | |
const hero = { | |
firstName: 'Dana', | |
lastName: 'Scully', | |
}; | |
const heroes = [ | |
'Batman', | |
'Superman', | |
]; | |
// bad | |
function createHero( | |
firstName, | |
lastName, | |
inventorOf | |
) {// does nothing} | |
// good | |
function createHero( | |
firstName, | |
lastName, | |
inventorOf, | |
) {// does nothing} | |
// good (在一个 "rest" 元素后面,绝对不能出现逗号) | |
function createHero( | |
firstName, | |
lastName, | |
inventorOf, | |
...heroArgs | |
) {// does nothing} | |
// bad | |
createHero( | |
firstName, | |
lastName, | |
inventorOf | |
); | |
// good | |
createHero( | |
firstName, | |
lastName, | |
inventorOf, | |
); | |
// good (在一个 "rest" 元素后面,绝对不能出现逗号) | |
createHero( | |
firstName, | |
lastName, | |
inventorOf, | |
...heroArgs | |
) |
分号
永远明确的使用分号结束你的代码行
eslint
- semi
当 JavaScript 遇到没有分号结尾的一行,它会执行 自动插入分号 Automatic Semicolon Insertion 这一规则来决定行末是否加分号。如果 JavaScript 在你的断行里错误的插入了分号,就会出现一些古怪的行为。当新的功能加到 JavaScript 里后,这些规则会变得更复杂难懂。显示的结束语句,并通过配置代码检查去捕获没有带分号的地方可以帮助你防止这种错误。
// bad | |
(function () { | |
const name = 'Skywalker' | |
return name | |
})() | |
// good | |
(function () { | |
const name = 'Skywalker'; | |
return name; | |
}()); | |
// good, 行首加分号,避免文件被连接到一起时立即执行函数被当做变量来执行。;(() => { | |
const name = 'Skywalker'; | |
return name; | |
}()); |
强类型转换
在语句开始执行强制类型转换
使用 String
进行字符类型转换
eslint
- no-new-wrappers
// => this.reviewScore = 9; | |
// bad | |
const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string" | |
// bad | |
const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf() | |
// bad | |
const totalScore = this.reviewScore.toString(); // 不保证返回 string | |
// good | |
const totalScore = String(this.reviewScore); |
使用 Number
进行数字类型转换
eslint
- radix
使用 parseInt
转换 string
通常都需要带上基数。
const inputValue = '4'; | |
// bad | |
const val = new Number(inputValue); | |
// bad | |
const val = +inputValue; | |
// bad | |
const val = inputValue >> 0; | |
// bad | |
const val = parseInt(inputValue); | |
// good | |
const val = Number(inputValue); | |
// good | |
const val = parseInt(inputValue, 10); |
在注释中说明为什么要使用移位运算
如果你感觉 parseInt
满足不要你的需求,想使用移位进行运算,那么你一定要写明白,这是因为 性能问题,同时,你还需要注意,数字使用 64 位 表示的,但移位运算常常返回的是 32 位的整形,移位运算对于大于 32 位的整数会导致一些 意外行为,最大的 32 位整数是 2,147,483,647
。
// good | |
/** | |
* parseInt 是代码运行慢的原因 | |
* 用 Bitshifting 将字符串转成数字使代码运行效率大幅增长 | |
*/ | |
const val = inputValue >> 0; | |
2147483647 >> 0 //=> 2147483647 | |
2147483648 >> 0 //=> -2147483648 | |
2147483649 >> 0 //=> -2147483647 |
布尔
const age = 0; | |
// bad | |
const hasAge = new Boolean(age); | |
// good | |
const hasAge = Boolean(age); | |
// best | |
const hasAge = !!age; |
命名规则
避免使用一个字母命名
eslint
- id-length
// bad | |
function q() {// ...} | |
// good | |
function query() {// ...} |
使用小驼峰式命名对象、函数、实例
eslint
- camelcase
// bad | |
const OBJEcttsssss = {}; | |
const this_is_my_object = {}; | |
function c() {} | |
// good | |
const thisIsMyObject = {}; | |
function thisIsMyFunction() {} |
使用大驼峰式命名类
eslint
- new-cap
// bad | |
function user(options) {this.name = options.name;} | |
const bad = new user({name: 'nope',}); | |
// good | |
class User {constructor(options) {this.name = options.name;} | |
} | |
const good = new User({name: 'yup',}); |
不要使用前置或后置下划线(除非引入的第三方库本身使用)
eslint
- no-underscore-dangle
JavaScript 没有私有属性或私有方法的概念。尽管前置下划线通常的概念上意味着“private”,事实上,这些属性是完全公有的,因此这部分也是你的 API 的内容。这一概念可能会导致开发者误以为更改这个不会导致崩溃或者不需要测试。如果你想要什么东西变成“private”,那就不要让它在这里出现。
// bad | |
this.__firstName__ = 'Panda'; | |
this.firstName_ = 'Panda'; | |
this._firstName = 'Panda'; | |
// good | |
this.firstName = 'Panda'; |
不要保存引用 this
用箭头函数或函数绑定——Function#bind
// bad | |
function foo() { | |
const self = this; | |
return function () {console.log(self); | |
}; | |
} | |
// bad | |
function foo() { | |
const that = this; | |
return function () {console.log(that); | |
}; | |
} | |
// good | |
function foo() {return () => {console.log(this); | |
}; | |
} |
保证文件名、export
模块名以及 import
模块名一致
// file 1 contents | |
class CheckBox {// ...} | |
export default CheckBox; | |
// file 2 contents | |
export default function fortyTwo() { return 42;} | |
// file 3 contents | |
export default function insideDirectory() {} | |
// in some other file | |
// bad | |
import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename | |
import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export | |
import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export | |
// bad | |
import CheckBox from './check_box'; // PascalCase import/export, snake_case filename | |
import forty_two from './forty_two'; // snake_case import/filename, camelCase export | |
import inside_directory from './inside_directory'; // snake_case import, camelCase export | |
import index from './inside_directory/index'; // requiring the index file explicitly | |
import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly | |
// good | |
import CheckBox from './CheckBox'; // PascalCase export/import/filename | |
import fortyTwo from './fortyTwo'; // camelCase export/import/filename | |
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" | |
// ^ supports both insideDirectory.js and insideDirectory/index.js |
export default
一个函数时、函数名小驼峰式,文件与函数名一致
function makeStyleGuide() {// ...} | |
export default makeStyleGuide; |
当 export
一个结构体、类、单例、函数库或者对象时,使用大驼峰式
const Helpers = {guid: () => return uuid(),}; | |
export default Helpers; |
简称或缩写应该全部大写或者全部小写
名字是给人读的,不是为了适应电脑的算法
// bad | |
import SmsContainer from './containers/SmsContainer'; | |
// bad | |
const HttpRequests = [// ...]; | |
// good | |
import SMSContainer from './containers/SMSContainer'; | |
// good | |
const HTTPRequests = [// ...]; | |
// best | |
import TextMessageContainer from './containers/TextMessageContainer'; | |
// best | |
const Requests = [// ...]; |
使用全大写字母设置静态变量
- 导出变量
- 是
const
定义的,保证不能被改变 - 这个变量是可信的,他的子属性都是不能被改变的
一般我们都将项目的全局参数使用这种 全大写 + 下划线分隔的常量 来定义一些系统配置参数导出,比如
const LIST_VIEW_PAGE_SIZE = 10
可以表示列表页每次加载 10 条数据;如果导出项目是一个对象,那么必须保证这个对象的所有属性都是不可变的,同时,它的属性不再是全大写,而是使用小写驼峰式。
// bad | |
const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file'; | |
// bad | |
export const THING_TO_BE_CHANGED = 'should obviously not be uppercased'; | |
// bad | |
export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables'; | |
// --- | |
// allowed but does not supply semantic value | |
export const apiKey = 'SOMEKEY'; | |
// better in most cases | |
export const API_KEY = 'SOMEKEY'; | |
// --- | |
// bad - unnecessarily uppercases key while adding no semantic value | |
export const MAPPING = {KEY: 'value'}; | |
// good | |
export const MAPPING = {key: 'value'}; |
访问器
若非必要,不要使用访问器
由于 JavaScript 的 getters/setters
是有副作用的,而且会让他人在查看代码的时候难以理解,后期也会难以维护,所以不推荐使用访问器函数,如果非要使用,可以使用自己实现的 getVal()
与 setVal()
。
// bad | |
class Dragon {get age() {// ...} | |
set age(value) {// ...} | |
} | |
// good | |
class Dragon {getAge() {// ...} | |
setAge(value) {// ...} | |
} |
如果属性或者方法是一个布尔判断值,那么使用 isVal()
或者 hasVal()
。
// bad | |
if (!dragon.age()) {return false;} | |
// good | |
if (!dragon.hasAge()) {return false;} |
如果非要使用 get()
与 set()
,那么它们两者必须同时使用
class Jedi {constructor(options = {}) { | |
const lightsaber = options.lightsaber || 'blue'; | |
this.set('lightsaber', lightsaber); | |
} | |
set(key, val) {this[key] = val; | |
} | |
get(key) {return this[key]; | |
} | |
} |
事件
当你需要向事件附加数据时,将数据封装成为一个对象,而不是使用原始值,这会使得以后可以很方便的增加附加值的字段。
// bad | |
$(this).trigger('listingUpdated', listing.id); | |
// ... | |
$(this).on('listingUpdated', (e, listingID) => {// do something with listingID}); |
而是:
// good | |
$(this).trigger('listingUpdated', { listingID: listing.id}); | |
// ... | |
$(this).on('listingUpdated', (e, data) => {// do something with data.listingID}); |
jQuery
为所有 jQuery 对象加上 $
前缀
// bad | |
const sidebar = $('.sidebar'); | |
// good | |
const $sidebar = $('.sidebar'); | |
// good | |
const $sidebarBtn = $('.sidebar-btn'); |
缓存 jQuery 结果
// bad | |
function setSidebar() {$('.sidebar').hide(); | |
// ... | |
$('.sidebar').css({'background-color': 'pink',}); | |
} | |
// good | |
function setSidebar() {const $sidebar = $('.sidebar'); | |
$sidebar.hide(); | |
// ... | |
$sidebar.css({'background-color': 'pink',}); | |
} |
使用级联查询 $('.sidebar ul')
或者子父级查询 $('.sidebar > ul')
在 jQuery 对象查询作用域下使用 find
方法
// bad | |
$('ul', '.sidebar').hide(); | |
// bad | |
$('.sidebar').find('ul').hide(); | |
// good | |
$('.sidebar ul').hide(); | |
// good | |
$('.sidebar > ul').hide(); | |
// good | |
$sidebar.find('ul').hide(); |
ES5 兼容性
直接参考 Kangax 提供的 ES5 兼容性列表