Airbnb JavaScript Style Guide
译者的话:文章中有一些词句翻译的可能不是很准确,如果有错误的话欢迎大家及时指正。
文中的深蓝色字体是我的个人见解,可略过。
原文地址戳这里:Airbnb JavaScript Style Guide,里边作者还列出了一些其他的规范,可以参考看看。
1. 变量类型
1.1 原始类型(Primitives)
当你访问一个原始类型时,直接改变他的值。
- string
- number
- boolean
- null
- undefined
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
1.2 复杂类型(Complex)
当你访问一个复杂类型时,将对其进行引用。
- object
- array
- function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
2. 引用
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. 对象
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: requireEnhancedObjectLiteralsWhy? => 简洁
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: disallowQuoteKeyInObjectsWhy? 我们一般认为主观上更容易阅读,能帮助凸显语法,并且更容易被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
,andisPrototypeof
。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. 数组
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. 解构
5.1 访问和使用多属性对象时,使用对象解构。
jscs: requireObjectDestructuringWhy? 解构能把你从创建属性的临时引用中解放出来
// 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. 字符串
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-escapeWhy? 反斜线有碍可读性,所以应该在万不得已时才用。
//bad const foo = '\'this\' \i\s \"quote\"'; //good const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`;
7. 函数
7.1 使用命名函数表达式而不是函数声明。
eslint: func-style
jscs: disallowFunctionDeclarationsWhy? 为什么?函数声明被挂起,这意味着在函数在文件中定义之前很容易引用函数。这会损害可读性和可维护性。如果您发现函数的定义足够大或足够复杂,它会干扰理解文件的其余部分,那么可能是时候将其解压缩到自己的模块中了!不要忘记给表达式命名——匿名函数会使查找错误的调用堆栈中的问题变得更加困难。(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-paramsWhy?
...
是明确表达你想要拉哪些参数,另外,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-funcWhy?使用这种方式创建函数相当于字符串和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-blocksWhy? 可以保持一致性,而且添加或删除名字时不用添加或删除空格。
//bad const f = function(){}; const g = function (){}; const h = function() {}; //good const x = function () {}; const y = function a() {};
7.12 不要改写参数。
eslint: no-param-ressignWhy? 改写过的对象作为参数传递可能会导致原始调用函数出现不必要的问题。
//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-reassignWhy? 参数再赋值会导致意外的行为发生,特别是当访问
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-spreadWhy? 你不需要提供上下文,但是你也不能很轻易地用
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]);
- 7.15 函数中如果有多个引用变量,应该每个单独放在一排,缩进,并且最后一个变量后边有逗号。
// 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: requireArrowFunctionsWhy? 它创建了的函数,有让可以执行
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, requireShorthandArrowFunctionsWhy? 语法糖。当多个函数连接在一起时,可读性比较强。
// 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: disallowParenthesesAroundArrowParamWhy? 减少视觉混乱
// 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; };