译者的话:文章中有一些词句翻译的可能不是很准确,如果有错误的话欢迎大家及时指正。
文中的深蓝色字体是我的个人见解,可略过。
原文地址戳这里:Airbnb JavaScript Style Guide,里边作者还列出了一些其他的规范,可以参考看看。
1.1 原始类型(Primitives)
当你访问一个原始类型时,直接改变他的值。
const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
1.2 复杂类型(Complex)
当你访问一个复杂类型时,将对其进行引用。
const foo = [1, 2];
const bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
2.1 所有引用中使用const
,避免使用var
。
eslint:
prefer-const,
no-const-assign
为什么要这么用呢?因为这保证了你不能再次你更改引用变量,而更改引用变量可能会导致bug和代码理解困难。
//bad
var count = 1;
if(true) {
count += 1;
}
//good, use the let.
let count = 1;
if(true) {
count += 1;
}
2.2 如果必须要改变变量的值,用let
替代var
。
eslint: no-var jscs: disallowVar
为啥?因为
let
是块级作用域,好过使用函数作用域的var
。
//bad
var count = 1;
if(true){
count += 1;
}
//good, use the let.
let count = 1;
if(true) {
count += 1;
}
2.3 注意!let
和const
声明的变量都拥有块级作用域
//const和let只存在于定义他们的作用域中
{
let a = 1;
const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError
3.1 创建对象时,尽量使用字面量方法。
eslint: no-new-object
//bad
const item = new Object();
//good
const item = {};
3.2 创建动态属性名称时,使用计算属性名称。
为什么?因为他们允许在同一个地方定义一个对象的所有属性。
function getKey(k) {
return `a key named ${k}`;
}
//bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
//good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
3.3 使用对象快速创建法。
eslint: object-shorthand
jscs: requireEnhancedObjectLiterals
// bad
const atom = {
value: 1;
addValue: function(value) {
return atom.value + value;
},
};
//good
const atom = {
value: 1,
addValue(value) {
return atom.value + value;
},
};
3.4 属性值简写。
eslint: object-shorthand
jscs: requireEnhancedObjectLiterals
Why? => 简洁
const lukeSkywalker = 'Luke Skywalker';
//bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
//good
const obj = {
lukeSkywalker,
};
3.5 将简写属性全部写在对象声明的开头
Why? => 方便看出哪个简写了
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJediWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};
3.6 仅把含无效标示的属性用引号括起来。
eslint: quote-props
jscs: disallowQuoteKeyInObjects
Why? 我们一般认为主观上更容易阅读,能帮助凸显语法,并且更容易被JS引擎优化。
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};
3.7 不要直接调用object.prototype
方法,例如hasOwnProperty
,`propertyIsEnmerable
,and isPrototypeof
。
Why? 这些属性有可能被对象属性掩盖了,如
{ hasOwnProperty: false }
,或者可能是一个null对象(Object.create(null))
。
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object, key));
// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));
3.8 Prefer the object spread operator over Object.assign to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// 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 }
4.1 创建时使用字面量方法。
eslint: no-array-constructor
//bad
const items = new Array();
//good
const items = [];
4.2 使用Array#push代替直接添加项到数组中。
const someStack = [];
// bad
someStack[someStack.length] = 'despacito';
// good
someStack.push('despacito');
4.3 拷贝数组时使用...
//bad
const len = items.length;
const itemsCopy = [];
let i;
for(i=0; i<len; i+=1) {
itemsCopy[i] = items[i];
}
//good
const itemsSopy = [...items];
4.4 把一个类数组的对象转换为数组时,用Array.from
const foo document.querySelectorAll('.foo');
const nodes = Array.from(foo);
4.5 数组方法回调时使用return
。如果function中只包含一些简单的逻辑,并且无副作用的话可以省略。
following: 8.2
eslint: array-callback-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;
});
4.6 如果一个数组中有很多行的话,在开始数组的括号后和结束数组的括号前使用换行符。
// bad
const arr = [
[0, 1], [2, 3], [4, 5],
];
const objectInArray = [{
id: 1,
}, {
id: 2,
}];
const numberInArray = [
1, 2,
];
// good
const arr = [[0, 1], [2, 3], [4, 5]];
const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];
const numberInArray = [
1,
2,
];
5.1 访问和使用多属性对象时,使用对象解构。
jscs: requireObjectDestructuring
Why? 解构能把你从创建属性的临时引用中解放出来
// 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}`;
}
5.2 使用数组解构。jscs: requireArrayDestructuring
“`
const arr = [1,2,3,4];
//bad
const first = arr[0];
const second = arr[1];
//good
const [first, second] = arr;
“`
5.3 要返回多个值的时候,不适用数组解构,而是使用对象解构。
Why? 可以随时添加新的属性或者改变顺序,不必担心中断了请求。
// bad
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}
// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);
// good
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}
// the caller selects only the data they need
const { left, top } = processInput(input);
6.1 字符串用单引号''
。
eslint: quote
jscs: validateQuoteMarks
//bad
const name = "Capt. Janeway";
//bad - template literals should contain interpolation or newlines
const name = `Capt.janway`;
//good
const name = 'Capt. Janeway';
6.2 超过100字符的字符串不应该多行表示(使用字符串连接符)。
// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';
// bad
const errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.';
// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
6.3 以编程方式构建字符串时,使用模板字符串而不是直接串联起来。
Why? 模板字符串可读性强,语法简介,还方便完成字符串插值。
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name) {
return `How are you, ${ name }?`;
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
6.4 永远都不要在字符串中使用eval()
,漏洞多。
eslint: no-eval
JavaScript中的eval()不安全,可能会被利用做XSS攻击,eval也存在一个安全问题,因为它可以执行传给它的任何字符串,所以永远不要传入字符串或者来历不明和不受信任源的参数。
6.5 字符串中不需要没必要的转移字符。
eslint: no-useless-escape
Why? 反斜线有碍可读性,所以应该在万不得已时才用。
//bad
const foo = '\'this\' \i\s \"quote\"';
//good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;
7.1 使用命名函数表达式而不是函数声明。
eslint: func-style
jscs: disallowFunctionDeclarations
Why? 为什么?函数声明被挂起,这意味着在函数在文件中定义之前很容易引用函数。这会损害可读性和可维护性。如果您发现函数的定义足够大或足够复杂,它会干扰理解文件的其余部分,那么可能是时候将其解压缩到自己的模块中了!不要忘记给表达式命名——匿名函数会使查找错误的调用堆栈中的问题变得更加困难。(Discussion)
// bad
function foo(){
//...
}
// bad
const foo = function (){
//...
};
// good
const foo = function bar(){
//...
};
7.2 把立即调用的表达式放入括号中。
eslint: wrap-iife
jscs: requireParentheseAroundIIFE
一个立即调用函数表达式是一个单独的单元。值得注意的是,在这个世界上,模块无处不在,你几乎从不需要IIFE
```
//immediately-invoked function expression(IIFE)
(function(){
console.log('Welcome to the Internet. Please follow me');
})
```
* 7.3 永远都不要再一个非函数作用域(if, while, etc.)中声明函数。浏览器允许你这样做,但是他们各自都有不同的解释。
eslint: no-loop-func
7.4 注意:ECMA-262中定义了block
语句列表。函数声明不算是语句。Read ECMA-262’s note on this issue
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
7.5 永远不要给参数命名为arguments
。这将优先于每个函数作用域中的arguments
对象。
//bad
function foo(name, options, arguments) {
//...
}
//good
function foo(name, option, args) {
//...
}
7.6 永远不要使用arguments
,选择使用rest语法的...
替代。
eslint: prefer-rest-params
Why?
...
是明确表达你想要拉哪些参数,另外,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(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 = {}) {
// ...
}
7.8 避免默认参数的副作用
Why? 容易混淆
var b = 1;
//bad
function count(a=b++) {
console.log(a);
}
count(); //1
count(); //2
count(3); //3
count(); //3
7.9 永远把默认参数放在最后
//bad
function handleThings(opt = {}, name) {
//...
}
//good
function handleThings(name, opt = {}) {
//...
}
7.10 永远不要使用函数构造函数来创建一个新函数。
eslint: no-new-func
Why?使用这种方式创建函数相当于字符串和eval()一样,会有漏洞。
//bad
var add = new Function('a', 'b', 'return a+b');
//still bad
var subtract = Function('a', 'b', 'return a-b');
7.11 函数中使用空格。
eslint: space-before-function-paren
space-before-blocks
Why? 可以保持一致性,而且添加或删除名字时不用添加或删除空格。
//bad
const f = function(){};
const g = function (){};
const h = function() {};
//good
const x = function () {};
const y = function a() {};
7.12 不要改写参数。
eslint: no-param-ressign
Why? 改写过的对象作为参数传递可能会导致原始调用函数出现不必要的问题。
//bad
function f1(obj) {
obj.key = 1;
}
//good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1
}
7.13 不要给参数再赋值。
eslint: no-param-reassign
Why? 参数再赋值会导致意外的行为发生,特别是当访问
argument
对象时。它还可能导致优化问题,特别是在V8中。
// bad
function f1(a) {
a = 1;
// ...
}
function f2(a) {
if (!a) { a = 1; }
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
function f4(a = 1) {
// ...
}
7.14 调用可变参数函数时,使用扩展操作符...
。
eslint: prefer-spread
Why? 你不需要提供上下文,但是你也不能很轻易地用
apply
构成`new
// 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,
);
8.1 当您必须使用函数表达式(如传递匿名函数时),请使用箭头函数表示法。
eslint: prefer-arrow-callback, arrow-spacing
jscs: requireArrowFunctions
Why? 它创建了的函数,有让可以执行
this
的上下文。这是一种很简洁的语法Why not? 如果具有相当复杂的函数,则可以将该逻辑移到自己的函数声明中。
// 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;
})
8.2 如果函数体由一个语句组成,返回一个没有副作用的表达式,省略括号并使用隐式返回。或者,保留括号并使用返回语句。
eslint: arrow-parens, arrow-body-style
jscs: disallowParentheseAroundArrowParam, requireShorthandArrowFunctions
Why? 语法糖。当多个函数连接在一起时,可读性比较强。
// 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;
// bad
foo(() => bool = true);
// good
foo(() => {
bool = true;
});
8.3 如果表达式跨越多行,则用圆括号括起来以提高可读性。
Why? 使函数开头和结尾都很明晰。
// bad
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
);
// good
['get', 'post', 'put'].map(httpMethod => (
Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
));
8.4 如果函数只有一行并且没有使用圆括号,可以直接省略括号。否则,始终将圆括号括在参数中以确保清楚性和一致性。注意:一直使用圆括号也可以,这种情况下使用“always” option的eslint,jscs则不使用disallowParenthesesAroundArrowParam。
eslint: arrow-parens
jscs: disallowParenthesesAroundArrowParam
Why? 减少视觉混乱
// 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;
});
8.5 避免将箭头函数语法( => )与比较运算符混淆( <=, >= )。
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;
};