➰arrow Function
- 箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数
const 函数名 = (参数...) => {函数声明}||表达式
- 执行体为函数声明时需要加上
{}
,参数的规则参看上文内容
//支持解构函数 const f = ([a,b]=[1,2],{c:c}={c:3})=>a+b+c; f(); 👉 6;
Classes & Constructors(类与构造函数)
- 避免直接使用
prototype
, 多用class
。因为class
语法更加简洁和且阅读性更棒
// 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
实现继承,因为这是继承原型的内置功能
// 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() luke.setHeight(20);
- 写一个通用的
toString()
方法也没问题,但是需要保证其能执行且没有其他影响
class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } }
- 如果没有指定类,那么类需要有一个默认的构造方法。一个空的构造函数或者只是委托给父类是没有必要的
// 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'; } }
- 避免出现两个一样的类成员,因为前一个成员会被覆盖从而导致错误
// bad class Foo { bar() { return 1; } bar() { return 2; } } // good class Foo { bar() { return 1; } } // good class Foo { bar() { return 2; } }
Modules(模块)
- 始终使用模块(
import
/export
)来代替非标准的模块系统。你可以选择你喜欢的模块系统,因为模块代表未来
// bad const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // best import { es6 } from './AirbnbStyleGuide'; export default es6;
- 不要使用通配符进行导出,从而保证你输出一个独立的导出
// bad import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // good import AirbnbStyleGuide from './AirbnbStyleGuide';
- 不要把导入和导出写在一起,虽然一行简明扼要,但是我们更需要明确的导入方式和导出方式,保持其一致性
// bad // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // good // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6;
- 一个路径一次支持一个导入,因为一个路径一次支持有多个导入,会使代码变得难以维护
// 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';
- 拒绝导出可变绑定,这种方式通常应该避免,但是不排除有某些特殊情况需要这么做,但是应该记住,通常只导出常量引用
// bad let foo = 3; export { foo }; // good const foo = 3; export { foo };
- 在具有单一导出的模块中,建议使用默认导出而不是命名导出,这样对于代码的可读性和可维护性更加友好
// bad export function foo() {} // good export default function foo() {}
- 把所有的导入语句放在一起
// bad import foo from 'foo'; foo.init(); import bar from 'bar'; // good import foo from 'foo'; import bar from 'bar'; foo.init();
- 多行导入应该项多行数组和对象一样缩进,这样保持
{}
内容的一致性
// bad import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // good import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path';
- 导出语句中不允许出现
webpack
加载器语法。因为导入中使用加载器语法会将代码耦合到模块打包器中,,更建议使用webpack.config.js
// 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';
Iterators and Generators(迭代器和发生器)
- 不要使用迭代器,更推荐使用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);
- 不要使用发生器,因为他们还没有很好的兼容
- 如果你一定要用发生器,一定要注意关键字符的间距,举个例子,
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* () { // ... };
Properties(属性)
- 通过常量访问属性的时候使用
.
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');
- 使用
**
进行指数运算
// bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10;
Variables(变量)
- 总是使用
const
或者let
来声明变量,这样做可以避免污染全局命名空间
// bad superPower = new SuperPower(); // good const superPower = new SuperPower();
- 每个变量声明都对应一个
const
或者let
。这样做,可以独立的声明每一个变量,而不需要考虑;
和,
的关系,同时也方便对每个声明进行调试,而不是跳过所有的声明
// bad const items = getItems(), goSportsTeam = true, dragonball = 'z'; // bad // (compare to above, and try to spot the mistake) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // good const items = getItems(); const goSportsTeam = true; const dragonball = 'z';
- 对
let
和const
进行分组,这样增强代码可读性
// 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;
- 在需要的地方声明变量,因为
const
和let
是块作用域而不是函数作用域
// bad - unnecessary function call 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; }
- 不要进行链式声明变量的操作,这样可能创建隐式的全局变量
// 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 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`
不要使用一元递增和递减操作符(++,--),因为一元递增和一元递减可能受到分号插入的影响,并且可能导致应用中的值递增或者递减,并且不会报错。使用 num += 1
类似的语句也更加有表现力,并且可以避免预先递增或者递减从而导致程序发生意外
// bad const 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 const 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; ```
Hoisting(变量提升)
var
声明被置于函数作用域的顶部,但是他们的赋值不是,const
和let
声明会被置于一个新概念TDZ内。因此,typeof()
方法不再安全
// we know this wouldn’t work (assuming there // is no notDefined global variable) function example() { console.log(notDefined); // => throws a ReferenceError } // creating a variable declaration after you // reference the variable will work due to // variable hoisting. Note: the assignment // value of `true` is not hoisted. function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // the interpreter is hoisting the variable // declaration to the top of the scope, // which means our example could be rewritten as: function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // using const and let 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(); // => 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'); }; } // the same is true when the function name // is the same as the variable name. 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'); } }
Comparison Operators & Equality(比较操作符和等号)
- 使用
===
,!==
取代==
,!=
- 条件语句比如
if
会强制使用ToBoolean
抽象方法来进行转换,并且遵循以下规则:
- Objects 转换为 true
- Undefined 转换为 false
- Null 转换为 false
- Booleans 转换为 the value of the boolean
- Numbers 转换为 false 如果是 +0, -0, or NaN, 其余为 true
- Strings 转换为 false 如果是空字符串
''
, 其余为 true
if ([0] && []) { // true // an array (even an empty one) is an object, objects will evaluate to 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
使用{}
来创建块,比如let
,const
,function
,class
也是如此。因为在整个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 {} } }
- 三元表达式不应该嵌套,而应该单行表达
// bad const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // split into 2 separated ternary expressions const maybeNull = value1 > value2 ? 'baz' : null; // better const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // best 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;
- 当多个运算符混在一个语句中时,将需要的运算符括在括号里面,并且用括号区分开
**
,%
与+
,-
,*
,/
,这样代码更加有可读性,并且澄清了开发者的意图
// bad const foo = a && b < 0 || c > 0 || d + 1 === 0; // bad const bar = a ** b - 5 % d; // bad // one may be confused into thinking (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;
Blocks(块)
- 所有的多行块都要用
{}
// 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
,else
需要和if
的}
在同一行
// bad if (test) { thing1(); thing2(); } else { thing3(); } // good if (test) { thing1(); thing2(); } else { thing3(); }
- 如果一个
if else
语句内每个代码块都用了return
语句,那么else
语句就没有必要,分成多个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; } }
Control Statements(控制语句)
- 如果你的控制语句,比如
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(); }
Comments(注释)
多行注释使用 /** ... */
// bad // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
- 单行注释用
//
,并且在注释内容的上一行,在注释语句之前要空一行,当然,如果注释在文件的第一行就不需要空行了
// bad const active = true; // is current tab // good // is current tab const active = true; // bad function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // good function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // also good function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; }
注释文字以空格作为开始,方便阅读
// bad //is current tab const active = true; // good // is current tab const active = true; // bad /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } - 为你的提交或者评论加上 `FIXME` 或者 `TODO` 的前缀,好让其他开发者迅速明白你的意思。 `FIXME`表示这个问题需要弄清楚,`TODO`表示这个问题需要解决 - 使用 `// FIXME` 去注释问题
class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; } } ```
- 使用
// TODO
去注释问题的解决方法
class Calculator extends Abacus { constructor() { super(); // TODO: total should be configurable by an options param this.total = 0; } }